Look. Let's split everything into visual and model parts.Visual part is retreading. In determining the fact that the bulkhead has started, it is necessary to separate VM (mostly cover the element and show it to another one, but it can be changed slightly, e.g. semi-transparently), retread through MouseMove/MouseUp, determine where the element has been scattered, send new coordinates to VM, and re-engage the display of the element.VM will update the data, new coordinates will enter into force through the link.That's it.Write a simple example of the MVM-apping app. The annex draws a set of oversized squares. The program is simple, so I don't have a model. It'll look like: Start with VM. General super class to not implement INPC every time (if you use MVM Freimvour, you're probably already defined):class VM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now the class is a figure. We put out the public properties with the current value of the position and the team to change positions. If you're going to show this object, don't forget to celebrate a team of non-serious.class SquareVM : VM
{
public SquareVM()
{
RequestMove = new SimpleCommand<Point>(MoveTo);
}
// стандартное свойство
Point position;
public Point Position
{
get { return position; }
set { if (position != value) { position = value; NotifyPropertyChanged(); } }
}
// выставляем команду, которая занимается перемещением
public ICommand RequestMove { get; }
void MoveTo(Point newPosition)
{
// в реальности тут могут быть всякие проверки, конечно
Position = newPosition;
}
}
I'll use the most primitive version of the team:class SimpleCommand<T> : ICommand
{
readonly Action<T> onExecute;
public SimpleCommand(Action<T> onExecute) { this.onExecute = onExecute; }
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => onExecute((T)parameter);
}
Now, Chief VM, nothing special:class MainVM : VM
{
public ObservableCollection<SquareVM> Squares { get; } =
new ObservableCollection<SquareVM>()
{
new SquareVM() { Position = new Point( 30, 30) },
new SquareVM() { Position = new Point(100, 70) },
new SquareVM() { Position = new Point( 80, 0) },
new SquareVM() { Position = new Point( 90, 180) },
new SquareVM() { Position = new Point(200, 200) }
};
}
That's the end of the VM.Now, annex. Standard MVM: App.xaml without StartupUri and redeployment OnStartup:<Application x:Class="MvvmDraggable.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</Application>
public partial class App : Application
{
MainVM mainVM = new MainVM();
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
new MainWindow() { DataContext = mainVM }.Show();
}
}
Let's go to the interesting part: the show.The window is displaying a list of elements in CanvasHuh. The list of elements is used, as usual, ItemsControl:<Window x:Class="MvvmDraggable.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MvvmDraggable"
Title="Draggable squares" Height="350" Width="525">
<Grid>
<ItemsControl ItemsSource="{Binding Squares}" Width="300" Height="300"
Background="Beige">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!-- хост списка элементов - канвас, чтобы можно было
произвольно устанавливать координаты -->
<Canvas IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- сам квадрат вынесем в отдельный UserControl -->
<local:DraggableSquare/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<!-- а это привязка координат контейнера к VM -->
<Setter Property="Canvas.Left" Value="{Binding Position.X}"/>
<Setter Property="Canvas.Top" Value="{Binding Position.Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
</Window>
The control itself is also unsuccessful, the code for processing mouse messages, I honestly pulled out of the answer to the question. https://ru.stackoverflow.com/a/444924/10105 and threw everything unnecessary.<UserControl x:Class="MvvmDraggable.DraggableSquare"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="50" Height="50"
MouseDown="OnMouseDown" MouseUp="OnMouseUp"
Background="Green">
<Grid/>
</UserControl>
Little thinness: not to follow the team. DataContext'Eh, I've announced DependencyProperty RequestMoveCommandAnd I did. Binding It's him. We also signed in XAML. MouseDown and MouseUp♪public partial class DraggableSquare : UserControl
{
public DraggableSquare()
{
InitializeComponent();
// устанавливаем Binding RequestMove из VM на свойство RequestMoveCommand:
SetBinding(RequestMoveCommandProperty, new Binding("RequestMove"));
}
// стандартное DependencyProperty
#region dp ICommand RequestMoveCommand
public ICommand RequestMoveCommand
{
get { return (ICommand)GetValue(RequestMoveCommandProperty); }
set { SetValue(RequestMoveCommandProperty, value); }
}
public static readonly DependencyProperty RequestMoveCommandProperty =
DependencyProperty.Register("RequestMoveCommand", typeof(ICommand),
typeof(DraggableSquare));
#endregion
Vector relativeMousePos; // смещение мыши от левого верхнего угла квадрата
Canvas container; // канвас-контейнер
// по нажатию на левую клавишу начинаем следить за мышью
void OnMouseDown(object sender, MouseButtonEventArgs e)
{
container = FindParent<Canvas>();
relativeMousePos = e.GetPosition(this) - new Point();
MouseMove += OnDragMove;
LostMouseCapture += OnLostCapture;
Mouse.Capture(this);
}
// клавиша отпущена - завершаем процесс
void OnMouseUp(object sender, MouseButtonEventArgs e)
{
FinishDrag(sender, e);
Mouse.Capture(null);
}
// потеряли фокус (например, юзер переключился в другое окно) - завершаем тоже
void OnLostCapture(object sender, MouseEventArgs e)
{
FinishDrag(sender, e);
}
void OnDragMove(object sender, MouseEventArgs e)
{
UpdatePosition(e);
}
void FinishDrag(object sender, MouseEventArgs e)
{
MouseMove -= OnDragMove;
LostMouseCapture -= OnLostCapture;
UpdatePosition(e);
}
// требуем у VM обновить позицию через команду
void UpdatePosition(MouseEventArgs e)
{
var point = e.GetPosition(container);
// не забываем проверку на null
RequestMoveCommand?.Execute(point - relativeMousePos);
}
// это вспомогательная функция, ей место в общей библиотеке
private T FindParent<T>() where T : FrameworkElement
{
FrameworkElement current = this;
T t;
do
{
t = current as T;
current = (FrameworkElement)VisualTreeHelper.GetParent(current);
}
while (t == null && current != null);
return t;
}
}
That's it! https://ru.stackoverflow.com/a/618048/10105 Modification of this code to carry a reduced and semi-transparent view instead of squares.