在 WPF C# 中将 GridView 与字典绑定

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19993932/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 10:03:34  来源:igfitidea点击:

Bind GridView with Dictionary in WPF C#

c#wpfvisual-studiogridviewdictionary

提问by shivank

I am trying to bind a dictionary into GridView in WPF(C#) programmatically. The structure of the dictionary is -
Dictionary(string, Dictionary(string, int))
I am able to bind the key of the main dictionary to the GridView

我正在尝试以编程方式将字典绑定到 WPF(C#) 中的 GridView。字典的结构是 -
Dictionary(string, Dictionary(string, int))
我能够将主字典的键绑定到 GridView

Dictionary<string, Dictionary<string, int>> result
GridView myGrid = new GridView();  
GridViewColumn gc = new GridViewColumn();  
gc.Header = "File Name";  
gc.DisplayMemberBinding = new Binding("Key");  
myGrid.Columns.Add(gc);  

The source of the gridview is set to result

gridview的source设置为result

I want to create a column with the header set to Key of the inner Dictionary and bind it to the Value of the inner Dictionary

我想创建一个标题设置为内部字典键的列并将其绑定到内部字典的值

somethig like gc.DisplayMemberBinding = new Binding("Value.Value"); the dictionary is like this

像 gc.DisplayMemberBinding = new Binding("Value.Value"); 字典是这样的

{
'A', {(A1,1),(A2,2),(A3,3)}
'B', {(A1,4),(A2,5),(A3,6)}
'C', {(A1,7),(A2,8),(A3,9)}
}

So the gridview would be like

所以 gridview 会像

--------------------------------------
filename | A1 | A2 | A3
--------------------------------------
A | 1 | 2 | 3
B | 4 | 5 | 6
C | 7 | 8 | 9

--------------------------------------
文件名 | A1 | A2 | A3
--------------------------------------
A | 1 | 2 | 3
乙 | 4 | 5 | 6
C | 7 | 8 | 9

回答by

Okay, displaying nested dictionaries in a GridView... This might take some time and a fairly huge amount of text, so you might brew yourself some fresh hot coffee. Ready? Okay, let's get started.

好的,在 GridView 中显示嵌套字典...这可能需要一些时间和相当多的文本,因此您可能会为自己冲泡一些新鲜的热咖啡。准备好?好的,让我们开始吧。

A GridViewis basically just a view mode for a ListView control. A ListView control visualizes a collection (or list) of objects/values (whatever those objects might be). How this will relate to the dictionaries to be displayed will be explained in just a minute.

的GridView基本上只是对一个ListView控制的视图模式。ListView 控件可视化对象/值(无论这些对象可能是什么)的集合(或列表)。这将如何与要显示的字典相关,将在一分钟内解释。

As already mentioned, the data to be displayed is stored in a nested dictionary. The concrete dictionary date type is specified as follow:

如前所述,要显示的数据存储在嵌套字典中。具体的字典日期类型指定如下:

Dictionary< string, Dictionary<string, int > >

The key of the (outer) dictionary represents a file name and should be shown in the "FileName" column of the GridView. The (inner) dictionary associated with each file name will contain the values for the second and further columns.

(外部)字典的键代表一个文件名,应该显示在 GridView 的“FileName”列中。与每个文件名关联的(内部)字典将包含第二列和更多列的值。

The dictionary as specified above is an actual implementation of the interface

上面指定的字典是接口的实际实现

ICollection< KeyValuePair< string, Dictionary<string, int > > >

This is actually exactly what is needed for the ListView control - a collection of elements. Each element in this collection is of this type:

这实际上正是 ListView 控件所需要的——元素的集合。此集合中的每个元素都属于以下类型:

KeyValuePair< string, Dictionary<string, int > >

The Keyin the KeyValuePair is the file name. The KeyValuePair's Valueis the (inner) dictionary with the key/value pairs for the second and further columns. That means, each of these KeyValuePair elements contain the data for exactly one complete row.

KeyValuePair 中的Key是文件名。KeyValuePair 的是(内部)字典,其中包含第二列和更多列的键/值对。这意味着,这些 KeyValuePair 元素中的每一个都包含恰好一个完整行的数据。

Now, the harder part begins. For each column, the appropriate data from the KeyValuePair needs to be accessed. Unfortunately, it does not work with a simple binding for each column.

现在,更难的部分开始了。对于每一列,需要访问 KeyValuePair 中的相应数据。不幸的是,它不适用于每列的简单绑定。

However, the KeyValuePair itself can be bound to each column, and with the help of a tailor-made IValueConverterthe desired information can be extracted from the KeyValuePair.

然而,KeyValuePair 本身可以绑定到每一列,并且在量身定制的IValueConverter的帮助下,可以从 KeyValuePair 中提取所需的信息。

And here we will have to make a decision. We can either go the comparatively easy way of having a static number of non-modifiable columns. Or, we invest a little bit more coding effort if the intention is to make it easy for dynamic setting, adding or removing of columns. In the following, an overview about both approaches will be given.

在这里,我们将不得不做出决定。我们可以采用相对简单的方法来获得静态数量的不可修改的列。或者,如果目的是使动态设置、添加或删除列变得容易,我们会投入更多的编码工作。下面将对这两种方法进行概述。


1. The easy, but static and inflexible way.


1. 简单但静态且不灵活的方式。

To access the Keyof the KeyValuePair (the file name), a value converter is not necessary. A simple binding to the Keyproperty is sufficient.

要访问KeyValuePair的Key(文件名),不需要值转换器。对Key属性的简单绑定就足够了。

To access the values stored within the (inner) dictionary of the KeyValuePair, a custom IValueConverterwill be used. The value converter will need to know the key of the value to be extracted. This is being implemented as a public property DictionaryKey, which allows specifying the key in XAML.

要访问存储在 KeyValuePair 的(内部)字典中的值,将使用自定义IValueConverter。值转换器需要知道要提取的值的键。这是作为公共属性DictionaryKey 实现的,它允许在 XAML 中指定密钥。

    [ValueConversion(typeof(KeyValuePair<string, Dictionary<string, int>>), typeof(string))]
    public class GetInnerDictionaryValueConverter : IValueConverter
    {
        public string DictionaryKey { get; set; }

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (!(value is KeyValuePair<string, Dictionary<string, int>>))
                throw new NotSupportedException();

            Dictionary<string, int> innerDict = ((KeyValuePair<string, Dictionary<string, int>>) value).Value;
            int dictValue;
            return (innerDict.TryGetValue(DictionaryKey, out dictValue)) ? (object) dictValue : string.Empty;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }


The XAML for the ListView control might then look similar to the following.


ListView 控件的 XAML 可能类似于以下内容。

Let's say, the columns to display are "FileName", "A1", "A2", "A3" (the latter three being keys in the inner dictionary). Also note the three custom GetInnerDictionaryValueConverterinstances, which are created as static resources and are being used by the respective bindings.

假设要显示的列是“FileName”、“A1”、“A2”、“A3”(后三个是内部字典中的键)。另请注意三个自定义GetInnerDictionaryValueConverter实例,它们被创建为静态资源并由相应的绑定使用。

<ListView x:Name="MyListGridView">
    <ListView.Resources>
        <My:GetInnerDictionaryValueConverter x:Key="ConverterColum_A1" DictionaryKey="A1" />
        <My:GetInnerDictionaryValueConverter x:Key="ConverterColum_A2" DictionaryKey="A2" />
        <My:GetInnerDictionaryValueConverter x:Key="ConverterColum_A3" DictionaryKey="A3" />
    </ListView.Resources>
    <ListView.View>
        <GridView AllowsColumnReorder="true">
            <GridViewColumn Header="FileName" DisplayMemberBinding="{Binding Key}" />
            <GridViewColumn Header="A1" DisplayMemberBinding="{Binding Converter={StaticResource ConverterColum_A1}}" />
            <GridViewColumn Header="A2" DisplayMemberBinding="{Binding Converter={StaticResource ConverterColum_A2}}" />
            <GridViewColumn Header="A3" DisplayMemberBinding="{Binding Converter={StaticResource ConverterColum_A3}}" />
        </GridView>
    </ListView.View>
</ListView>

All that is left to do is to assign the actual dictionary with the data to the ItemsSourceproperty of the ListView control. This can be done through data binding in XAML or in code-behind.

剩下要做的就是将包含数据的实际字典分配给ListView 控件的ItemsSource属性。这可以通过 XAML 或代码隐藏中的数据绑定来完成。

As already mentioned, this approach is fairly straightforward and easy to implement. The disadvantage is that the number of columns is fixed, since the value converters necessary for the column data bindings are declared as static resources. If dynamically setting, adding or removing of columns with arbitrary DictionaryKeys is desired, we need to do away with those static converters. Which leads us to the second approach...

如前所述,这种方法相当简单且易于实施。缺点是列数是固定的,因为列数据绑定所需的值转换器被声明为静态资源。如果需要动态设置、添加或删除具有任意 DictionaryKeys 的列,我们需要取消那些静态转换器。这导致我们采用第二种方法......


2. A little bit more complicated, but allowing dynamic easy setting/adding/removing columns


2.稍微复杂一点,但允许动态轻松设置/添加/删除列

To allow dynamic setting, adding, removing of columns with arbitrary DictionaryKeys, the custom GetInnerDictionaryValueConverterintroduced before might be used, but the code-behind might become somewhat convoluted. A better approach is to define custom GridViewColumn types, which can also implement any required IValueConverter logic and take care of setting up the bindings. Separate custom value converter type(s) as before are not necessary anymore, which will simplify the handling of those columns.

为了允许动态设置、添加、删除具有任意 DictionaryKeys 的列,可能会使用之前引入的自定义GetInnerDictionaryValueConverter,但代码隐藏可能会变得有些复杂。更好的方法是定义自定义 GridViewColumn 类型,它也可以实现任何所需的 IValueConverter 逻辑并负责设置绑定。不再需要像以前一样单独的自定义值转换器类型,这将简化这些列的处理。

Concerning our example, only two custom columns are needed. The first custom column is for the file names, and it looks pretty simple:

关于我们的示例,只需要两个自定义列。第一个自定义列用于文件名,看起来很简单:

public class GridViewColumnFileName : GridViewColumn
{
    public GridViewColumnFileName()
    {
        DisplayMemberBinding = new Binding("Key")
        {
            Mode = BindingMode.OneWay
        };
    }
}

All it does is setting the binding in code-behind. You might point out that we also could keep the simple GridViewColumn with the "{Binding Key}" binding as in the example before -- and you would be absolutely right. I only show this implementation here to illustrate possible approaches.

它所做的只是在代码隐藏中设置绑定。您可能会指出,我们也可以像前面的示例一样使用“{Binding Key}”绑定来保留简单的 GridViewColumn —— 您绝对正确。我在这里只展示这个实现来说明可能的方法。

The custom column type for columns representing values from the inner dictionaries is a bit more complex. As the custom value converter before, this custom column needs to know the key of the value being displayed. By implementing the IValueConverterinterface as part of this custom column type, this column type can use itself as value converter for the binding.

表示来自内部字典的值的列的自定义列类型有点复杂。作为之前的自定义值转换器,这个自定义列需要知道正在显示的值的键。通过实现IValueConverter接口作为此自定义列类型的一部分,此列类型可以将自身用作绑定的值转换器。

[ValueConversion(typeof(KeyValuePair<string, Dictionary<string, int>>), typeof(string))]
public class GridViewColumnInnerDictionaryValue : GridViewColumn, IValueConverter
{
    public string InnerDictionaryKey
    {
        get { return _key; }
        set
        {
            if (_key == value) return;

            _key = value;

            if (string.IsNullOrWhiteSpace(value))
            {
                DisplayMemberBinding = null;
            }
            else if (DisplayMemberBinding == null)
            {
                DisplayMemberBinding = new Binding()
                {
                    Mode = BindingMode.OneWay,
                    Converter = this
                };
            }
        }
    }

    private string _key = null;

    #region IValueConverter

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value is KeyValuePair<string, Dictionary<string, int>>)
        {
            Dictionary<string, int> innerDict = ((KeyValuePair<string, Dictionary<string, int>>) value).Value;
            int dictValue;
            return (innerDict.TryGetValue(InnerDictionaryKey, out dictValue)) ? (object) dictValue : string.Empty;
        }

        throw new NotSupportedException();
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion IValueConverter
}

The implementation of the IValueConverter interface is exactly as done before. Of course, the GetInnerDictionaryValueConvertertype from before could be used instead of implementing its logic as part of GridViewColumnInnerDictionaryValue, but again, i wanted to demonstrate different possibilities.

IValueConverter 接口的实现与之前完全一样。当然,可以使用之前的GetInnerDictionaryValueConverter类型,而不是将其逻辑实现为GridViewColumnInnerDictionaryValue 的一部分,但同样,我想演示不同的可能性。

Creating the ListView control in XAML using the custom column types would look like this:

使用自定义列类型在 XAML 中创建 ListView 控件如下所示:

<ListView x:Name="MyListGridView">
    <ListView.View>
        <GridView AllowsColumnReorder="true">
            <My:GridViewColumnFileName Header="FileName" />
            <My:GridViewColumnInnerDictionaryValue Header="A1" InnerDictionaryKey="A1" />
            <My:GridViewColumnInnerDictionaryValue Header="A2" InnerDictionaryKey="A2" />
            <My:GridViewColumnInnerDictionaryValue Header="A3" InnerDictionaryKey="A3" />
        </GridView>
    </ListView.View>
</ListView>

Instead of declaring the columns in XAML, adding and removing of columns to/from the GridView.Columnsproperty could also be done easily in code-behind. Again, don't forget to set the ListView's ItemsSourceproperty to the dictionary, either through data binding in XAML or in code-behind.

除了在 XAML 中声明列之外,在GridView.Columns属性中添加和删​​除列也可以在代码隐藏中轻松完成。同样,不要忘记通过 XAML 中的数据绑定或代码隐藏将 ListView 的ItemsSource属性设置为字典。