Свойства зависимости, которые зависят от других свойств - PullRequest
1 голос
/ 20 февраля 2012

Класс C реализует INotifyPropertyChanged.

Предположим, что C имеет свойства Длина, Ширина и Площадь, где Площадь = Длина * Ширина.Изменение либо может вызвать изменение в области.Все три связаны, т. Е. Пользовательский интерфейс ожидает, что все три уведомят об изменениях в их значениях.

При изменении длины или ширины их установщики вызывают NotifyPropertyChanged.

Как мне обращаться с вычисленной площадьюимущество?В настоящее время шаблон, который я могу придумать, обнаруживает в NotifyPropertyChanged, является ли измененное свойство либо Длина, либо Ширина, и, если это так, инициирует дополнительное уведомление PropertyChanged для Area.Это, однако, требует, чтобы я поддерживал внутри NotifyPropertyChanged граф зависимостей, который я считаю антипаттерном.

Итак, мой вопрос: Как мне кодировать свойства зависимости, которые зависят от других свойств зависимости?

edit : Люди здесь предположили, что Length и Width также вызывают NotifyPropertyChanged для Area.Опять же, я думаю, что это анти-шаблон.Свойство (IMHO) не должно знать, кто от него зависит, как и NotifyPropertyChanged.Только имущество должно знать, от кого оно зависит.

Ответы [ 5 ]

3 голосов
/ 13 декабря 2012

Вот статья, описывающая, как создать собственный атрибут, который автоматически вызывает PropertyChanged для свойств, зависящих от другого свойства: http://www.redmountainsw.com/wordpress/2012/01/17/a-nicer-way-to-handle-dependent-values-on-propertychanged/

Код будет выглядеть следующим образом:

[DependsOn("A")]
[DependsOn("B")]
public int Total
{
  get { return A + B; }
}

public int A 
{
  get { return m_A; }
  set { m_A = value; RaisePropertyChanged("A"); }
}

public int B
{
  get { return m_B: }
  set { m_B = value; RaisePropertyChanged("B"); }
}

Я сам не пробовал, но мне нравится идея

3 голосов
/ 26 февраля 2012

Эта проблема не давала мне покоя, поэтому я снова открыл ее.

Во-первых, я хотел бы извиниться за каждого, кто лично воспринимает мой «анти-шаблонный» комментарий. Решения, предложенные здесь, действительно, как это было сделано в WPF. Однако, тем не менее, ИМХО, они вызваны плохой практикой, недостатками в их рамках.

Я утверждаю, что руководство , скрывающее информацию , предписывает, что, когда B зависит от A, A не должен знать о B. Например, когда B происходит от A, A должен нет кода, говорящего: «Если мой тип времени выполнения действительно B, то делай то и это». Аналогично, когда B использует A, у A не должно быть кода, говорящего: «Если объект, вызывающий этот метод, является B, то ...»

Отсюда следует, что если свойство B зависит от свойства A, то A не должен отвечать за непосредственное оповещение B.

И наоборот, поддержание (как я сейчас делаю) графа зависимостей внутри NotifyPropertyChanged также является анти-паттерном. Этот метод должен быть легким и выполнять то, что он называет состояниями, а не поддерживать отношения зависимости между свойствами.

Итак, я думаю, что необходимое решение заключается в аспектно-ориентированном программировании : Peroperty B должен использовать атрибут «I-зависимость-от (свойство A)», а некоторые программы переписывания кода должны создавать граф зависимостей. и измените NotifyPropertyChanged прозрачно.

Сегодня я - один программист, работающий над одним продуктом, поэтому я не могу больше оправдываться этим, но я считаю, что это правильное решение.

0 голосов
/ 13 мая 2016

Вот возможная реализация атрибута:

public class DependentPropertiesAttribute : Attribute
{
    private readonly string[] properties;

    public DependentPropertiesAttribute(params string[] dp)
    {
        properties = dp;
    }

    public string[] Properties
    {
        get
        {
            return properties;
        }
    }
}

Затем в модели базового представления мы обрабатываем механизм вызова зависимостей свойств:

public class ViewModelBase : INotifyPropertyChanged
{
    public ViewModelBase()
    {
        DetectPropertiesDependencies();
    }

    private readonly Dictionary<string, List<string>> _dependencies = new Dictionary<string, List<string>>();

