Есть ли * чистый * способ заставить свойство зависимости только для чтения отражать значение другого свойства? - PullRequest
5 голосов
/ 19 августа 2009

Код ниже - мое текущее решение.

Если вам понадобится пара минут, чтобы выяснить, что делает этот код, Я слышу вас .

Это ужасный беспорядок, если когда-либо был. Я бы убил, чтобы увидеть альтернативу, (но не позволяйте этому отговорить вас от ответа ... :-). О боже, я даже сократил его (по существу), удалив код преобразователя, и я ОЧЕНЬ чувствую дислексию, когда смотрю на этот код.

Прекрасным примером того, что я пытаюсь имитировать, было бы свойство FrameworkElement.ActualWidth. Вы знаете, как вычисляется и переназначается свойство ActualWidth при каждом изменении свойства Width или при перерисовке элемента управления. или когда еще? ------

С точки зрения разработчика, это похоже на трудную работу по привязке данных.
Но ActualWidth - свойство зависимости только для чтения. Действительно ли Microsoft должна пройти через эту гигантскую дыру в коде, чтобы заставить это работать? Или есть более простой способ, который использует существующую функциональность системы привязки данных?

public class foo : FrameworkElement
{
    [ValueConversion(typeof(string), typeof(int))]
    public class fooConverter : IValueConverter
    {   public object Convert(  object value, Type targetType,
                                object parameter, CultureInfo culture)
        { ... }
        public object ConvertBack(  object value, Type targetType,
                                    object parameter, CultureInfo culture)
        { ... }
    }

    private static readonly fooConverter fooConv = new fooConverter();



    private static readonly DependencyPropertyKey ReadOnlyIntPropertyKey =
        DependencyProperty.RegisterReadOnly( "ReadOnlyInt", typeof(int),
                                             typeof(foo), null);
    public int ReadOnlyInt
    {   get { return (int)GetValue(ReadOnlyIntPropertyKey.DependencyProperty); }
    }



    public static readonly DependencyProperty ReadWriteStrProperty =
        DependencyProperty.Register( "ReadWriteStr", typeof(string), typeof(foo),
                                     new PropertyMetadata(ReadWriteStr_Changed));
    public string ReadWriteStr
    {   get { return (string)GetValue(ReadWriteStrProperty); }
        set { SetValue(ReadWriteStrProperty, value); }
    }



    private static void ReadWriteStr_Changed(   DependencyObject d,
                                            DependencyPropertyChangedEventArgs e)
    {   try
        {   if (d is foo)
            {   foo f = d as foo;
                f.SetValue( ReadOnlyIntPropertyKey,
                            fooConv.Convert(f.ReadWriteStr, typeof(int), null,
                                            CultureInfo.CurrentCulture));
            }
        }
        catch { }
    }
}

Ответы [ 3 ]

4 голосов
/ 19 августа 2009

К сожалению, вам понадобится большая часть того, что у вас есть. В этом случае IValueConverter не требуется, поэтому вы можете упростить его до:

public class foo : FrameworkElement
{
    private static readonly DependencyPropertyKey ReadOnlyIntPropertyKey =
        DependencyProperty.RegisterReadOnly( "ReadOnlyInt", typeof(int),
                                         typeof(foo), null);
    public int ReadOnlyInt
    {
       get { return (int)GetValue(ReadOnlyIntPropertyKey.DependencyProperty); }
    }

    public static readonly DependencyProperty ReadWriteStrProperty =
        DependencyProperty.Register( "ReadWriteStr", typeof(string), typeof(foo),
                                 new PropertyMetadata(ReadWriteStr_Changed));

    public string ReadWriteStr
    {
       get { return (string)GetValue(ReadWriteStrProperty); }
        set { SetValue(ReadWriteStrProperty, value); }
    }

    private static void ReadWriteStr_Changed(DependencyObject d,
                                        DependencyPropertyChangedEventArgs e)
    {
         foo f = d as foo;
         if (f != null)
         {
              int iVal;
              if (int.TryParse(f.ReadWriteStr, out iVal))
                  f.SetValue( ReadOnlyIntPropertyKey, iVal);
         }
    }
}
1 голос
/ 08 сентября 2017

