C# WPF 数据绑定到接口而不是实际对象 - 可以转换吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/327984/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 23:22:01  来源:igfitidea点击:

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

c#wpfdata-binding

提问by Rune Jacobsen

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.

现在我有一个 WPF ListBox 列出 ISomeInterface 的项目,使用自定义 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.

数据绑定引擎显然不会(我已经能够弄清楚)允许我绑定到接口属性 - 它看到该对象是一个 SomeClass 对象,并且只有当 SomeClass 碰巧具有可用的绑定属性时才会显示数据非接口属性。

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

如何告诉 DataTemplate 就好像每个对象都是 ISomeInterface 而不是 SomeClass 等一样?

Thanks!

谢谢!

采纳答案by dummyboy

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)}

回答by user7116

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:

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

public abstract class SomeClassBase
{

}

public class SomeClass : SomeClassBase
{

}

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

回答by Pieter Breed

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

你还有另一个选择。在您的 DataTemplate 上设置一个键并在 ItemTemplate 中引用该键。像这样:

<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

渲染

回答by MikeKulls

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

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

<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.

这有其缺点,但似乎对我来说效果很好。

回答by Mohammad Dehghan

This answerfrom Microsoft forums by Beatriz Costa - MSFTis worth reading (rather old):

Beatriz Costa - MSFT来自微软论坛的这个答案值得一读(相当旧):

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 IMyInterface1and IMyInterface2and 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 DataTemplatefor 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 ItemTemplateSelectorproperty of ItemsControlcan be used to pick which DataTemplateyou want to use for each item. In the SelectTemplatemethod 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 DataTemplatethat matches it.

数据绑定团队不久前讨论过添加对接口的支持,但最终没有实现它,因为我们无法为它想出一个好的设计。问题是接口没有对象类型那样的层次结构。考虑这样一种情况:您的数据源同时实现了IMyInterface1IMyInterface2并且您在资源中为这两个接口提供了 DataTemplate:您认为我们应该选择哪个 DataTemplate?

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

我们必须记住的另一件事是反射并不是那么便宜,这会降低我们在这种情况下的性能。

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

回答by Simon_Weaver

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

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

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

Or directly with the Bindingdirective.

或者直接使用Binding指令。

 <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.

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

 <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 表达式结尾。我一直在犯愚蠢的复制/粘贴错误。

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

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



Make sure to use Path=

确保使用 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:

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

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:

但是对于更复杂的接口绑定,我发现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)

i.e. don't do this:

即不要这样做:

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