Как написать ViewModelBase в MVVM

Я новичок в среде программирования WPF. Я пытаюсь написать программу с использованием шаблона проектирования MVVM.

Я провел несколько исследований и прочитал некоторые статьи, связанные с этим, и много раз я наткнулся на эту вещь под названием

ViewModelBase

Я знаю, что это такое. Но могу ли я узнать конкретно , с чего мне начать, чтобы я мог написать свою собственную ViewModelBase? Как... На самом деле понять, что происходит, не становясь слишком сложным. Спасибо:)

Ответ 1

Не стоит использовать фреймворки MVVM, если вы не знаете, что происходит внутри.

Итак, давайте шаг за шагом и создадим свой собственный класс ViewModelBase.

  1. ViewModelBase является общим классом для всех ваших моделей представления. Давайте перенесем всю общую логику в этот класс.

  2. Ваши ViewModels должны реализовывать INotifyPropertyChanged (вы понимаете, почему?)

    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    

    [CallerMemberName] не является обязательным, но он позволит вам написать: OnPropertyChanged(); вместо OnPropertyChanged("SomeProperty"); , так что вы избежите строковой константы в вашем коде. Пример:

    public string FirstName
    {
        set
        {
            _firtName = value;
            OnPropertyChanged(); //instead of OnPropertyChanged("FirstName") or OnPropertyChanged(nameof(FirstName))
        }
        get{ return _firstName;}
    }
    

    Обратите внимание, что OnPropertyChanged(() => SomeProperty) больше не рекомендуется, так как у нас nameof оператор nameof в С# 6.

  3. Обычной практикой является реализация свойств, вызывающих PropertyChanged, следующим образом:

    public string FirstName
    {
        get { return _firstName; }
        set { SetProperty(ref _firstName, value); }
    }
    

    Давайте определим SetProperty в вашей viewmodelbase:

    protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")
    {
        if (EqualityComparer<T>.Default.Equals(storage, value))
            return false;
        storage = value;
        this.OnPropertyChanged(propertyName);
        return true;
    }
    

    Он просто запускает событие PropertyChanged когда значение свойства изменяется и возвращает true. Это не вызывает событие, когда значение не изменилось, и возвращает false. Основная идея заключается в том, что метод SetProperty является виртуальным, и вы можете расширить его в более конкретный класс, например, для запуска проверки или вызова события PropertyChanging.

Это мило это. Это все, что должна содержать ViewModelBase на этом этапе. Остальное зависит от вашего проекта. Например, ваше приложение использует навигацию по страницам, и вы написали свой собственный NavigationService для обработки навигации из ViewModel. Таким образом, вы можете добавить свойство NavigationService в свой класс ViewModelBase, чтобы иметь к нему доступ со всех ваших моделей представления, если хотите.

Чтобы получить возможность повторного использования и сохранить SRP, у меня есть класс с именем BindableBase, который в значительной степени является реализацией INotifyPropertyChanged, как мы сделали здесь. Я повторно использую этот класс в каждом решении WPF/UWP/Silverligt/WindowsPhone, потому что он универсален.

Затем в каждом проекте я создаю собственный класс ViewModelBase, производный от BindableBase:

public abstract ViewModelBase : BindableBase
{
    //project specific logic for all viewmodels. 
    //E.g in this project I want to use EventAggregator heavily:
    public virtual IEventAggregator () => ServiceLocator.GetInstance<IEventAggregator>()   
}

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

public abstract PageViewModelBase : ViewModelBase
{
    //for example all my pages has title:
    public string Title {get; private set;}
}

Я мог бы иметь другой класс для диалогов:

public abstract DialogViewModelBase : ViewModelBase
{
    private bool? _dialogResult;

    public event EventHandler Closing;

    public string Title {get; private set;}
    public ObservableCollection<DialogButton> DialogButtons { get; }

    public bool? DialogResult
    {
        get { return _dialogResult; }
        set { SetProperty(ref _dialogResult, value); }
    }

    public void Close()
    {
        Closing?.Invoke(this, EventArgs.Empty);
    }
}

Ответ 2

У вас есть пакет nuget для реализации MVVM

  • Индикатор MVVM
  • MVVM Cross
  • Prism

Для меня легче для новичков - это MVVM light, потому что он предоставляет некоторый пример кода.

Итак, лучше установить этот пакет nuget, посмотреть на сгенерированный код и вернуться к нам для получения дополнительных пояснений, если вам нужно.

Ответ 3

Мне нравится эта BaseVewModel, она дает хороший чистый стиль для ваших моделей просмотра. Проверьте различные сравнения "до" и "после". Конечно, ничего не является обязательным - если вам не нравится функция, которую предоставляет BaseViewModel, не используйте ее. Или измените его, потому что у вас есть исходный код. В частности, обратите внимание, что есть три различных способа реализации свойств с уведомлением об изменениях - выберите уровень сложности, который вы понимаете/чувствуете себя комфортно.

Ответ 4

В большинстве инфраструктур MVVM базовые классы ViewModel фактически содержат очень мало кода - обычно это просто реализация INotifyPropertyChanged и некоторые вспомогательные функции.

Взгляните на исходный код MVVM Light ViewModelBase и ObservableObject. ObservableObject - это в основном реализация INotifyPropertyChanged - с использованием выражения lambda, а не "магических строк" ​​для имени свойства. ViewModelBase расширяет ObservableObject и в основном является служебным методом, чтобы определить, работаете ли вы в дизайнере Visual Studio

Ответ 5

Приведенный ниже класс можно использовать как ViewModelBase в проектах WPF:

public abstract class ViewModelBase : INotifyPropertyChanged
{
    /// <summary>
    /// Multicast event for property change notifications.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Checks if a property already matches a desired value.  Sets the property and
    /// notifies listeners only when necessary.
    /// </summary>
    /// <typeparam name="T">Type of the property.</typeparam>
    /// <param name="storage">Reference to a property with both getter and setter.</param>
    /// <param name="value">Desired value for the property.</param>
    /// <param name="propertyName">Name of the property used to notify listeners.This
    /// value is optional and can be provided automatically when invoked from compilers that
    /// support CallerMemberName.</param>
    /// <returns>True if the value was changed, false if the existing value matched the
    /// desired value.</returns>
    protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (object.Equals(storage, value)) return false;
        storage = value;
        // Log.DebugFormat("{0}.{1} = {2}", this.GetType().Name, propertyName, storage);
        this.OnPropertyChanged(propertyName);
        return true;
    }

    /// <summary>
    /// Notifies listeners that a property value has changed.
    /// </summary>
    /// <param name="propertyName">Name of the property used to notify listeners.  This
    /// value is optional and can be provided automatically when invoked from compilers
    /// that support <see cref="CallerMemberNameAttribute"/>.</param>
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var eventHandler = this.PropertyChanged;
        if (eventHandler != null)
            eventHandler(this, new PropertyChangedEventArgs(propertyName));
    }
}

И пример класса ViewModel:

public class MyViewModel : ViewModelBase
{
    private int myProperty;
    public int MyProperty
    {
        get { return myProperty; }
        set { SetProperty(ref myProperty, value);
    }
}

Ответ 6

Здесь есть хорошая дискуссия: https://codereview.stackexchange.com/q/13823 по этому вопросу. Использует хороший подход с использованием выражений, чтобы вы получали безопасность типа при создании измененных свойств.