    private void DetectPropertiesDependencies()
    {
        var propertyInfoWithDependencies = GetType().GetProperties().Where(
        prop => Attribute.IsDefined(prop, typeof(DependentPropertiesAttribute))).ToArray();

        foreach (PropertyInfo propertyInfo in propertyInfoWithDependencies)
        {
            var ca = propertyInfo.GetCustomAttributes(false).OfType<DependentPropertiesAttribute>().Single();
            if (ca.Properties != null)
            {
                foreach (string prop in ca.Properties)
                {
                    if (!_dependencies.ContainsKey(prop))
                    {
                        _dependencies.Add(prop, new List<string>());
                    }

                    _dependencies[prop].Add(propertyInfo.Name);
                }
            }
        }
    }

    protected void OnPropertyChanged(params Expression<Func<object>>[] expressions)
    {
        expressions.Select(expr => ReflectionHelper.GetPropertyName(expr)).ToList().ForEach(p => {
            RaisePropertyChanged(p);
            RaiseDependentProperties(p, new List<string>() { p });
        });

    }

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    protected virtual void RaisePropertyChanged(string propertyName)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    protected void RaiseDependentProperties(string propertyName, List<string> calledProperties = null)
    {
        if (!_dependencies.Any() || !_dependencies.ContainsKey(propertyName))
            return;

        if (calledProperties == null)
            calledProperties = new List<string>();

        List<string> dependentProperties = _dependencies[propertyName];

        foreach (var dependentProperty in dependentProperties)
        {
            if (!calledProperties.Contains(dependentProperty))
            {
                RaisePropertyChanged(dependentProperty);
                RaiseDependentProperties(dependentProperty, calledProperties);
            }
        }
    }
}

Наконец, мы определяем зависимости в нашей ViewModel

[DependentProperties("Prop1", "Prop2")]
public bool SomeCalculatedProperty
{
    get
    {
        return Prop1 + Prop2;
    }
}
0 голосов
/ 20 февраля 2012

Затем вы должны повысить в два раза, в параметрах свойства Длина и Ширина.Один для фактического свойства и один для свойства Area.

, например:

private int _width;
public int Width
{
    get { return _width; }
    set
    {
        if (_width == value) return;
        _width = value;
        NotifyPropertyChanged("Width");
        NotifyPropertyChanged("Area");
    }
}

Люди здесь предложили, что Length и Width также вызывают NotifyPropertyChanged для Area.Опять же, я думаю, что это анти-шаблон.Свойство (IMHO) не должно знать, кто от него зависит, как и NotifyPropertyChanged.Только свойство должно знать, от кого оно зависит.

Это не анти-паттерн.На самом деле, ваши данные инкапсулированы внутри этого класса, поэтому этот класс знает, когда и что изменилось.За пределами этого класса вы не должны знать, что Area зависит от ширины и длины.Поэтому наиболее логичным местом для уведомления слушателей о Area является установщик ширины и длины.

Свойство (IMHO) не должно знать, кто от него зависит, как и NotifyPropertyChanged.

Он не нарушает инкапсуляцию, потому что вы находитесь в одном классе, в той же структуре данных.

Дополнительная информация заключается в том, что knockout.js (библиотека javascript mvvm) имеет концепцию, которая обращается к этой проблеме: Computed Observables .Поэтому я считаю, что это абсолютно приемлемо.

0 голосов
/ 20 февраля 2012

Когда свойства Length или Width изменены, вы запускаете PropertyChanged для Area в дополнение к , чтобы запустить его для Length или Width.

Вот очень простая реализация, основанная на вспомогательных полях и методе OnPropertyChanged для запуска события PropertyChanged:

public Double Length {
  get { return this.length; }
  set {
    this.length = value;
    OnPropertyChanged("Length");
    OnPropertyChanged("Area");
  }
}

public Double Width {
  get { return this.width; }
  set {
    this.width = value;
    OnPropertyChanged("Width");
    OnPropertyChanged("Area");
  }
}

public Double Area {
  get { return this.length*this.width; }
}

Подобные действия, безусловно, не являются анти-паттернами. Это именно то, что нужно сделать. Вы, как разработчик класса, знаете, что когда Length изменяется, то Area также изменяется, и вы кодируете его, вызывая соответствующее событие.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...