Да, есть простой способ «сделать доступным только для чтения DependencyProperty для отображения значения другого свойства», но это может потребовать довольно фундаментального изменения в общей модели программирования свойств вашей приложение. Короче говоря, вместо использования от DependencyPropertyKey до push значений в свойство, каждый доступный только для чтения DependencyProperty может иметь обратный вызов CoerceValue , который создает его собственное значение извлечение всех исходных значений, от которых оно зависит.

В этом подходе параметр 'value', который передается в CoerceValue, игнорируется. Вместо этого каждая функция DP * CoerceValue пересчитывает свое значение «с нуля», напрямую выбирая все необходимые значения из экземпляра DependencyObject, переданного в CoerceValue (вы можете использовать для этого dobj.GetValue(...), если хотите избежать приведения к тип экземпляра владельца).

Старайтесь подавлять любые подозрения, что игнорирование значения, предоставленного для CoerceValue, может привести к потере чего-либо. Если вы придерживаетесь этой модели, эти значения никогда не будут полезны, и общая работа будет такой же или меньше, чем «принудительная» модель, потому что исходные значения, которые не изменились, как всегда, кэшируются системой DP. Все, что изменилось, это то, кто отвечает за расчет и где он сделан. Здесь приятно то, что вычисление каждого значения DP всегда централизовано в одном месте и специально связано с этим DP, а не разбросано по приложению.

Вы можете выбросить DependencyPropertyKey в этой модели, потому что она вам никогда не понадобится. Вместо этого, чтобы обновить значение любого DP, доступного только для чтения, вы просто вызываете CoerceValue или InvalidateValue в экземпляре владельца, указывая желаемый DP. Это работает, потому что эти две функции не требуют использования клавиши DP , они вместо этого используют открытый идентификатор DependencyProperty и являются открытыми функциями, поэтому любой код может вызывать их из в любом месте.

Что касается того, когда и где делать эти CoerceValue / InvalidateValue звонки, есть два варианта:

  • Стремление: Вызовите InvalidateValue вызов для (целевого) DP в PropertyChangedCallback каждого (исходного) DP, который упоминается в (целевых) функциях DP CoerceValueCallback,
    - или -
  • Ленивый: Всегда вызывайте CoerceValue на DP непосредственно перед извлечением его значения.

Это правда, что этот метод не так удобен для XAML, но это не было требованием вопроса OP. Однако, учитывая, что при таком подходе вам даже не нужно извлекать или сохранять DependencyPropertyKey вообще, кажется, что это может быть одним из самых изящных способов пойти, если вы сможете переосмыслить свое приложение вокруг семантика "pull".


В совершенно ином ключе есть еще одно решение, которое может быть даже проще:

Откройте INotifyPropertyChanged на вашем DependencyObject и используйте свойства CLR для свойств только для чтения, которые теперь будут иметь простое поле поддержки. Да, система привязки WPF будет правильно обнаруживать и контролировать оба механизма - DependencyProperty и INotifyPropertyChanged - в одном экземпляре класса. Для установки изменений этого свойства, доступного только для чтения, рекомендуется использовать установщик, частный или другой, и этот установщик должен проверить поле поддержки, чтобы обнаружить пустые (избыточные) изменения, в противном случае возникает событие CLR PropertyChanged старого стиля.

Для привязки к этому свойству, доступному только для чтения, либо используйте перегрузку OnPropertyChanged владельца (для самосвязывания), чтобы передать изменения из DP, либо, для привязки из произвольных внешних свойств, используйте System.ComponentModel.DependencyPropertyDescriptor.FromProperty, чтобы получить DependencyPropertyDescriptor для соответствующих DP-источников и используйте метод AddValueChanged для установки обработчика, который выдвигает новые значения.

Конечно, для свойств, не относящихся к DP, или экземпляров, отличных от DependencyObject, вы можете просто подписаться на их событие INotifyPropertyChanged, чтобы отслеживать изменения, которые могут повлиять на ваше свойство только для чтения. В любом случае, независимо от того, каким образом вы передаете изменения в свойство только для чтения, событие, вызванное его установщиком, гарантирует, что изменения свойства только для чтения правильно распространяются далее на любые дополнительные зависимые свойства, будь то WPF / DP, CLR, данные или иначе.

1 голос
/ 19 августа 2009

Это не так плохо, как вы предлагаете, ИМХО ...

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

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