我正在编写一个松散耦合的复合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
似乎是可行的。
您可以将接口转换为等效的抽象类。它是这样工作的。
我在uwp中的数据模板中使用了绑定接口类型。我没有在绑定路径上显式指定接口类型。当接口没有显式实现时,它就起作用了。当接口显式实现时,它以静默方式失败。我认为,如果接口是显式实现的,那么需要在Binding路径中显式引用接口类型,这样Binding才能正确查找属性路径。
这是我的InheritanceDataTemplateSelector
,它只适用于接口:
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
:再添加一个资源
<myWpf:InterfaceDataTemplateSelector x:Key="InterfaceDataTemplateSelector" />
然后,在需要使用DataTemplate
的正确位置,指定应该使用此选择器。例如,在ItemsControl
:中
<ItemsControl ItemsSource="{Binding SomeViewModelCollection}"
ItemTemplateSelector="{StaticResource InterfaceDataTemplateSelector}">
注意:ViewModel接口不必扩展INotifyPropertyChanged
。ViewModel的具体实现可以实现它,如果需要的话
另请注意:与其他答案相反,在绑定到接口视图模型的成员时,不需要使用任何特殊的表示法。(至少在任何最新版本的WPF中都没有。)