WPF数据绑定接口,而不是实际的对象投射可能?
假设我有这样的界面:
public interface ISomeInterface { ... }
我也有几个类实现这个接口;
public class SomeClass : ISomeInterface { ... }
现在我有一个WPF ListBox列出ISomeInterface的项目,使用自定义的DataTemplate。
数据绑定引擎显然不会(我已经能够找出)允许我绑定到接口属性 – 它看到的对象是一个SomeClass对象,数据只显示如果SomeClass应该碰巧有约束属性可用一个非界面属性。
我怎么能告诉DataTemplate行事,如果每个对象是一个ISomeInterface,而不是一个SomeClass等?
谢谢!
为了绑定到显式实现的接口成员,所有你需要做的就是使用括号。 例如:
隐:
{Binding Path=MyValue}
明确:
{Binding Path=(mynamespacealias:IMyInterface.MyValue)}
来自Microsoft Beatriz Costa – Microsoft的论坛的回答值得一读(相当老):
数据绑定团队刚刚讨论了增加对接口的支持,但最终没有实现它,因为我们不能为它提供一个好的devise。 问题是接口没有像对象types那样的层次结构。 考虑一下你的数据源实现了
IMyInterface1
和IMyInterface2
的场景,并且你在这些资源中的这两个IMyInterface1
都有DataTemplates:你认为我们应该select哪个DataTemplate?在为对象types进行隐式数据模板时,我们首先尝试为确切typesfind一个
DataTemplate
,然后为其父类,祖父类等等。 有非常明确的types的顺序供我们申请。 当我们谈到添加对接口的支持时,我们考虑使用reflection来找出所有接口并将它们添加到types列表的末尾。 我们遇到的问题是在types实现多个接口时定义接口的顺序。我们必须记住的另一件事是反思并不是那么便宜,这会降低我们对这种情况的performance。
那么解决scheme是什么? 你不能在XAML中这样做,但是你可以用一些代码轻松地完成。
ItemsControl
的ItemTemplateSelector
属性可以用来select每个项目使用哪个DataTemplate
。 在您的模板select器的SelectTemplate
方法中,您将收到作为参数的您将模板的项目。 在这里,你可以检查它实现的接口,并返回与之匹配的DataTemplate
。
简短的答案是DataTemplate不支持接口(考虑多重inheritance,显式隐式等)。 我们倾向于解决这个问题的方式是让基类扩展到允许DataTemplate专业化/泛化。 这意味着一个体面的但不一定是最佳的解决scheme将是:
public abstract class SomeClassBase { } public class SomeClass : SomeClassBase { } <DataTemplate DataType="{x:Type local:SomeClassBase}"> <!-- ... --> </DataTemplate>
dummyboybuild议的答案是最好的答案(它应该被投票到最高层)。 它有一个问题,devise者不喜欢它(给出一个错误“对象null不能用作一个PropertyPath的访问器参数),但有一个很好的解决方法。解决方法是定义一个数据模板中的项目,然后将模板设置为标签或其他内容控件。例如,我试图添加一个像这样的图像
<Image Width="120" Height="120" HorizontalAlignment="Center" Source="{Binding Path=(starbug:IPhotoItem.PhotoSmall)}" Name="mainImage"></Image>
但它一直给我同样的错误。 解决scheme是创build一个标签,并使用数据模板来显示我的内容
<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>
这有它的缺点,但它似乎对我工作得很好。
你有另一种select。 在DataTemplate上设置一个键,并在ItemTemplate中引用该键。 喜欢这个:
<DataTemplate DataType="{x:Type documents:ISpecificOutcome}" x:Key="SpecificOutcomesTemplate"> <Label Content="{Binding Name}" ToolTip="{Binding Description}" /> </DataTemplate>
然后通过键在您想要使用它的地方引用模板,如下所示:
<ListBox ItemsSource="{Binding Path=SpecificOutcomes}" ItemTemplate="{StaticResource SpecificOutcomesTemplate}" > </ListBox>
注意:如果接口属性位于path中,也可以使用更复杂的多部分path:
<TextBlock> <TextBlock.Text> <Binding Path="Packages[0].(myNamespace:IShippingPackage.ShippingMethod).CarrierServiceCode"/> </TextBlock.Text> </TextBlock>
或者直接使用Binding
指令。
<TextBlock Text="{Binding Path=Packages[0].(myNamespace:IShippingPackage.ShippingMethod).CarrierServiceCode}"/>
或者当使用一个接口的多个属性时,您可以在本地重新定义DataContext以使代码更具可读性。
<StackPanel DataContext={Binding Path=Packages[0].(myNamespace:IShippingPackage.ShippingMethod)}"> <TextBlock Text="{Binding CarrierName}"/> <TextBlock Text="{Binding CarrierServiceCode}"/> </StackPanel>
提示:小心意外结束)}
在pathexpression式的末尾。 愚蠢的复制/粘贴错误,我一直在做。
Path="(myNameSpace:IShippingPackage.ShippingMethod)}"
确保使用Path=
发现如果我不显式使用Path=
那么它可能无法parsing绑定。 通常我会写这样的东西:
Text="{Binding FirstName}"
代替
Text="{Binding Path=FirstName}"
但是对于更复杂的接口绑定,我发现需要Path=
来避免这个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)
即不要这样做:
<TextBlock Text="{Binding Packages[0].(myNamespace:IShippingPackage.ShippingMethod).CarrierServiceCode}"/>