我正在编写一个复合松散耦合的 MVVM WPF 应用程序,父 VM 中的子 VM 是接口而不是类实例,例如
public IChildViewModel { get; set; }
现在如何使用 DataTemplate 呈现此属性?喜欢:
<DataTemplate DataType="{x:Type contracts:IChildViewModel}">
据我了解,由于接口的性质(多重继承等),WPF 不允许这种直接绑定。但是,由于接口应该广泛应用于松散耦合的应用程序中,是否有任何解决方法可以将 DataTemplate 绑定到接口?谢谢。
您可以通过明确告诉 wpf 您正在绑定到接口字段来绑定到接口:
(请注意,ViewModelBase 只是一个实现 INotifyPropertyChanged 接口的基类)
public class Implementation : ViewModelBase, IInterface
{
private string textField;
public string TextField
{
get
{
return textField;
}
set
{
if (value == textField) return;
textField = value;
OnPropertyChanged();
}
}
}
public interface IInterface
{
string TextField { get; set; }
}
然后在 ViewModel 上:
private IInterface interfaceContent;
public IInterface InterfaceContent
{
get { return interfaceContent; }
}
最后是使之成为可能的 Xaml:
<ContentControl Grid.Row="1" Grid.Column="0" Content="{Binding InterfaceContent}">
<ContentControl.ContentTemplate>
<DataTemplate DataType="{x:Type viewModels:IInterface}">
<TextBox Text="{Binding Path=(viewModels:IInterface.TextField)}"/>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
如您所见,绑定明确引用“IInterface”定义。
似乎在这种情况下使用
DataTemplateSelector
是正确的方法。
这是我的
InterfaceDataTemplateSelector
,它仅适用于接口:
namespace MyWpf;
using Sys = System;
using Wpf = System.Windows;
using WpfControls = System.Windows.Controls;
//PEARL: DataTemplate in WPF does not work with interfaces!
// The declaration <DataTemplate DataType="{x:Type SomeInterface}"> silently fails.
// We solve this problem by introducing a DataTemplateSelector
// that takes interfaces into consideration.
//Original inspiration from https://stackoverflow.com/q/41714918/773113
public class InterfaceDataTemplateSelector : WpfControls.DataTemplateSelector
{
delegate object? ResourceFinder( object key );
public override Wpf.DataTemplate? SelectTemplate( object item, Wpf.DependencyObject container )
{
ResourceFinder resourceFinder = getResourceFinder( container );
return tryGetDataTemplateRecursively( item.GetType(), resourceFinder );
}
static ResourceFinder getResourceFinder( Wpf.DependencyObject container ) //
=> (container is Wpf.FrameworkElement containerAsFrameworkElement) //
? containerAsFrameworkElement.TryFindResource //
: Wpf.Application.Current.TryFindResource;
static Wpf.DataTemplate? tryGetDataTemplateRecursively( Sys.Type type, ResourceFinder resourceFinder )
{
return tryGetDataTemplateFromType( type, resourceFinder ) //
?? tryGetDataTemplateFromInterfacesRecursively( type, resourceFinder ) //
?? tryGetDataTemplateFromSuperTypeRecursively( type, resourceFinder );
}
static Wpf.DataTemplate? tryGetDataTemplateFromType( Sys.Type type, ResourceFinder tryFindResource )
{
Wpf.DataTemplateKey resourceKey = new Wpf.DataTemplateKey( type );
object? resource = tryFindResource( resourceKey );
if( resource is Wpf.DataTemplate dataTemplate )
{
if( !dataTemplate.IsSealed )
dataTemplate.DataType = type;
return dataTemplate;
}
return null;
}
static Wpf.DataTemplate? tryGetDataTemplateFromInterfacesRecursively( Sys.Type type, ResourceFinder resourceFinder )
{
foreach( var interfaceType in type.GetInterfaces() )
{
Wpf.DataTemplate? dataTemplate = tryGetDataTemplateRecursively( interfaceType, resourceFinder );
if( dataTemplate != null )
return dataTemplate;
}
return null;
}
static Wpf.DataTemplate? tryGetDataTemplateFromSuperTypeRecursively( Sys.Type type, ResourceFinder resourceFinder )
{
return type.BaseType == null ? null : tryGetDataTemplateRecursively( type.BaseType, resourceFinder );
}
}
使用方法:
在您的
Resources
部分中,像往常一样定义每个 DataTemplate
,现在每个 DataType
是一个接口而不是具体类型:
<DataTemplate DataType="{x:Type viewModels:MyViewModelInterface}">
<local:MyView />
</DataTemplate>
然后,为
InheritanceDataTemplateSelector
添加一项资源:
<UserControl x:Class=...
xmlns:myWpf="clr-namespace:MyWpf;assembly=MyWpf"
...
<FrameworkElement.Resources>
<myWpf:InterfaceDataTemplateSelector x:Key="InterfaceDataTemplateSelector" />
然后,在需要使用
DataTemplate
的正确位置,指定应使用该选择器。例如,在 ItemsControl
:
<ItemsControl ItemsSource="{Binding SomeViewModelCollection}"
ItemTemplateSelector="{StaticResource InterfaceDataTemplateSelector}">
注意:ViewModel 接口不必扩展
INotifyPropertyChanged
。 ViewModel 的具体实现 可以实现它,如果需要的话。
另请注意:与其他答案所建议的相反,在绑定到接口视图模型的成员时不需要使用任何特殊符号。 (至少在任何最新版本的 WPF 中都没有。)