WPF INotifyPropertyChanged для связанных свойств только для чтения - PullRequest
9 голосов
/ 26 августа 2010

Я пытаюсь понять, как обновить пользовательский интерфейс, если у меня есть свойство только для чтения, которое зависит от другого свойства, чтобы при изменении одного свойства обновлялись оба элемента интерфейса (в данном случае текстовое поле и текстовое поле только для чтения Например:

public class raz : INotifyPropertyChanged
{

  int _foo;
  public int foo
  {
    get
    {
      return _foo;
    }
    set
    {
      _foo = value;
      onPropertyChanged(this, "foo");
    }
  }

  public int bar
  {
    get
    {
      return foo*foo;
    }
  }

  public raz()
  {

  }

  public event PropertyChangedEventHandler PropertyChanged;
  private void onPropertyChanged(object sender, string propertyName)
  {
    if(this.PropertyChanged != null)
    {
      PropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
    }
  }
}

Насколько я понимаю, панель не будет автоматически обновлять пользовательский интерфейс при изменении foo. Какой правильный способ сделать это?

Ответы [ 7 ]

12 голосов
/ 01 октября 2015

Я понимаю, что это старый вопрос, но это первый результат Google "NotifyPropertyChanged связанных свойств", поэтому я думаю, что было бы целесообразно добавить этот ответ, чтобы был какой-то конкретный код.

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

Класс атрибута:

[AttributeUsage(AttributeTargets.Property,AllowMultiple = true)]
public class DependsOnPropertyAttribute : Attribute
{
    public readonly string Dependence;

    public DependsOnPropertyAttribute(string otherProperty)
    {
        Dependence = otherProperty;
    }
}

И в моей базовой модели представления (от которой наследуются все другие модели представления WPF):

public abstract class BaseViewModel : INotifyPropertyChanged
{
    protected Dictionary<string, List<string>> DependencyMap;

    protected BaseViewModel()
    {
        DependencyMap = new Dictionary<string, List<string>>();

        foreach (var property in GetType().GetProperties())
        {
            var attributes = property.GetCustomAttributes<DependsOnPropertyAttribute>();
            foreach (var dependsAttr in attributes)
            {
                if (dependsAttr == null)
                    continue;

                var dependence = dependsAttr.Dependence;
                if (!DependencyMap.ContainsKey(dependence))
                    DependencyMap.Add(dependence, new List<string>());
                DependencyMap[dependence].Add(property.Name);
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler == null)
            return;

        handler(this, new PropertyChangedEventArgs(propertyName));

        if (!DependencyMap.ContainsKey(propertyName))
            return;

        foreach (var dependentProperty in DependencyMap[propertyName])
        {
            handler(this, new PropertyChangedEventArgs(dependentProperty));
        }
    }
}

Теперь это позволяет легко помечать свойства, например:

public int NormalProperty
{
    get {return _model.modelProperty; }
    set 
    {
        _model.modelProperty = value;
        OnPropertyChanged();
    }
}

[DependsOnProperty(nameof(NormalProperty))]
public int CalculatedProperty
{
    get { return _model.modelProperty + 1; }
}
9 голосов
/ 26 августа 2010

Один из способов указать, что бар изменился, - добавить вызов к onPropertyChanged(this, "bar") в установщике foo.Ужасно, черт возьми, я знаю, но у вас это есть.

Если foo определен в классе предка или у вас нет доступа к реализации метода установки, я полагаю, вы могли бы подписаться на PropertyChangedсобытие, так что когда вы видите изменение «foo», вы также можете запустить уведомление об изменении «бар».Подписка на события в вашем собственном экземпляре объекта одинаково уродлива, но сделает работу.

8 голосов
/ 27 августа 2010

Если это серьезная проблема (под «серьезным», я имею в виду, что у вас нетривиальное количество зависимых свойств только для чтения), вы можете создать карту зависимостей свойств, например:

private static Dictionary<string, string[]> _DependencyMap = 
    new Dictionary<string, string[]>
{
   {"Foo", new[] { "Bar", "Baz" } },
};

и затем сослаться на него в OnPropertyChanged:

PropertyChanged(this, new PropertyChangedEventArgs(propertyName))
if (_DependencyMap.ContainsKey(propertyName))
{
   foreach (string p in _DependencyMap[propertyName])
   {
      PropertyChanged(this, new PropertyChangedEventArgs(p))
   }
}

По сути, это мало чем отличается от простого помещения нескольких вызовов OnPropertyChanged в установщик Foo, поскольку необходимо обновлять карту зависимостей для каждогодобавляемое новое зависимое свойство.

Но оно позволяет впоследствии реализовать PropertyChangeDependsOnAttribute и использовать отражение для сканирования типа и построения карты зависимостей.Таким образом, ваша собственность будет выглядеть примерно так:

[PropertyChangeDependsOn("Foo")]
public int Bar { get { return Foo * Foo; } }
6 голосов
/ 26 августа 2010

Вы можете просто позвонить

OnPropertyChanged(this, "bar");

из любого места в этом классе ... Вы простудились даже так:

    public raz()
    {
        this.PropertyChanged += new PropertyChangedEventHandler(raz_PropertyChanged);
    }

    void raz_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if(e.PropertyName == "foo")
        {
             onPropertyChanged(this, "bar");
        }
    }
3 голосов
/ 27 августа 2010

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

В WPF часто есть много способов сделать то же самое.Часто нет правильного пути, просто личные предпочтения.

0 голосов
/ 21 февраля 2011

Я почти уверен, что это должно быть возможно декларативным способом, но моей первой попыткой решить эту проблему была неудача.Решение, которое я хочу достичь, использует лямбда-выражения для определения этого.Поскольку выражения могут быть проанализированы (??), должна быть возможность проанализировать выражения и присоединиться ко всем событиям NotifyPropertyChanged, чтобы получать уведомления об зависимых изменениях данных.

в ContinousLinq, это отлично работает для коллекций.

private SelfUpdatingExpression<int> m_fooExp = new SelfUpdatingExpression<int>(this, ()=> Foo * Foo);

public int Foo
{
    get
    { 
        return  m_fooExp.Value;   
    }
}

но, к сожалению, мне не хватает фундаментальных ноу-хау в выражениях и Linq: (

0 голосов
/ 26 августа 2010

В зависимости от затрат на вычисления и от того, как часто вы ожидаете, что оно будет использоваться, может быть полезно сделать его частным набором свойств и вычислить значение, когда установлено foo, а не вычислять на лету, когдаbar get называется.По сути, это решение для кэширования, и затем вы можете сделать уведомление об изменении свойства как часть частного установщика bar.Я обычно предпочитаю этот подход, главным образом потому, что я использую AOP (через Postsharp) для реализации фактического INotifyPropertyChanged шаблона.

-Dan

...