简体   繁体   English

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

[英]Bind GridView with Dictionary in WPF C#

I am trying to bind a dictionary into GridView in WPF(C#) programmatically.我正在尝试以编程方式将字典绑定到 WPF(C#) 中的 GridView。 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我能够将主字典的键绑定到 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");像 gc.DisplayMemberBinding = new Binding("Value.Value"); the dictionary is like this字典是这样的

{
'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 | A1 | A2 | A2 | A3 A3
-------------------------------------- -------------------------------
A |一个 | 1 | 1 | 2 | 2 | 3 3
B |乙 | 4 | 4 | 5 | 5 | 6 6
C | C | 7 | 7 | 8 | 8 | 9 9

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.好的,在 GridView 中显示嵌套字典...这可能需要一些时间和相当多的文本,因此您可能会为自己冲泡一些新鲜的热咖啡。 Ready?准备好? Okay, let's get started.好的,让我们开始吧。

A GridView is basically just a view mode for a ListView control. GridView基本上只是 ListView 控件的视图模式。 A ListView control visualizes a collection (or list) of objects/values (whatever those objects might be). ListView 控件可视化对象/值(无论这些对象可能是什么)的集合(或列表)。 How this will relate to the dictionaries to be displayed will be explained in just a minute.这将如何与要显示的词典相关,将在一分钟内解释。

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. (外部)字典的键代表一个文件名,应该显示在 GridView 的“FileName”列中。 The (inner) dictionary associated with each file name will contain the values for the second and further columns.与每个文件名关联的(内部)字典将包含第二列和更多列的值。

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.这实际上正是 ListView 控件所需要的——元素的集合。 Each element in this collection is of this type:此集合中的每个元素都属于以下类型:

KeyValuePair< string, Dictionary<string, int > >

The Key in the KeyValuePair is the file name. KeyValuePair 中的Key是文件名。 The KeyValuePair's Value is the (inner) dictionary with the key/value pairs for the second and further columns. KeyValuePair 的是(内部)字典,其中包含第二列和更多列的键/值对。 That means, each of these KeyValuePair elements contain the data for exactly one complete row.这意味着,这些 KeyValuePair 元素中的每一个都包含恰好一个完整行的数据。

Now, the harder part begins.现在,更难的部分开始了。 For each column, the appropriate data from the KeyValuePair needs to be accessed.对于每一列,都需要访问 KeyValuePair 中的相应数据。 Unfortunately, it does not work with a simple binding for each column.不幸的是,它不适用于每列的简单绑定。

However, the KeyValuePair itself can be bound to each column, and with the help of a tailor-made IValueConverter the 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 Key of the KeyValuePair (the file name), a value converter is not necessary.要访问 KeyValuePair 的Key (文件名),不需要值转换器。 A simple binding to the Key property is sufficient.Key属性的简单绑定就足够了。

To access the values stored within the (inner) dictionary of the KeyValuePair, a custom IValueConverter will be used.要访问存储在 KeyValuePair 的(内部)字典中的值,将使用自定义IValueConverter 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.这是作为公共属性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).假设要显示的列是“FileName”、“A1”、“A2”、“A3”(后三个是内部字典中的键)。 Also note the three custom GetInnerDictionaryValueConverter instances, which are created as static resources and are being used by the respective bindings.另请注意三个自定义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 ItemsSource property of the ListView control.剩下要做的就是将包含数据的实际字典分配给 ListView 控件的ItemsSource属性。 This can be done through data binding in XAML or in code-behind.这可以通过 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.如果需要动态设置、添加或删除具有任意 DictionaryKeys 的列,我们需要取消那些静态转换器。 Which leads us to the second approach...这导致我们采用第二种方法......


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 GetInnerDictionaryValueConverter introduced before might be used, but the code-behind might become somewhat convoluted.为了允许动态设置、添加、删除具有任意 DictionaryKeys 的列,可能会使用之前引入的自定义GetInnerDictionaryValueConverter ,但代码隐藏可能会变得有些复杂。 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.更好的方法是定义自定义 GridViewColumn 类型,它也可以实现任何所需的 IValueConverter 逻辑并负责设置绑定。 Separate custom value converter type(s) as before are not necessary anymore, which will simplify the handling of those columns.不再需要像以前一样单独的自定义值转换器类型,这将简化这些列的处理。

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.您可能会指出,我们还可以像前面的示例一样使用“{Binding Key}”绑定来保留简单的 GridViewColumn —— 这绝对是正确的。 I only show this implementation here to illustrate possible approaches.我在这里只展示这个实现来说明可能的方法。

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 IValueConverter interface 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. IValueConverter 接口的实现与之前完全一样。 Of course, the GetInnerDictionaryValueConverter type from before could be used instead of implementing its logic as part of GridViewColumnInnerDictionaryValue , but again, i wanted to demonstrate different possibilities.当然,可以使用之前的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.Columns property could also be done easily in code-behind.除了在 XAML 中声明列之外,在GridView.Columns属性中添加和删除列也可以在代码隐藏中轻松完成。 Again, don't forget to set the ListView's ItemsSource property to the dictionary, either through data binding in XAML or in code-behind.同样,不要忘记通过 XAML 中的数据绑定或代码隐藏将 ListView 的ItemsSource属性设置为字典。

If your columns are not fixed, and you don't want to hard code them in the XAML, the other option is to pack the data into a DataTable, and display the DataGrid with automatic columns.如果您的列不是固定的,并且您不想在 XAML 中对它们进行硬编码,另一种选择是将数据打包到 DataTable 中,并使用自动列显示 DataGrid。

    private static DataTable DictionaryToDataTable(
        Dictionary<string, Dictionary<string, int>> values, 
        string fixedColumn)
    {

        DataTable result = new DataTable();
        result.Columns.Add(fixedColumn);

        IEnumerable<string> headers = values.SelectMany(row => row.Value)
            .Select(cell => cell.Key).Distinct();
        headers.ForEach(b => result.Columns.Add(b));

        foreach (KeyValuePair<string, Dictionary<string, int>> row in values)
        {
            DataRow dataRow = result.NewRow();
            dataRow[fixedColumn] = row.Key;
            foreach (KeyValuePair<string, int> cell in row.Value)
            {
                dataRow[cell.Key] = cell.Value;
            }

            result.Rows.Add(row);
        }

        return result;
    }

Then the Xaml is quite simple:那么 Xaml 就很简单了:

<DataGrid ItemsSource="{Binding MyDataTableProperty}" AutoGenerateColumns="True" />

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM