c# - 如何通过WMVVM将WPF数据网格绑定(bind)到包含组合框和测试框组合的列

标签 c# wpf mvvm datagridviewcolumn

在我的datagrid中,我有一个textbox列和另一个应包含组合框和文本框组合的列,这些组合应动态设置。例如,我让用户设置计算机的状态。因此,State和Value是每一列的标题,其中Value可以根据State的类型包含comboBox或TextBox。其类型可以是 bool 值或枚举。如果是枚举,则显示组合框,否则显示textBox。

我正在尝试通过 View 模型执行此操作,但不确定如何在xaml中设置DataGridview。或者在这种情况下有可能...?

<DataGrid Name="dataGridView" ItemsSource="{Binding Path=StateParametersList}" CanUserAddRows="False" 
                      IsReadOnly="True" >

                <DataGrid.Columns>
                    <DataGridTextColumn Binding="{Binding State}"/>
                    <DataGridTemplateColumn>
                        <DataGridTemplateColumn.CellEditingTemplate>
                            <DataTemplate>
                                <ComboBox ItemsSource="{Binding ValueCell}" SelectedItem="{Binding Value}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellEditingTemplate>
                    </DataGridTemplateColumn>
                </DataGrid.Columns>

            </DataGrid>

viewModel:
private ObservableCollection<StateParameters> StateParametersList =
        new ObservableCollection<StateParameters>();

    public ObservableCollection<StateParameters> StateParametersList
    {
        get { return StateParametersList; }
        set
        {
            StateParametersList = value;
            NotifyPropertyChanged(nameof(StateParametersList));
        }
    }
[Serializable]
public class StateParameters
{
    public string State { get; set; }
    public object Value { get; set; }
}

List<string> ValueCell = new List<string>();

其中ValueCell是comboBox中将在运行时填充的项目列表。

因此,我本可以通过xaml.cs文件完成此操作,并根据其枚举与否创建了组合框,但我想通过View Model实现。并且,每个comboBox将具有不同的值,这些值会在运行时动态填充。我在这里苦苦挣扎,因此,如果有人能指出正确的方向,我将不胜感激。

最佳答案

1.组织状态参数数据模型

