Недавнее решение, которое я придумал, заключается в инкапсуляции логики отправки событий в выделенный класс.
У класса есть открытый метод с именем Handle
, который имеет ту же сигнатуру, что и делегат PropertyChangedEventHandler
, что означает, что его можно подписать на событие PropertyChanged
любого класса, реализующего интерфейс INotifyPropertyChanged
.
Класс принимает делегаты, такие как часто используемые DelegateCommand
, используемые большинством реализаций WPF, что означает, что его можно использовать без создания подклассов.
Класс выглядит так:
public class PropertyChangedHandler
{
private readonly Action<string> handler;
private readonly Predicate<string> condition;
private readonly IEnumerable<string> properties;
public PropertyChangedHandler(Action<string> handler,
Predicate<string> condition, IEnumerable<string> properties)
{
this.handler = handler;
this.condition = condition;
this.properties = properties;
}
public void Handle(object sender, PropertyChangedEventArgs e)
{
string property = e.PropertyName ?? string.Empty;
if (this.Observes(property) && this.ShouldHandle(property))
{
handler(property);
}
}
private bool ShouldHandle(string property)
{
return condition == null ? true : condition(property);
}
private bool Observes(string property)
{
return string.IsNullOrEmpty(property) ? true :
!properties.Any() ? true : properties.Contains(property);
}
}
Затем вы можете зарегистрировать измененный свойством обработчик событий, например так:
var eventHandler = new PropertyChangedHandler(
handler: p => { /* event handler logic... */ },
condition: p => { /* determine if handler is invoked... */ },
properties: new string[] { "Foo", "Bar" }
);
aViewModel.PropertyChanged += eventHandler.Handle;
PropertyChangedHandler
заботится о проверке PropertyName
PropertyChangedEventArgs
и гарантирует, что handler
вызывается при правильных изменениях свойств.
Обратите внимание, что PropertyChangedHandler также принимает предикат, чтобы делегат обработчика мог быть условно отправлен. Класс также позволяет указывать несколько свойств, чтобы один обработчик можно было связать с несколькими свойствами за один раз.
Это может быть легко расширено с помощью некоторых методов расширений для более удобной регистрации обработчика, которая позволяет создавать обработчик событий и подписываться на событие PropertyChanged
в одном вызове метода и задавать свойства, используя выражения вместо строк для достижения чего-либо. это выглядит так:
aViewModel.OnPropertyChanged(
handler: p => handlerMethod(),
condition: p => handlerCondition,
properties: aViewModel.GetProperties(
p => p.Foo,
p => p.Bar,
p => p.Baz
)
);
Это в основном говорит о том, что когда свойства Foo
, Bar
или Baz
изменятся, handlerMethod
будет вызвано, если handlerCondition
истинно.
Перегрузки метода OnPropertychanged
предусмотрены для удовлетворения различных требований регистрации событий.
Если, например, вы хотите зарегистрировать обработчик, который вызывается для любого события измененного свойства и всегда выполняется, вы можете просто сделать следующее:
aViewModel.OnPropertyChanged(p => handlerMethod());
Если, например, вы хотите зарегистрировать обработчик, который всегда выполняется, но только для одного конкретного изменения свойства, вы можете сделать следующее:
aViewModel.OnPropertyChanged(
handler: p => handlerMethod(),
properties: aViewModel.GetProperties(p => p.Foo)
);
Я нашел этот подход очень полезным при написании приложений WPF MVVM. Представьте, что у вас есть сценарий, в котором вы хотите аннулировать команду при изменении любого из трех свойств. Используя обычный метод, вы должны сделать что-то вроде этого:
void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "Foo":
case "Bar":
case "Baz":
FooBarBazCommand.Invalidate();
break;
....
}
}
Если вы измените имя какого-либо из свойств viewModel, вам нужно будет не забыть обновить обработчик событий, чтобы выбрать правильные свойства.
Используя указанный выше класс PropertyChangedHandler
, вы можете достичь того же результата с помощью следующего:
aViewModel.OnPropertyChanged(
handler: p => FooBarBazCommand.Invalidate(),
properties: aViewModel.GetProperties(
p => p.Foo,
p => p.Bar,
p => p.Baz
)
);
Теперь это обеспечивает безопасность во время компиляции, поэтому, если какое-либо из свойств viewModel будет переименовано, программа не сможет скомпилироваться.