In a recent project I was interested in Dragging shapes with the mouse in a WPF / MVVM Visual Studio project.
This post contains an example on how to drag a rectangle from from one canvas location to another using the WPF / MVVM architecture.
This project uses Microsoft Visual Studio Community 2019.
Step 1: Create a new WPF project:
Step 2: Add event-handling and event-raising infrastructure
Just add the following classes to your project:
EventArgs.cs
using System;
namespace Canvas1
{
public class EventArgs<T> : EventArgs
{
public EventArgs(T value)
{
Value = value;
}
public T Value { get; private set; }
}
}
EventRaiser.cs
using System;
namespace Canvas1
{
public static class EventRaiser
{
public static void Raise(this EventHandler handler, object sender)
{
handler?.Invoke(sender, EventArgs.Empty);
}
public static void Raise<T>(this EventHandler<EventArgs<T>> handler, object sender, T value)
{
handler?.Invoke(sender, new EventArgs<T>(value));
}
public static void Raise<T>(this EventHandler<T> handler, object sender, T value) where T : EventArgs
{
handler?.Invoke(sender, value);
}
public static void Raise<T>(this EventHandler<EventArgs<T>> handler, object sender, EventArgs<T> value)
{
handler?.Invoke(sender, value);
}
}
}
RelayCommand.cs
using System;
using System.Windows.Input;
namespace Canvas1
{
public class RelayCommand<T> : ICommand
{
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
public RelayCommand(Action<T> execute)
: this(execute, null)
{
_execute = execute;
}
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
throw new ArgumentNullException(nameof(execute));
_execute = execute;
_canExecute = canExecute;
}
#region ICommand Members
public bool CanExecute(object parameter)
{
return (_canExecute == null) || _canExecute((T)parameter);
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
#endregion
}
public class RelayCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public RelayCommand(Action<object> execute)
: this(execute, null)
{
_execute = execute;
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#region ICommand Members
public bool CanExecute(object parameter)
{
return (_canExecute == null) || _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
// Ensures WPF commanding infrastructure asks all RelayCommand objects whether their
// associated views should be enabled whenever a command is invoked
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
CanExecuteChangedInternal += value;
}
remove
{
CommandManager.RequerySuggested -= value;
CanExecuteChangedInternal -= value;
}
}
#endregion
private event EventHandler CanExecuteChangedInternal;
public void RaiseCanExecuteChanged()
{
CanExecuteChangedInternal.Raise(this);
}
}
}
Step 3: Use a dependency property to obtain mouse x,y coordinates
MouseBehaviour.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
namespace Canvas1
{
public class MouseBehaviour : Behavior<Panel>
{
public static readonly DependencyProperty MouseYProperty = DependencyProperty.Register(
"MouseY", typeof(double), typeof(MouseBehaviour), new PropertyMetadata(default(double)));
public static readonly DependencyProperty MouseXProperty = DependencyProperty.Register(
"MouseX", typeof(double), typeof(MouseBehaviour), new PropertyMetadata(default(double)));
public double MouseY
{
get { return (double)GetValue(MouseYProperty); }
set { SetValue(MouseYProperty, value); }
}
public double MouseX
{
get { return (double)GetValue(MouseXProperty); }
set { SetValue(MouseXProperty, value); }
}
protected override void OnAttached()
{
AssociatedObject.MouseMove += AssociatedObjectOnMouseMove;
}
protected override void OnDetaching()
{
AssociatedObject.MouseMove -= AssociatedObjectOnMouseMove;
}
private void AssociatedObjectOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
{
var pos = mouseEventArgs.GetPosition(AssociatedObject);
MouseX = pos.X;
MouseY = pos.Y;
}
}
}
Step 4: Add necessary references
In this example we need to add a reference to System.Windows.Interactivity.
This will allow us to continuously monitor the mouse position as it is being moved around the screen.
Right-click your References folder and select Add reference… (Sometimes this library will need to be installed via NuGet package manager)
Step 5: Add the Main Window View Model (MVVM) class
MainWindowViewModel.cs
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Input;
namespace Canvas1
{
public class MainWindowViewModel : INotifyPropertyChanged
{
bool captured = false;
public ICommand _leftButtonDownCommand;
public ICommand _leftButtonUpCommand;
public ICommand _previewMouseMove;
public ICommand _leftMouseButtonUp;
public MainWindowViewModel()
{
PanelX = 100;
PanelY = 100;
RectX = PanelX - 50.0;
RectY = PanelY - 50.0;
}
public ICommand PreviewMouseMove
{
get
{
return _previewMouseMove ?? (_previewMouseMove = new RelayCommand(
x =>
{
if (captured)
{
RectX = PanelX - 50.0;
RectY = PanelY - 50.0;
}
}));
}
}
public ICommand LeftMouseButtonUp
{
get
{
return _leftMouseButtonUp ?? (_leftMouseButtonUp = new RelayCommand(
x =>
{
captured = false;
}));
}
}
public ICommand LeftMouseButtonDown
{
get
{
return _leftButtonDownCommand ?? (_leftButtonDownCommand = new RelayCommand(
x =>
{
captured = true;
}));
}
}
private double _panelX;
private double _panelY;
private double _rectX;
private double _rectY;
public double RectX
{
get { return _rectX; }
set
{
if (value.Equals(_rectX)) return;
_rectX = value;
OnPropertyChanged("RectX");
}
}
public double RectY
{
get { return _rectY; }
set
{
if (value.Equals(_rectY)) return;
_rectY = value;
OnPropertyChanged("RectY");
}
}
public double PanelX
{
get { return _panelX; }
set
{
if (value.Equals(_panelX)) return;
_panelX = value;
OnPropertyChanged("PanelX");
}
}
public double PanelY
{
get { return _panelY; }
set
{
if (value.Equals(_panelY)) return;
_panelY = value;
OnPropertyChanged("PanelY");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
VerifyPropertyName(propertyName);
var handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
[Conditional("DEBUG")]
private void VerifyPropertyName(string propertyName)
{
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
throw new ArgumentNullException(GetType().Name + " does not contain property: " + propertyName);
}
}
}
Step 6: Create an example WPF window
MainWindow.xaml
<Window x:Class="Canvas1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Canvas1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="525">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<DockPanel>
<!--Un-comment if you want to view the coordinates-->
<!--<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<TextBlock Text="{Binding PanelX, StringFormat='X={0}'}" />
<TextBlock Text="{Binding PanelY, StringFormat='y={0}'}" />
</StackPanel>-->
<Canvas x:Name="LayoutRoot" Background="White">
<i:Interaction.Behaviors>
<local:MouseBehaviour
MouseX="{Binding PanelX, Mode=OneWayToSource}"
MouseY="{Binding PanelY, Mode=OneWayToSource}"
/>
</i:Interaction.Behaviors>
<Rectangle
x:Name="testSquare"
Fill="Red" Height="100"
Stroke="Black"
Width="100"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Canvas.Left="{Binding RectX, Mode=TwoWay}"
Canvas.Top="{Binding RectY, Mode=TwoWay}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseDown" >
<i:InvokeCommandAction
Command="{Binding ElementName=testSquare, Path=DataContext.LeftMouseButtonDown}"
CommandParameter="{Binding}" />
</i:EventTrigger>
<i:EventTrigger EventName="PreviewMouseUp" >
<i:InvokeCommandAction
Command="{Binding ElementName=testSquare, Path=DataContext.LeftMouseButtonUp}"
CommandParameter="{Binding}" />
</i:EventTrigger>
<i:EventTrigger EventName="PreviewMouseMove" >
<i:InvokeCommandAction
Command="{Binding ElementName=testSquare, Path=DataContext.PreviewMouseMove}"
CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Rectangle>
</Canvas>
</DockPanel>
</Grid>
</Window>
Demonstration:
Video of application as follows: