WPF数据绑定到接口而不是实际对象 - 可能吗?

Run*_*sen 50 c# data-binding wpf

说我有这样的界面:

public interface ISomeInterface
{
...
}
Run Code Online (Sandbox Code Playgroud)

我还有几个实现这个接口的类;

public class SomeClass : ISomeInterface
{
...
}
Run Code Online (Sandbox Code Playgroud)

现在我有一个WPF ListBox列出了ISomeInterface的项目,使用自定义DataTemplate.

数据绑定引擎显然不会(我已经弄明白)允许我绑定到接口属性 - 它看到该对象是一个SomeClass对象,并且数据只显示SomeClass应该碰巧有绑定属性可用非接口属性.

我怎么能告诉DataTemplate好像每个对象都是一个ISomeInterface,而不是SomeClass等?

谢谢!

小智 62

为了绑定到显式实现的接口成员,您需要做的就是使用括号.例如:

隐:

{Binding Path=MyValue}
Run Code Online (Sandbox Code Playgroud)

明确:

{Binding Path=(mynamespacealias:IMyInterface.MyValue)}
Run Code Online (Sandbox Code Playgroud)

  • @Steve这个答案是正确的 - OP正在讨论数据模板中的绑定路径,具体类型的显式接口属性默认情况下对绑定引擎是不可见的(隐式的).我认为接受的答案并不是OP所追求的,特别是因为这个答案是在整整一年之后发布的. (2认同)

Moh*_*han 19

Beatriz Costa-MSFT的微软论坛的答案值得一读(相当古老):

数据绑定团队讨论了不久前添加对接口的支持,但最终没有实现它,因为我们无法为它提供一个好的设计.问题是接口没有像对象类型那样的层次结构.考虑您的数据源同时实现方案IMyInterface1,并IMyInterface2与您在资源这两个接口的DataTemplates:它的DataTemplate你认为我们应该拿起?

在对对象类型进行隐式数据模板化时,我们首先尝试查找DataTemplate确切类型,然后查找其父类,祖父类等.我们可以应用非常明确的类型顺序.当我们谈到添加对接口的支持时,我们考虑使用反射来找出所有接口并将它们添加到类型列表的末尾.我们遇到的问题是当类型实现多个接口时定义接口的顺序.

我们必须记住的另一件事是反射并不便宜,这会减少我们对这种情况的影响.

那么解决方案是什么?您无法在XAML中完成所有操作,但您可以使用一些代码轻松完成.该ItemTemplateSelector属性ItemsControl可用于选择DataTemplate要用于每个项目的项目.在SelectTemplate模板选择器的方法中,您将接收要模板化的项目作为参数.在这里,您可以检查它实现的接口,并返回DataTemplate与之匹配的接口.


use*_*116 13

简短的回答是DataTemplate不支持接口(考虑多重继承,显式v.隐式等).我们倾向于解决这个问题的方法是扩展基类以允许DataTemplate专门化/泛化.这意味着一个体面但不一定是最佳的解决方案是:

public abstract class SomeClassBase
{

}

public class SomeClass : SomeClassBase
{

}

<DataTemplate DataType="{x:Type local:SomeClassBase}">
    <!-- ... -->
</DataTemplate>
Run Code Online (Sandbox Code Playgroud)

  • 谢谢 - 我会接受你的回答,即使这意味着我几乎要做很多工作来做我想做的事情.当你的任务是将WPF UI置于现有业务对象库之上时,有时候生活很糟糕.:) (3认同)

Pie*_*eed 9

你有另一种选择.在DataTemplate上设置一个Key并在ItemTemplate中引用该键.像这样:

<DataTemplate DataType="{x:Type documents:ISpecificOutcome}"
              x:Key="SpecificOutcomesTemplate">
    <Label Content="{Binding Name}"
           ToolTip="{Binding Description}" />
</DataTemplate>
Run Code Online (Sandbox Code Playgroud)

然后按键使用它来引用模板,如下所示:

<ListBox ItemsSource="{Binding Path=SpecificOutcomes}"
         ItemTemplate="{StaticResource SpecificOutcomesTemplate}"
         >
</ListBox>
Run Code Online (Sandbox Code Playgroud)

渲染

  • 不幸的是,这意味着您必须明确告诉每个绑定使用哪个模板.这很烦人,因为在我的情况下,我想绑定到相同的接口属性,但在运行时更改底层对象.我不想明确地告诉绑定只实现一个数据模板,因为我希望它在运行时根据支持该属性的底层对象来更改该数据模板. (2认同)

Mik*_*lls 8

dummyboy建议的答案是最好的答案(它应该被投票到顶级imo).它确实存在设计者不喜欢它的问题(给出错误"Object null不能用作PropertyPath的访问器参数)但是有一个很好的解决方法.解决方法是在datatemplate中定义项目然后将模板设置为标签或其他内容控件.例如,我试图添加这样的图像

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

但它一直给我同样的错误.解决方案是创建标签并使用数据模板来显示我的内容

<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>
Run Code Online (Sandbox Code Playgroud)

这有其缺点,但它似乎对我很好.


Sim*_*ver 5

注意:如果接口属性位于路径内,您也可以使用更复杂的多部分路径:

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

或者直接使用Binding指令。

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

或者,当使用接口的多个属性时,您可以在本地重新定义 DataContext 以使代码更具可读性。

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

)}提示:请注意路径表达式末尾是否意外出现。我不断犯愚蠢的复制/粘贴错误。

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


确保使用Path=

发现如果我不明确使用Path=那么它可能无法解析绑定。通常我会写这样的东西:

Text="{Binding FirstName}"
Run Code Online (Sandbox Code Playgroud)

代替

Text="{Binding Path=FirstName}"
Run Code Online (Sandbox Code Playgroud)

但随着更复杂的接口绑定,我发现Path=需要避免此异常:

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)
Run Code Online (Sandbox Code Playgroud)

即不要这样做:

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