#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