Как написать ViewModelBase в MVVM
Я новичок в среде программирования WPF. Я пытаюсь написать программу с использованием шаблона проектирования MVVM.
Я провел несколько исследований и прочитал некоторые статьи, связанные с этим, и много раз я наткнулся на эту вещь под названием
ViewModelBase
Я знаю, что это такое. Но могу ли я узнать конкретно , с чего мне начать, чтобы я мог написать свою собственную ViewModelBase? Как... На самом деле понять, что происходит, не становясь слишком сложным. Спасибо:)
Ответы
Ответ 1
Не стоит использовать фреймворки MVVM, если вы не знаете, что происходит внутри.
Итак, давайте шаг за шагом и создадим свой собственный класс ViewModelBase.
-
ViewModelBase является общим классом для всех ваших моделей представления. Давайте перенесем всю общую логику в этот класс.
-
Ваши 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.
-
Обычной практикой является реализация свойств, вызывающих 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 по этому вопросу. Использует хороший подход с использованием выражений, чтобы вы получали безопасность типа при создании измененных свойств.