WPF MVVM -如何创建一个包含动态选择的控件类型的列的分层数据网格

pw9qyyiw  于 2023-03-24  发布在  其他
关注(0)|答案(2)|浏览(247)

我有以下ViewModel:

// Top-level Policy ViewModel
class MainWindowVM {
    // A collection of ViewModels, each mapping to an application role.
    public List<RolePolicyVM> ComponentPolicyVMs { get; set; } = new();
}

// Contains the component policies pretaining to a specific application roles.
class RolePolicyVM {
    // Role name
    public String Name { get; set; }
    
    // Component policies applying to the ADCS role.
    public List<ComponentPolicyVM> ComponentPolicies { get; set; } = new();
}

// Contains the component policies pretaining to a specific agent plugin or worker
class ComponentPolicyVM {
    // Plugin or worker Id. Not rendered.
    public Guid Id { get; set; }
    
    // The description of the setting to be displayed in the AgentConfig tool
    public String Description { get; set; }
    
    // The tooltip to be displayed when mousing over the Description TextBlock
    public String ToolTipText { get; set; }
    
    // The value to bind to a control
    public Object Value { get; set; }
    
    // The data type of "Value". 
    // This property determines what type of control to render.
    public Type ValueType { get; set; }
}

我需要创建一个DataGrid或TreeView控件,它是基于AppPolicyVM.ComponentPolicyVMs中的元素动态填充的。

  • 顶层层次结构绑定到RolePolicyVM.Name
  • 第二级必须包含2列:
  • 第一列绑定到ComponentPolicyVM.Description
  • 第二列绑定到ComponentPolicyVM.Value,但这里是它变得棘手的地方:我需要这里呈现的控件是基于ComponentPolicyVM.ValueType的值动态的。例如,如果它是typeof(string),我们会看到一个TextBox。如果它是typeof(bool),它是一个复选框。我需要支持boolstringint

我已经渲染了一个TreeView,它总是渲染一个复选框:

我所坚持的(甚至不确定这是否可能)是如何添加逻辑,以动态呈现基于ComponentPolicyVM.ValueType的适当类型的控件。
以下是我目前为止的XAML:

<Window x:Class="AppPolicyConfig.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:vm="clr-namespace:AppPolicyConfig.ViewModels"
        xmlns:local="clr-namespace:AppPolicyConfig"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <vm:MainWindowVM/>
    </Window.DataContext>
    <TreeView x:Name="MainTreeView"
              HorizontalAlignment="Stretch"
              VerticalAlignment="Stretch"
              Margin="10"
              ItemsSource="{Binding ComponentPolicyVMs}">
        <TreeView.ItemContainerStyle>
            <Style TargetType="{x:Type TreeViewItem}">
                <Setter Property="IsExpanded" Value="True" />
            </Style>
        </TreeView.ItemContainerStyle>
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding ComponentPolicies}" DataType="{x:Type vm:RolePolicyVM}">
                <TextBlock Text="{Binding Name}"/>
                <HierarchicalDataTemplate.ItemTemplate>
                    <DataTemplate DataType="{x:Type vm:ComponentPolicyVM}">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="300" />
                                <ColumnDefinition Width="*" />
                            </Grid.ColumnDefinitions>
                            <TextBlock Grid.Column="0" 
                                       Text="{Binding Description}"/>
                            <CheckBox Grid.Column="1"
                                      IsChecked="{Binding Value}"/>
                        </Grid>
                    </DataTemplate>
                </HierarchicalDataTemplate.ItemTemplate>
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>
</Window>
yacmzcpb

yacmzcpb1#

我所坚持的(并且不确定这是否可能)是如何添加逻辑,该逻辑可以基于ComponentPolicyVM.ValueType动态呈现适当的控件类型。
尝试将TreeViewItemTemplateSelector属性设置为自定义DataTemplateSelector,该自定义DataTemplateSelector基于ComponentPolicyVMValueType属性的值返回DataTemplate,例如:

public class CustomTemplateSelector : DataTemplateSelector
{
    public DataTemplate TemplateA { get; set; }
    public DataTemplate TemplateB { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item is ComponentPolicyVM componentPolicyVM)
        {
            if (componentPolicyVM.ValueType == typeof(TypeA))
                return TemplateA;
            else if (componentPolicyVM.ValueType == typeof(TypeB))
                return TemplateB;
        }

        return base.SelectTemplate(item, container);
    }
}
mdfafbf1

mdfafbf12#

您可以使用DataTemplate来根据Type进行选择,与ContentTemplateSelector配对,因此切换ComponentPolicyVM的模板:
DataTemplate示例:

<DataTemplate DataType="{x:Type vm:ComponentPolicyVM}">
  <ContentControl Content="{Binding ValueType}" ContentTemplateSelector="{StaticResource ComponentValueTypeTemplateSelector}" />
</DataTemplate>

组件选择器示例:

public class ComponentValueTypeTemplateSelector : DataTemplateSelector
    {
        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            FrameworkElement element = container as FrameworkElement;
            if (!(item is ComponentPolicyVM cpVM))
                return null;

            if (cpVM.ValueType == typeOf(string))
                return element.FindResource("ValueTypeStringTemplate") as DataTemplate;
            else if(cpVM.ValueType == typeOf(bool))
                return element.FindResource("ValueTypeBoolTemplate") as DataTemplate;
            else if(cpVM.ValueType == typeOf(int))
                return element.FindResource("ValueTypeIntTemplate") as DataTemplate;
            else
                return null;
        }
    }

然后在一些资源文件中,你需要为你需要的东西定义数据模板(ValueTypeIntTemplateValueTypeStringTemplateValueTypeBoolTemplate)你的模板可以在任何地方定义,只需要确保container可以看到它们(即导入资源),这样选择器就可以找到它们。

相关问题