Привязка к интерфейсу только в том случае, если это необходимый тип (в противном случае используйте запасной вариант)?

#c# #wpf #xaml #mvvm #data-binding

Вопрос:

У меня есть следующее свойство в моей ViewModel

 public IEquipment Equipment
{
    get
    {
        return equipment;
    }
    set
    {
        if (equipment != value)
        {
            equipment = value;
            InvokePropertyChanged("Equipment");
        }
    }
}
 

Сам этот элемент имеет bool свойство, которое Ellipse , на мой взгляд, привязано к an, которое я хочу использовать в качестве элемента индикатора:

 <Ellipse Width="10" Height="10" Fill="{Binding Equipment.IsAvailable, Converter={StaticResource BoolToColorConverter}, FallbackValue=DarkGray}" Margin="1"/>
 

BoolToColorConverter Просто преобразует цвет либо в зеленый ( true ), либо в красный ( false ). Во время выполнения Equipment может быть экземпляр одного из двух типов классов, которые наследуются от IEquipment . Только один из них обладает этим IsAvailable свойством. На практике это работает нормально, я получаю восьмикратный красный или зеленый цвет … или серый, если активен другой тип оборудования.

Проблема в том, что каждый раз, когда обновляется графический интерфейс, выводится следующее предупреждение:

Система.Windows.Предупреждение о данных: 40: ошибка пути привязки выражения: свойство ‘isAvailable’ не найдено в ‘object’

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

  1. Просто добавьте IsAvailable свойство к другому типу и установите для него значение null ( BoolToColorConverter может обрабатывать значения null и возвращает темно-серый): Это может быть нормально для простого bool , но в моем реальном случае есть и другие элементы, которые довольно специфичны для класса.
  2. Выполните привязку данных в коде: это может сработать. Использование события, подобного Loaded при запуске, для установки привязки вручную во время выполнения на основе типа. Однако это может вызвать проблемы при последующей отладке, поскольку все остальные привязки в проекте выполняются непосредственно в файле xaml. Кроме того, Equipment может измениться в течение срока службы ViewModel, поэтому мне пришлось бы как-то отслеживать это.

Комментарии:

1. Вы можете добавить что-то вроде IsEquipmentAvailable свойства прямо в саму модель. Там в getter вы проверяете, имеет ли оборудование свойство isAvailable (предпочтительно с использованием некоторого интерфейса, а не через отражение). Затем вы привязываетесь непосредственно к этому свойству, а не к Equipment.IsAvailable .

2. @Evk Спасибо, это тоже была моя первая идея, однако без установщика в GUI есть обновление nu. Реализация установщика и отслеживание изменений означали бы зависимости, которые я не хочу иметь…

3. Вам не нужен установщик, если вы будете: 1. Вызывать InvokePropertyChanged("IsEquipmentAvailable") из установщика Equipment 2. Подписаться на propertychanged of Equipment в его установщике и сделать то же самое там. Немного кода, но, вероятно, проще, чем перепроектировать с помощью шаблонов данных.

4. @Evk Ха, конечно! Спасибо, что вы об этом не подумали… Единственное, что меня немного беспокоит, это то же самое, что и в первом пункте моего OP: если это не просто простой bool, а списки, объекты и т. Д. У меня внезапно появились несвязанные вещи в моей ViewModel. Так что я думаю, что решение в порядке, но зависит от конкретного случая.

Ответ №1:

Xaml не привязывается к интерфейсам, он привязывается к конкретным типам.

Если ваши типы имеют разные свойства, то для их привязки вам нужен другой xaml.

Используйте DataTemplates, чтобы указать разные xaml для отображения каждого типа.

Комментарии:

1. Это очень сильно зависит от того, что именно вы пытаетесь сделать. Например, вы могли бы обернуть свои объекты IEquipment в EquipmentViewModel, который всегда имеет свойство isAvailable и обрабатывает различия в его получателе. Ваша цитата «есть и другие элементы, которые довольно специфичны для класса». цитата заставила меня подумать, что были более фундаментальные различия с тем, что вы отображали.

2. Да, как упоминалось выше в комментариях с Evk, мы обсуждали то же самое. Однако в моем случае мне придется использовать шаблоны, поскольку у меня есть несколько разных свойств.

Ответ №2:

Если свойства ваших производных от IEquipment (здесь Equipment и OtherEquipment в качестве примеров) сильно отличаются и не имеют общего интерфейса, они, скорее всего, отличаются по своему внешнему виду. В этом случае вам понадобятся разные DataTemplate s для каждого типа. Это пример для a ContentControl , но он работает так же ItemsContol , как и с неявными шаблонами данных (нет x:Key , но a DataType ), которые применяются автоматически.

 <ContentControl Content="{Binding Equipment}">
   <ContentControl.Resources>
      <DataTemplate DataType="{x:Type local:Equipment}">
         <Ellipse Width="10" Height="10" Fill="{Binding IsAvailable, Converter={StaticResource BoolToColorConverter}, FallbackValue=DarkGray}" Margin="1"/>
      </DataTemplate>
      <DataTemplate DataType="{x:Type local:OtherEquipment}">
         <Ellipse Width="10" Height="10" Fill="DarkGray" Margin="1"/>
      </DataTemplate>
   </ContentControl.Resources>
</ContentControl>
 

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

 public class EquipmentAvailabilityToColorConverter : IValueConverter
{
   public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
   {
      if (value is Equipment equipment)
         return equipment.IsAvailable ? Brushes.Green : Brushes.Red;

      return (Brush)parameter;
   }

   public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
   {
      throw new InvalidOperationException();
   }
}
 
 <Ellipse Width="10" Height="10" Fill="{Binding Equipment, Converter={StaticResource EquipmentAvailabilityToColorConverter}, ConverterParameter={x:Static Brushes.DarkGray}}" Margin="1"/>