Binding to interface typed properties

2019-06-27 20:41发布

问题:

I just got bit by yet another binding oddity in WPF. Consider the following class and its IStupid typed property called MyStupid:

public struct DumbClass
{
    public IStupid MyStupid { get { return new IsStupid(); } }
}
public interface IStupid{}
public class IsStupid : IStupid{}

Now consider the following binding to a ListBox:

var items = new List<DumbClass>(new []{new DumbClass(), new DumbClass(), new DumbClass()});
OptListBox.ItemsSource = items;

There is nothing special about the xaml:

<ListBox Name="OptOccurances" Height="238" HorizontalAlignment="Left" Margin="130,34,0,0" VerticalAlignment="Top" Width="229" >
</ListBox>

As expected, the output of the listbox is 3 rows of "MyProject.DumbClass".

However if I set DisplayMemberPath="MyStupid" (or create an ItemTemplate, binding 'MyStupid' directly to a TextBlock in the template), I get 3 empty rows instead, when I expected it to say MyProject.IsStupid. Why is the databinding engine unable to call the default ToString() implementation and display the class name. Is there a workaround for a interface typed property? At the very least, is there a reason why no binding error is thrown?

回答1:

I can reproduce this issue. It looks like WPF bug.

Here is the workaround you can use: Instead of DisplayMemberPath, you can use Item's DataTemplate with StringFormat parameter, which will forcefully convert property value to string:

    <ListBox x:Name="OptOccurances" Height="238" HorizontalAlignment="Left" Margin="130,34,0,0" VerticalAlignment="Top" Width="229" >
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Path=MyStupid, StringFormat='{}{0}' }"/>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

Generally you can use WPF Trace Settings to figure out such kind of problems:

But in this case as I see no binding errors happen.

Additionally you can use WPF Visualizer for Visual Studio 2012, which allows you to investigate tree right from the debug Watch:

Using the following code you can get TextBlock with its binding:

    private void btn_Click_1(object sender, RoutedEventArgs e)
    {
        var listBoxItem = OptOccurances.ItemContainerGenerator.ContainerFromIndex(0) as ListBoxItem;
        var item = OptOccurances.Items[1] as DumbClass;
        var tbk =  VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(listBoxItem, 0),0),0) as TextBlock;
        var binding = BindingOperations.GetBinding(tbk, TextBlock.TextProperty);
        var be = BindingOperations.GetBindingExpression(tbk, TextBlock.TextProperty);
        var vs = DependencyPropertyHelper.GetValueSource(tbk, TextBlock.TextProperty);
        var val = tbk.GetValue(TextBlock.TextProperty);
    }

And it shows that Binding status is actually active and the object mapped is correct. Obviously internals of Binding (PropertyPathWorker) works differently for getting the value in case type of property



标签: .net wpf wpf-4.0