Событие NotifyPropertyChanged, в котором в аргументах события содержится старое значение
Существует ли интерфейс, похожий на INotifyPropertyChanged, в котором в аргументах события содержится старое значение изменяемого свойства, или мне нужно расширить этот интерфейс для его создания?
Например:
public String ProcessDescription
{
get { return _ProcessDescription; }
set
{
if( value != ProcessDescription )
{
String oldValue = _ProcessDescription;
_ProcessDescription = value;
InvokePropertyChanged("ProcessDescription", oldvalue);
}
}
}
InvokePropertyChanged(String PropertyName, OldValue)
{
this.PropertyChanged( new ExtendedPropertyChangedEventArgs(PropertyName, OldValue) );
}
Я также согласился бы на событие, подобное PropertyChanging, которое предоставляет эту информацию, независимо от того, поддерживает ли оно e.Cancel.
4 ответа
Как указано в ответах, мне пришлось реализовать собственное решение. Для блага других я представил это здесь:
Расширенное событие PropertyChanged
Это событие было специально разработано для обратной совместимости со старыми событиями propertyChanged. Он может использоваться взаимозаменяемо с простым PropertyChangedEventArgs вызывающими. Конечно, в таких случаях ответственность за проверку того, может ли переданный PropertyChangedEventArgs быть передан в PropertyChangedExtendedEventArgs, если они хотят его использовать, лежит на обработчике события. Нет необходимости в даункинге, если все, что их интересует, это свойство PropertyName.
public class PropertyChangedExtendedEventArgs<T> : PropertyChangedEventArgs
{
public virtual T OldValue { get; private set; }
public virtual T NewValue { get; private set; }
public PropertyChangedExtendedEventArgs(string propertyName, T oldValue, T newValue)
: base(propertyName)
{
OldValue = oldValue;
NewValue = newValue;
}
}
Расширенный интерфейс PropertyChanged
Если программист хотел создать событие, которое заставляет уведомляющие свойства включать старое значение и новое значение, им нужно только реализовать следующий интерфейс:
// Summary: Notifies clients that a property value is changing, but includes extended event infomation
/* The following NotifyPropertyChanged Interface is employed when you wish to enforce the inclusion of old and
* new values. (Users must provide PropertyChangedExtendedEventArgs, PropertyChangedEventArgs are disallowed.) */
public interface INotifyPropertyChangedExtended<T>
{
event PropertyChangedExtendedEventHandler<T> PropertyChanged;
}
public delegate void PropertyChangedExtendedEventHandler<T>(object sender, PropertyChangedExtendedEventArgs<T> e);
Пример 1
Пользователь теперь может указать более продвинутый NotifyPropertyChanged
метод, который позволяет установщикам свойств передавать их старое значение:
public String testString
{
get { return testString; }
set
{
String temp = testString;
testValue2 = value;
NotifyPropertyChanged("TestString", temp, value);
}
}
Где твой новый NotifyPropertyChanged
Метод выглядит так:
protected void NotifyPropertyChanged<T>(string propertyName, T oldvalue, T newvalue)
{
OnPropertyChanged(this, new PropertyChangedExtendedEventArgs<T>(propertyName, oldvalue, newvalue));
}
А также OnPropertyChanged
так же, как всегда:
public virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(sender, e);
}
Пример 2
Или, если вы предпочитаете использовать лямбда-выражения и полностью отказаться от жестко запрограммированных строк с именами свойств, вы можете использовать следующее:
public String TestString
{
get { return testString; }
private set { SetNotifyingProperty(() => TestString, ref testString, value); }
}
Который поддерживается следующей магией:
protected void SetNotifyingProperty<T>(Expression<Func<T>> expression, ref T field, T value)
{
if (field == null || !field.Equals(value))
{
T oldValue = field;
field = value;
OnPropertyChanged(this, new PropertyChangedExtendedEventArgs<T>(GetPropertyName(expression), oldValue, value));
}
}
protected string GetPropertyName<T>(Expression<Func<T>> expression)
{
MemberExpression memberExpression = (MemberExpression)expression.Body;
return memberExpression.Member.Name;
}
Спектакль
Если производительность является проблемой, посмотрите на этот вопрос: Реализация NotifyPropertyChanged без магических строк.
Таким образом, накладные расходы минимальны. Добавление старого значения и переключение на расширенное событие приводит к замедлению примерно на 15%, при этом допускается порядка одного миллиона уведомлений о свойствах в секунду, а переключение на лямбда-выражения - это пятикратное замедление, позволяющее получать примерно сто тысяч уведомлений о свойствах в секунду. второй. Эти цифры далеки от того, чтобы создать узкое место в любом приложении, управляемом пользовательским интерфейсом.
Общепринятый ответ велик, но я изо всех сил, чтобы проследить, какPropertyChangedExtendedEventArgs<T>
должен был быть реализован, в конце концов я понял, что это не так.
Ниже приведен полный рабочий пример, показывающий, как использовать PropertyChangedExtendedEventArgs<T>
.
using System;
using System.ComponentModel;
namespace ConsoleApp10
{
class Program
{
static void Main(string[] args)
{
var p = new Program();
p.Run();
}
private void Run()
{
// Create Poco
var poco = new MyPoco(1, "MyOldName", 150);
// Attach property changed event
poco.PropertyChanged += PocoOnPropertyChanged;
// Change data
poco.Id = 10;
poco.Name = "NewName";
poco.Height = 170;
}
/// <summary>
/// Property changed handler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void PocoOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
// Without casting 'e' is a standard PropertyChanged event
if (Equals(e.PropertyName, nameof(MyPoco.Id)))
{
Console.WriteLine($"'{nameof(MyPoco.Id)}' has changed, but we have no other data");
}
// New extended property changed event of type 'string'
if (Equals(e.PropertyName, nameof(MyPoco.Name)))
{
// Need to cast into type we know and are expecting
if (e is PropertyChangedExtendedEventArgs<string> extended)
{
Console.WriteLine(
$"'{nameof(MyPoco.Name)}' has changed, from '{extended.OldValue}' to '{extended.NewValue}'.");
}
}
// New extended property changed event of type 'double'
if (Equals(e.PropertyName, nameof(MyPoco.Height)))
{
// This cast will fail as the types are wrong
if (e is PropertyChangedExtendedEventArgs<string>)
{
// Should never hit here
}
// Cast into type we know and are expecting
if (e is PropertyChangedExtendedEventArgs<double> extended)
{
Console.WriteLine(
$"'{nameof(MyPoco.Height)}' has changed, from '{extended.OldValue}' to '{extended.NewValue}'.");
}
}
}
}
/// <summary>
/// Example POCO
/// </summary>
public sealed class MyPoco : NotifyBase
{
private int _id;
private string _name;
private double _height;
public MyPoco(int id, string name, double height)
{
_id = id;
_name = name;
_height = height;
}
public int Id
{
get => _id;
set
{
var old = _id;
_id = value;
OnPropertyChanged(old, value, nameof(Id));
}
}
public string Name
{
get => _name;
set
{
var old = _name;
_name = value;
OnPropertyChanged(old, value, nameof(Name));
}
}
public double Height
{
get => _height;
set
{
var old = _height;
_height = value;
OnPropertyChanged(old, value, nameof(Height));
}
}
}
/// <summary>
/// Notifying base class
/// </summary>
public abstract class NotifyBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged<T>(T oldValue, T newValue, string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedExtendedEventArgs<T>(oldValue, newValue, propertyName));
}
}
/// <summary>
/// Extended property changed
/// </summary>
/// <typeparam name="T"></typeparam>
public sealed class PropertyChangedExtendedEventArgs<T> : PropertyChangedEventArgs
{
public PropertyChangedExtendedEventArgs(T oldValue, T newValue, string propertyName)
: base(propertyName)
{
OldValue = oldValue;
NewValue = newValue;
}
public T OldValue { get; }
public T NewValue { get; }
}
}
Выход:
'Id' has changed, but we have no other data
'Name' has changed, from 'MyOldName' to 'NewName'.
'Height' has changed, from '150' to '170'.
Похоже, вы хотите использовать INotifyPropertyChanging
в сочетании с INotifyPropertyChanged
, Документация MSDN: http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanging.aspx
Нет, вы должны создать свой собственный с нуля.
Раньше я делал то же самое в своем исследовательском проекте Granite, но пришел к выводу, что это не стоит затрат. Рассчитывается слишком много свойств, с которыми я работаю, и запускать их дважды, чтобы вызвать событие, было слишком дорого.
Если вы хотите использовать только старое значение, вы можете вызвать событие перед изменением значения свойства. Но это было бы отклонением от того, как обычно используется это событие, поэтому я бы создал специальный интерфейс и аргументы за него.