WPF databinding to interface and not actual object - casting possible?

C#WpfData Binding

C# Problem Overview


Say I have an interface like this:

public interface ISomeInterface
{
...
}

I also have a couple of classes implementing this interface;

public class SomeClass : ISomeInterface
{
...
}

Now I have a WPF ListBox listing items of ISomeInterface, using a custom DataTemplate.

The databinding engine will apparently not (that I have been able to figure out) allow me to bind to interface properties - it sees that the object is a SomeClass object, and data only shows up if SomeClass should happen to have the bound property available as a non-interface property.

How can I tell the DataTemplate to act as if every object is an ISomeInterface, and not a SomeClass etc.?

Thanks!

C# Solutions


Solution 1 - C#

In order to bind to explicit implemented interface members, all you need to do is to use the parentheses. For example:

implicit:

{Binding Path=MyValue}

explicit:

{Binding Path=(mynamespacealias:IMyInterface.MyValue)}

Solution 2 - C#

This answer from Microsoft forums by Beatriz Costa - MSFT is worth reading (rather old):

> The data binding team discussed adding support for interfaces a while ago but ended up not implementing it because we could not come up with a good design for it. The problem was that interfaces don't have a hierarchy like object types do. Consider the scenario where your data source implements both IMyInterface1 and IMyInterface2 and you have DataTemplates for both of those interfaces in the resources: which DataTemplate do you think we should pick up? > > When doing implicit data templating for object types, we first try to find a DataTemplate for the exact type, then for its parent, grandparent and so on. There is very well defined order of types for us to apply. When we talked about adding support for interfaces, we considered using reflection to find out all interfaces and adding them to the end of the list of types. The problem we encountered was defining the order of the interfaces when the type implements multiple interfaces. > > The other thing we had to keep in mind is that reflection is not that cheap, and this would decrease our perf a little for this scenario. > > So what's the solution? You can't do this all in XAML, but you can do it easily with a little bit of code. The ItemTemplateSelector property of ItemsControl can be used to pick which DataTemplate you want to use for each item. In the SelectTemplate method for your template selector, you receive as a parameter the item you will template. Here, you can check for what interface it implements and return the DataTemplate that matches it.

Solution 3 - C#

The short answer is DataTemplate's do not support interfaces (think about multiple inheritance, explicit v. implicit, etc). The way we tend to get around this is to have a base class things extend to allow the DataTemplate specialization/generalization. This means a decent, but not necessarily optimal, solution would be:

public abstract class SomeClassBase
{

}

public class SomeClass : SomeClassBase
{

}

<DataTemplate DataType="{x:Type local:SomeClassBase}">
    <!-- ... -->
</DataTemplate>

Solution 4 - C#

You have another option. Set a Key on your DataTemplate and reference that key in the ItemTemplate. Like this:

<DataTemplate DataType="{x:Type documents:ISpecificOutcome}"
              x:Key="SpecificOutcomesTemplate">
    <Label Content="{Binding Name}"
           ToolTip="{Binding Description}" />
</DataTemplate>

then reference the template by key where you want to use it, like this:

<ListBox ItemsSource="{Binding Path=SpecificOutcomes}"
         ItemTemplate="{StaticResource SpecificOutcomesTemplate}"
         >
</ListBox>

Rendering

Solution 5 - C#

The answer suggested by dummyboy is the best answer (it should be voted to the top imo). It does have an issue that the designer doesn't like it (gives an error "Object null cannot be used as an accessor parameter for a PropertyPath) but there is a good workaround. The workaround is to define the item in a datatemplate and then set the template to a label or other content control. As an example, I was trying to add an Image like this

<Image Width="120" Height="120" HorizontalAlignment="Center" Source="{Binding Path=(starbug:IPhotoItem.PhotoSmall)}" Name="mainImage"></Image>

But it kept giving me the same error. The solution was to create a label and use a data template to show my content

<Label Content="{Binding}" HorizontalAlignment="Center" MouseDoubleClick="Label_MouseDoubleClick">
    <Label.ContentTemplate>
        <DataTemplate>
            <StackPanel>
                <Image Source="{Binding Path=(starbug:IPhotoItem.PhotoSmall)}" Width="120" Height="120" Stretch="Uniform" ></Image>
            </StackPanel>
        </DataTemplate>
    </Label.ContentTemplate>
</Label>

This has its downsides but it seems to work pretty well for me.

Solution 6 - C#

Note: You can use more complex multi-part paths like this too if the interface property is inside a path:

 <TextBlock>
    <TextBlock.Text>
        <Binding Path="Packages[0].(myNamespace:IShippingPackage.ShippingMethod).CarrierServiceCode"/>
    </TextBlock.Text>
 </TextBlock>

Or directly with the Binding directive.

 <TextBlock Text="{Binding Path=Packages[0].(myNamespace:IShippingPackage.ShippingMethod).CarrierServiceCode}"/>

Or when using multiple properties of an interface you can redefine the DataContext locally to make the code more readable.

 <StackPanel DataContext={Binding Path=Packages[0].(myNamespace:IShippingPackage.ShippingMethod)}">
    <TextBlock Text="{Binding CarrierName}"/>
    <TextBlock Text="{Binding CarrierServiceCode}"/>
  </StackPanel>

Tip: Watch out for accidentally ending up with )}at the end of a Path expression. Stupid copy/paste error I keep making.

Path="(myNameSpace:IShippingPackage.ShippingMethod)}"


Make sure to use Path=

Discovered that if I don't explicitly use Path= then it may not be able to parse the binding. Typically I will just write something like this:

Text="{Binding FirstName}"

instead of

Text="{Binding Path=FirstName}"

But with the more complex interface binding I found that Path= was needed to avoid this exception:

System.ArgumentNullException: Key cannot be null.
Parameter name: key
   at System.Collections.Specialized.ListDictionary.get_Item(Object key)
   at System.Collections.Specialized.HybridDictionary.get_Item(Object key)
   at System.ComponentModel.PropertyChangedEventManager.RemoveListener(INotifyPropertyChanged source, String propertyName, IWeakEventListener listener, EventHandler`1 handler)
   at System.ComponentModel.PropertyChangedEventManager.RemoveHandler(INotifyPropertyChanged source, EventHandler`1 handler, String propertyName)
   at MS.Internal.Data.PropertyPathWorker.ReplaceItem(Int32 k, Object newO, Object parent)
   at MS.Internal.Data.PropertyPathWorker.UpdateSourceValueState(Int32 k, ICollectionView collectionView, Object newValue, Boolean isASubPropertyChange)

i.e. don't do this:

<TextBlock Text="{Binding Packages[0].(myNamespace:IShippingPackage.ShippingMethod).CarrierServiceCode}"/>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRune JacobsenView Question on Stackoverflow
Solution 1 - C#dummyboyView Answer on Stackoverflow
Solution 2 - C#Mohammad DehghanView Answer on Stackoverflow
Solution 3 - C#user7116View Answer on Stackoverflow
Solution 4 - C#Pieter BreedView Answer on Stackoverflow
Solution 5 - C#MikeKullsView Answer on Stackoverflow
Solution 6 - C#Simon_WeaverView Answer on Stackoverflow