WPF binding behaviour different when bound property is declared as interface vs class type?
This started with weird behaviour that I thought was tied to my implementation of ToString()
, and I asked this question: Why won't WPF databindings show text when ToString() has a collaborating object?
It turns out to have nothing to do with collaborators and is reproducible.
When I bind Label.Content
to a property of the DataContext
that is declared as an interface type, ToString()
is called on the runtime object and the label displays the result.
When I bind TextBlock.Text
to the same property, ToString()
is never called and nothing is displayed. But, if I change the declared property to a concrete implementation of the interface, it works as expected.
Is this somehow by design? If so, any idea why?
To reproduce:
- Create a new WPF Application (.NET 3.5 SP1)
- Add the following classes:
public interface IFoo { string foo_part1 { get; set; } string foo_part2 { get; set; } } public class Foo : IFoo { public string foo_part1 { get; set; } public string foo_part2 { get; set; } public override string ToString() { return foo_part1 + " - " + foo_part2; } }
public class Bar
{
public IFoo foo
{
get { return new Foo {foo_part1 = "first开发者_JAVA技巧", foo_part2 = "second"}; }
}
}
Set the XAML of Window1 to:
<Window x:Class="WpfApplication1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300"> <StackPanel> <Label Content="{Binding foo, Mode=Default}"/> <TextBlock Text="{Binding foo, Mode=Default}"/> </StackPanel> </Window>
in Window1.xaml.cs:
public partial class Window1 : Window { public Window1() { InitializeComponent(); DataContext = new Bar(); } }
When you run this application, you'll see the text only once (at the top, in the label). If you change the type of foo
property on Bar
class to Foo
(instead of IFoo
) and run the application again, you'll see the text in both controls.
I know this thread is old but I found a workaround for this problem. Use the StringFormat property on the binding:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
<Label Content="{Binding foo, Mode=Default}"/>
<TextBlock Text="{Binding foo, Mode=Default, StringFormat={}{0}}"/>
</StackPanel>
</Window>
Yes you are right. Obviously the ContentControl.Content
property is implemented differently from the TextBlock.Text
property. One obvious difference of course is that the ContentControl
will actually generate a TextBlock
instance for a content object that is not a Visual
and doesn't have a DataTemplate
. The TextBlock
does not. It will render the text by itself. In both cases the string is determined by
- the
IValueConverter
(if present in the binding) - the
TypeConverter
(if declared) object.ToString()
It seems that this algorithm differs only in step 3 between TextBlock
and ContentControl
as you have shown. While the ContentControl
actually resolves the object behind the interface, the TextBlock
does not. Interesting.
I guess this is something that you have to live with. You have several options now:
- Expose a string property on your interface and bind to that
- Expose the data object as a concrete class instead as an interface
- Use a
ContentControl
instead of aTextBlock
- provide a
IValueConverter
and use it in the binding - provide a
TypeConverter
for your interface - do something else (there's probably more)
精彩评论