当查看期望的用户交互时,关于如何将它们呈现给用户/由用户编辑,存在不同类别的状态参数。在问题的范围内,我们可以确定以下类别:

  • 可切换参数(bool)
  • 一个选择参数,其中参数的值是给定集合中的一个(实际上是像枚举或任何其他数据类型)
  • 最好使用一个文本参数(string)

  • 2.实现状态参数数据模型

    状态参数具有状态名称/标识符和值。该值可以是不同的类型。这本质上是问题中StateParameters类的定义。

    但是,正如稍后在我的答案中将变得更加明显的那样,具有不同的类型/类来代表状态参数的不同类别(如上所列)将有利于连接UI中的表示和交互逻辑。

    当然,无论其类别如何,每个状态参数都应由相同的基本类型表示。显而易见的选择是使状态参数基类型成为抽象类或接口(interface)。在这里,我选择了一个接口(interface):
    public interface IStateParameter
    {
        string State { get; }
        object Value { get; set; }
    }
    

    我现在不再根据上面列出的类别直接创建具体的状态参数类,而是创建一个附加的抽象基类。此类将是通用的,从而使以类型安全的方式处理状态参数更加容易:
    public abstract class StateParameter<T> : IStateParameter, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        public string State { get; set; }
    
        public T Value
        {
            get { return _v; }
            set
            {
                if ((_v as IEquatable<T>)?.Equals(value) == true || ReferenceEquals(_v, value) || _v?.Equals(value) == true)
                    return;
    
                _v = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
            }
        }
    
        private T _v;
    
        object IStateParameter.Value
        {
            get { return this.Value; }
            set { this.Value = (T) value; }
        }
    }
    

    (虽然State属性具有一个setter,但该属性只能设置一次,因此,不需要为此设置属性更改通知。从技术上讲,您可以随时更改该属性;我只是选择在此处使用setter来保存代码在我的回答中比较简短。)

    请注意 INotifyPropertyChanged 接口(interface)的实现,这是必需的,因为UI将通过绑定(bind)来操作Value属性。还要注意IStateParameter接口(interface)属性Valueexplicit interface implementation,它将“隐藏”它,除非您将状态参数对象引用显式转换为IStateParameter。这是有意的,因为StateParameter<T>提供了自己的Value属性,该属性的类型与StateParameter 的泛型类型参数匹配。同样,不幸的是,Value setter 中的相等比较有些笨拙,因为此处的泛型类型参数T完全不受约束,可以是某些值类型或某些引用类型。因此,平等比较必须涵盖所有可能的情况。

    因此,完成这些准备工作后,就该将我们的重点转向实际问题了。现在,我们将根据答案开头概述的类别来实现具体的状态参数类型:
        public class BoolStateParameter : StateParameter<bool>
        { }
    
        public class TextStateParameter : StateParameter<string>
        { }
    
        public class ChoiceStateParameter : StateParameter<object>
        {
            public Array Choices { get; set; }
        }
    

    ChoiceStateParameter类声明一个附加属性,该属性用于保存具有特定状态参数可能值的数组。 (就像上面的StateParameter .State一样,此属性只能设置一次,而我在这里给它提供一个setter的原因是使答案中的代码相对简短。)

    除了ChoiceStateParameter类,其他任何类中都没有任何声明。您问,如果我们可以直接使用StateParameter /StateParameter ,为什么我们需要BoolStateParameter/TextStateParameter?这是个好问题。如果我们不必处理XAML,则可以轻松地直接使用StateParameter /StateParameter (假设_StateParameter 不是抽象类)。但是,尝试从XAML标记中引用泛型类型是非常痛苦的,完全是不可能的。因此,已经定义了非通用的具体状态参数类BoolStateParameter,TextStateParameter和ChoiceStateParameter。

    哦,在忘记之前,由于我们已经将公共(public)状态参数基本类型声明为名为IStateParameter的接口(interface),因此必须相应地调整viewmodel中StateParametersList属性的类型参数(当然也要调整其后备字段):
    public ObservableCollection<IStateParameter> StateParametersList { get ..... set ..... }
    

    完成此操作后,我们已经完成了C#代码端的部分,然后转到DataGrid。

    3. UI/XAML

    由于不同的状态参数类别需要不同的交互元素(CheckBoxes,TextBoxes,ComboBoxes),因此我们将尝试利用DataTemplates定义应如何在DataGrid单元格内表示每个状态参数类别。

    现在,我们为什么要努力定义这些类别并为每个类别声明不同的状态参数类型也将变得显而易见。因为DataTemplates可以与特定类型相关联。现在,我们将为每种BoolStateParameterTextStateParameterChoiceStateParameter类型定义那些DataTemplates。

    DataTemplates将放置在DataGrid中,作为DataGrid资源字典的一部分:
    <DataGrid Name="dataGridView" ItemsSource="{Binding Path=StateParametersList}" ... >
    
        <DataGrid.Resources>
            <DataTemplate DataType="{x:Type local:BoolStateParameter}">
                <CheckBox IsChecked="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
            </DataTemplate>
    
            <DataTemplate DataType="{x:Type local:TextStateParameter}">
                <TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
            </DataTemplate>
    
            <DataTemplate DataType="{x:Type local:ChoiceStateParameter}">
                <ComboBox ItemsSource="{Binding Choices}" SelectedItem="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
            </DataTemplate>
        </DataGrid.Resources>
    

    (注意:您可能需要调整我在这里使用的local:命名空间,或与映射到声明状态参数类的C#命名空间的XML命名空间进行交换。)

    下一步是根据给定列单元格中要处理的状态参数的实际类型,使DataGridTemplateColumn选择适当的DataTemplate。但是,DataGridTemplateColumn无法从资源必需的本身中选择一个DataTemplate,DataGrid控件也不能代表DataGridTemplateColumn进行操作。所以现在怎么办?

    幸运的是,WPF中有UI元素使用资源字典中的DataTemplate呈现一些值/对象,并根据值/对象的类型来选择DataTemplate。 UI元素之一就是 ContentPresenter ,我们将在DataGridTemplateColumn中使用它:
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding State}"/>
    
            <DataGridTemplateColumn Width="*">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <ContentPresenter Content="{Binding}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    
    </DataGrid>
    

    就是这样。随着基础数据模型(状态参数类)的小扩展,XAML问题就消失了(或者我希望如此)。

    4.演示数据集

    一个快速的测试数据集来演示实际的代码(以随机选择的枚举类型为例):
    StateParametersList = new ObservableCollection<IStateParameter>
    {
        new BoolStateParameter
        {
            State = "Bool1",
            Value = false
        },
        new ChoiceStateParameter
        {
            State = "Enum FileShare",
            Value = System.IO.FileShare.ReadWrite,
            Choices = Enum.GetValues(typeof(System.IO.FileShare))
        },
        new TextStateParameter
        {
            State = "Text1",
            Value = "Hello"
        },
        new BoolStateParameter
        {
            State = "Bool2",
            Value = true
        },
        new ChoiceStateParameter
        {
            State = "Enum ConsoleKey",
            Value = System.ConsoleKey.Backspace,
            Choices = Enum.GetValues(typeof(System.ConsoleKey))
        },
        new TextStateParameter
        {
            State = "Text2",
            Value = "World"
        }
    };
    

    它看起来像这样:

    enter image description here

    关于c# - 如何通过WMVVM将WPF数据网格绑定(bind)到包含组合框和测试框组合的列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56719155/

    相关文章:

    c# - WP - 删除功能,但仅适用于 "new buyers"

    c# - 了解随机二维方向矢量生成

    c# - 如何将图像添加到RichTextBlock的运行中?

    c# - WPF 将 comboBox 绑定(bind)到 List<string>

    wpf - 如何在 WPF 中拉伸(stretch)位图而不平滑像素

    c# - Caliburn micro ViewModel 没有从另一个 ViewModel 接收消息

    c# - 将 IsMouseOver 绑定(bind)到 UserControl 中的 ViewModel

    java - 哪一个有利于更好的性能?

    c# - 不要按转义字符串拆分 - C#

    c# - OpenXml:工作表子元素的顺序更改导致文件损坏