Страницы

Поиск по вопросам

суббота, 30 ноября 2019 г.

Drag&Drop по правилам WPF

#c_sharp #wpf #mvvm #binding #drag_n_drop


Подскажите пожалуйста как сделать правильно drag&drop прибиндив его положение к MVVM,
чтоб можно было бы его сохранить в файл просеарилизировав в JSON? Я знаю как это всё
делается по "старинке", поставить событие на Mouse_Down, Mouse_Move и Mouse_Up, а когда
происходит перетаскивание в Mouse_Move делать на подобии "canvas.setleft(uiobj, canvas.getleft(uiobj)+mouse.x)"
(этот пример не будет работать я знаю), а вот как в mvvm всё это переделать я не знаю :-(


    


Ответы

Ответ 1



Смотрите. Давайте поделим всё на визуальную и модельную части. Визуальная часть занимается перетаскиванием. При определении факта начала перетаскивания нужно отвязаться от VM (проще всего, наверное, скрыть элемент и показать на его месте другой, заодно можно немного поменять его вид, например, на полупрозрачный), обработать перетаскивание через MouseMove/MouseUp, определить, куда был дропнут элемент, отослать новые координаты в VM, и снова включить отображение элемента. VM обновит данные, новые координаты вступят в силу через привязку. Всё. Написал простой пример MVVM-приложения с перетаскиванием. Приложение рисует набор из перетаскиваемых квадратов. Программа простая, поэтому я обхожусь без модели. Будет выглядеть так: Начинаем с VM. Общий суперкласс, чтобы не имплементировать INPC каждый раз (если вы пользуетесь MVVM-фреймворком, у вас наверняка такой уже определён): class VM : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } Теперь класс, представляющий собой фигуру. Мы выкладываем публичные свойства с текущим значением позиции, и командой для смены позиции. Если вы будете сериализировать этот объект, не забудьте отметить команду несериализируемой. class SquareVM : VM { public SquareVM() { RequestMove = new SimpleCommand(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; } } Я использую примитивнейший вариант команды: class SimpleCommand : ICommand { readonly Action onExecute; public SimpleCommand(Action onExecute) { this.onExecute = onExecute; } public event EventHandler CanExecuteChanged; public bool CanExecute(object parameter) => true; public void Execute(object parameter) => onExecute((T)parameter); } Теперь, главная VM, ничего особенного: class MainVM : VM { public ObservableCollection Squares { get; } = new ObservableCollection() { 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) } }; } На этом с VM-частью покончено. Теперь, приложение. Стандартная заготовка для MVVM: App.xaml без StartupUri и переопределение OnStartup: public partial class App : Application { MainVM mainVM = new MainVM(); protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); new MainWindow() { DataContext = mainVM }.Show(); } } Переходим к интересной части: представление. Окно отображает список элементов в Canvas'е. Для привязки списка элементов использован, как обычно, ItemsControl: Сам контрол тоже несложен, код для обработки мышиных сообщений я честно стащил из ответа на вопрос «Как отследить перемещение одного окна над другим?» и выкинул всё ненужное. Маленькая тонкость: чтобы не следить за командой в DataContext'е, я объявил DependencyProperty RequestMoveCommand, и установил Binding на него. Также мы подписались в XAML'е на MouseDown и 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(); 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() 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; } } Теперь совсем всё! Вот тут модификация этого кода, чтобы вместо квадратов таскался их уменьшенный и полупрозрачный предпросмотр.

Ответ 2



Как сделать это без "многокода" с использованием Catel (https://github.com/Catel/Catel), добавляем этот namespace: xmlns:catel="http://schemas.catelproject.com" в DragDrop.DataTemplate вы можете определить любой шаблон элемента который будет 'перетягиватся': ну а если Elements == ObservableCollection<> (более точно INotifyCollectionChanged), то подписываемся на ее событие во VM...и делаем все что нужно для обработки события изменения коллекции в результате DnD. P.S. не считаю вопрос Перемещение контролов по ItemsControl с использованием MVVM дублирующим, потому что данный вопрос более общий, а тот более прикладной... (никто не будет в рабочем проекте изобретать велосипед) так же смотрите ответ здесь: https://ru.stackoverflow.com/a/1045363/358860

Комментариев нет:

Отправить комментарий