Где размещать логику приложения MVVM?

277
18 июля 2017, 17:27

Всем привет!

Возможно вопрос типичный. Но не дает мне покоя. Если с моделями, вроде таблиц, вопросов не возникает, то в данном случае не смог найти ответа.

Суть: дорабатываю свою программу (WPF) вышивки крестиком, возникла потребность рисования с различными его аспектами (типы, выделение и тп).

Есть примерно такой код (идею подсмотрел в FastGrid):

enum CellBlockType
{
    CrossStitch,
    HalfStitch,
    //...
}
interface ISchemeCellBlock
{
    CellBlockType BlockType { get; }
    //...
}
interface ISchemeCell
{
    int BlockCount { get; }
    ISchemeCellBlock GetBlock(int index);
}
interface ISchemeModel
{
    int RowCount { get; }
    int ColumnCount { get; }
    ISchemeCell GetCell(ISchemeView view, int row, int column);
    void HandleCommand(ISchemeView view, CellAddress address, object commandParameter, ref bool handled);
}
interface ISchemeView
{
    void InvalidateCell(int row, int column);
    void InvalidateAll();
    bool ShowGuideline { get; set; }
    //...
}
struct CellAddress { }

В данный момент все действия мыши обрабатываются в HandleCommand, а в качестве commandParameter передаются MouseEvenArgs, чтобы понимать когда рисовать/не рисовать, перемещать/вставлять и тп.

Таким образом, ISchemeView изменяет данные модели минуя ViewModel, но и не в коде самого представления. В самой же ViewModel предполагается выбор "кистей рисования", масштабирование, команды ввода/вывода и все косвенные изменения модели.

Имеет ли право на жизнь такой код в рамках MVVM? Или же надо "отправлять" действия мыши из ISchemeView во ViewModel, а уже через нее воздействовать на саму модель?

Answer 1

Если вы хотите следовать паттерну MVVM, то у вас пока не получается. Model ничего не должна знать про View, а у вас знает (вы передаете ISchemeView и MouseEvenArgs)

Познакомьтесь с командами (интерфейс ICommand). Часто используются сторонние реализации типа DelegateCommand:

public class DelegateCommand : ICommand
{
    private readonly Predicate<object> _canExecute;
    private readonly Action<object> _execute;
    public event EventHandler CanExecuteChanged;
    public DelegateCommand(Action<object> execute) 
                   : this(execute, null)
    {
    }
    public DelegateCommand(Action<object> execute, 
                   Predicate<object> canExecute)
    {
        _execute = execute;
        _canExecute = canExecute;
    }
    public override bool CanExecute(object parameter)
    {
        if (_canExecute == null)
        {
            return true;
        }
        return _canExecute(parameter);
    }
    public override void Execute(object parameter)
    {
        _execute(parameter);
    }
    public void RaiseCanExecuteChanged()
    {
        if( CanExecuteChanged != null )
        {
            CanExecuteChanged(this, EventArgs.Empty);
        }
    }
}

Команда содержит ссылку на метод, который будет выполняться при выполнении команды, и ссылку на метод, который указывает, может ли выполниться команда.

Таким образом, во ViewModel вы создаете свойство типа ICommand, пишите методы на выполнение и выяснение возможности выполнения, привязываете команду ко View. Конечно, для View должно быть определено dependency property, которое отдаст правильный параметр с координатам в команду (это уже другой вопрос, разработка своего контрола, сейчас не знаю какой компонент у вас во View). Но во ViewModel будет что-то примерно такое:

public class SchemeViewModel : NotifyPropertyChangedBase
{
    ISchemeModel _model;
    public ICommand SelectCommand {get;} = new DelegateCommand(Select, CanSelect);
    void Select(object o)
    {
        var coords = o as CellAddress;
        _model.Select(coords);
    }
    bool CanSelect(object o)
    {
        var coords = o as CellAddress;
        return _model.CanSelect(coords);
    }
}

Также откажитесь от интерфейса ISchemeView, который позволяет управлять View из модели. Это фишка паттера MVC, но не MVVM. Есть много разных способов сделать что вы хотите. Например, подписаться на события модели из вьюмодели. Помните, ваша модель должна работать как независимая часть программы. Вы должны уметь изменить модель кодом. Invalidate правда происходит во View, или оно происходит в модели, а View просто отображает состояние модели?

READ ALSO
Смена обоев рабочего стола не всегда работает

Смена обоев рабочего стола не всегда работает

На windows 7 не всегда меняются обои, есть ли этому решение?

353
Совместное использование Attach() и Entry() в Entity Framework

Совместное использование Attach() и Entry() в Entity Framework

Заметил, что часто используют эти методы в связке:

281
Изменить иконку формы CSharpCodeProvider c#

Изменить иконку формы CSharpCodeProvider c#

Динамически компилирую ехе

261
Различия методов Find(), FirstOrDefault() при использовании с Entity Framework

Различия методов Find(), FirstOrDefault() при использовании с Entity Framework

Если нужно получить запись из базы данных по ее первичному ключу, можно воспользоваться и тем и другим методомОба метода вернут объект сущности,...

363