XAML 将不同的DataTemplate绑定到接口实现

bmp9r5qi  于 2023-06-19  发布在  其他
关注(0)|答案(2)|浏览(99)

这个问题与我在问题中接受的答案有关:Dynamic content in DataGrid or GridView CellTemplate
对于DataGrid的ItemSource,我有一个不同数据类型的集合,根据DataSignalModel集合中当前索引的数据类型,将使用相应的DataType。
这是帮助我解决相关问题的解决方案:
当您存储一个数据类型成员而不是创建两个不同的模型类型时,您必须创建一个自定义的DataTemplateSelector,用于根据DataType选择数据模板。

public class DataSignalModelTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (!(item is SignalDisplayModel dataSignalModel) || !(container is FrameworkElement containerFrameworkElement))
            return null;

        switch (dataSignalModel.DataType)
        {
            case DataType.Bool:
                return FindDataTemplate(containerFrameworkElement, "DataSignalModelBoolTemplate");
            case DataType.Int:
            case DataType.Float:
                return FindDataTemplate(containerFrameworkElement, "DataSignalModelNumericTemplate");
            default:
                throw new ArgumentOutOfRangeException();
        }
    }

    private static DataTemplate FindDataTemplate(FrameworkElement frameworkElement, string key)
    {
        return (DataTemplate)frameworkElement.FindResource(key);
    }
}

我想遵循下面的建议。
关于数据类型的说明:在DataSignalModel中保留成员DataType以确定其类型。通常,您会为每个数据创建专门的类型,而不是一个一对多的模式
因此,当我改变我的设计,以具有接口或基类和不同类型的具体实现时,我如何调整我当前的解决方案以使用不同的DataTemplate?

  • 摆脱自定义DataTemplateSelector,并以某种方式将不同的DataTemplates绑定到相应的IDataSignalModel实现?
  • 调整自定义DataTemplateSelector以返回项目类型的DataTemplate?
  • 或者是另一种方法?
9jyewag0

9jyewag01#

如前所述,您可以为数据创建不同的类型,例如:

public class BoolDataSignalModel: INotifyPropertyChanged
{
   // ...
}
public class IntDataSignalModel: INotifyPropertyChanged
{
   // ...
}

只要创建尽可能多的模型,你需要的。它们不一定必须共享一个公共的基类型,这对于数据模板化来说并不重要。我猜你的数据收集还是这样暴露的:

public IList<DataSignalModel> DataSignalsList
{
   get { return _dataSignalsList; }
   set { _dataSignalsList = value; }
}

如果你在运行时不更改集合,这是可以的,否则使用ObservableCollection<T>。现在,在XAML中为每个模型定义一个数据模板,并将DataType设置为您的模型类型,例如:

<DataTemplate DataType="{x:Type local:BoolDataSignalModel}">
   <StackPanel Orientation="Horizontal" Margin="5">
      <Button Content="Click Me" FontSize="10" Height="18"/>
   </StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:IntDataSignalModel}">
   <StackPanel Orientation="Horizontal">
      <TextBlock Text="0" Margin="10 5 10 5"/>
      <Slider Value="50" Minimum="0" Maximum="100" MinWidth="150"/>
      <TextBlock Text="100" Margin="10 5 10 5"/>
   </StackPanel>
</DataTemplate>

请注意,您不要定义x:Key,否则它将无法工作。如果您将这些数据模板放在将集合绑定到的项控件范围内的资源字典中,则它将根据项类型自动选择适当的数据模板。不需要数据模板选择器或基类。
有关详细信息,请参阅Data Templating Overview

wribegjk

wribegjk2#

下面是我的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的具体实现可以*实现它,如果需要。

相关问题