Home > Back-end >  Binding to interface only if it is the needed type (use fallback otherwise)?
Binding to interface only if it is the needed type (use fallback otherwise)?

Time:10-22

I have the following property in my ViewModel

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

This item itself has a bool property, which is bound to an Ellipse in my View, which I want to use as a indicator item:

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

The BoolToColorConverter simply converts the color to either green (true) or red (false). During runtime Equipment can be an instance of one of two class types which inherit from IEquipment. Only one of them has the IsAvailable property. In practice this works fine, I get eighter my red or green color...or a gray one, in case the other type of equipment is active.

Problem is, that each time the GUI updates, the following warning gets output:

System.Windows.Data Warning: 40 : BindingExpression path error: 'IsAvailable' property not found on 'object'

How can I avoid this issue? Basically I want to bind to this property only if it is of the correct type. I can think of two solutions, which I'm not particularly fond of:

  1. Simply add the IsAvailable property to the other type and set it to null (BoolToColorConverter can handle null values and returns dark grey): This might be ok for a simple bool, but in my actual case there are other items, which are quite class specific.
  2. Do the databinding in the code-behind: This might work. Using an event like Loaded on startup to set the binding manually at runtime based on the type. However, this might be troublesome for debugging later, because all other Bindings in the project happen directly in the xaml file. Additionally, Equipment might change during the lifetime of the ViewModel, so I would have to somehow track it.

CodePudding user response:

Xaml doesn't bind to interfaces, it binds to concrete types.

If your types have different properties, then you need different xaml to bind them.

Use DataTemplates to specify different xaml for displaying each type.

CodePudding user response:

If the properties on your derivatives of IEquipment (here Equipment and OtherEquipment as examples) differ a lot and do not share a common interface, they most likely differ in their appearance. In this case you would need different DataTemplates for each type. This is an example for a ContentControl, but it works the same fot ItemsContols with implicit data templates (no x:Key, but a DataType) that are applied automatically.

<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>

A workaround for your specific issue could be writing a custom, specialized value converter.

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"/>
  • Related