Can you please tell me how to correctly drag & drop the position to MVVM, so that it can be saved to a file by sizing it in JSON? I know how this is done in the "old fashioned way", put the event on Mouse_Down, Mouse_Move and Mouse_Up, and when dragging occurs in Mouse_Move do like "canvas.setleft (uiobj, canvas.getleft (uiobj) + mouse.x)" (this An example will not work, I know), but I don’t know how to remake it in mvvm :-(

enter image description here

  • one
    Here is a good tutorial: codeproject.com/Articles/420545/… . I did on it - Donil
  • Most likely it does not fit your situation, but it may be useful to someone who searches for the drag & drop keywords. I once did drag & drop to move list items on this tutorial and was pleased. - Andrey K.

1 answer 1

See it. Let's divide everything into visual and model parts.

The visual part is dragging. When determining the fact of the start of dragging, you need to get rid of the VM (the easiest thing is probably to hide the element and show it in its place, you can change its appearance a little, for example, to translucent), handle the dragging via MouseMove / MouseUp, determine where the element was dropped , send the new coordinates to the VM, and re-enable the display of the item.

VM will update the data, the new coordinates will take effect through the binding.

Everything.


Wrote a simple MVVM application with drag and drop. The app draws a set of draggable squares. The program is simple, so I do without a model. It will look like this:

draggable squares

We start with VM. General superclass to not implement INPC every time (if you use the MVVM framework, you probably already have one 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 post public properties with the current position value, and the team to change positions. If you will serialize this object, do not forget to mark the command non-serializable.

 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 use the most primitive version of the command:

 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, the main 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) } }; } 

This is the end of the VM part.

Now, the app. Standard preset for MVVM: App.xaml without StartupUri and redefining 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(); } } 

We turn to the interesting part: representation.

A window displays a list of items in Canvas ' e. To bind the list of items 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 simple, I honestly pulled the code for processing mouse messages from the answer to the question “ How to track the movement of one window over another? "And threw all 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> 

Small subtlety: in order not to follow the team in DataContext , I declared a DependencyProperty RequestMoveCommand , and installed a Binding on it. We also subscribed to 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; } } 

Now absolutely everything!


Here is the modification of this code, so that instead of squares, their reduced and translucent preview is dragged.

  • And what do you mean by "When determining whether a drag is started, you need to get rid of the VM"? PS All the same decided that you need to deal with MVVM, sooner or later it will still come in handy. - alex-rudenkiy
  • @ alex-rudenkiy: Well, if your properties / coordinates are determined through binding to a VM, then you need (temporarily) to remove this dependency in some way. And about the handling of dragging, I somewhere had an answer with the code. - VladD
  • @ alex-rudenkiy: Right here: ru.stackoverflow.com/a/444924/10105 (there you can throw away MigrateTo and Actor 's, they are only needed to move to a new window). - VladD
  • @ alex-rudenkiy: I wrote a working example, I’ll debug it now and then post it. - VladD
  • one
    @maxwell: Because Binding done only to properties, not to fields. - VladD