在WPF C#中使用Dictionary绑定GridView

时间:2013-11-15 05:06:45

标签: c# wpf visual-studio gridview dictionary

我正在尝试以编程方式将字典绑定到WPF(C#)中的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);  

gridview的来源设置为结果

我想创建一个列,其标题设置为内部Dictionary的Key,并将其绑定到内部Dictionary的值

有些人喜欢     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)}
}

所以gridview就像

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

2 个答案:

答案 0 :(得分:17)

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

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

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

Dictionary< string, Dictionary<string, int > >

(外部)字典的键表示文件名,应显示在&#34; FileName&#34; GridView的列。 与每个文件名关联的(内部)字典将包含第二列和更多列的值。

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

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

这实际上正是ListView控件所需要的 - 一组元素。此集合中的每个元素都是此类型:

KeyValuePair< string, Dictionary<string, int > >

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

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

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

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


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

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

要访问存储在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();
        }
    }


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

让我们说,要显示的列是&#34; FileName&#34;,&#34; A1&#34;,&#34; A2&#34;,&#34; A3&#34; (后三者是内部词典中的键)。 另请注意三个自定义 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>

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

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


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

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

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

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

它只是在代码隐藏中设置绑定。 您可能会指出我们也可以将简单的GridViewColumn与&#34; {Binding Key}&#34;如前例子所示的绑定 - 你绝对是对的。 我只在这里展示这个实现,以说明可能的方法。

表示内部字典值的列的自定义列类型有点复杂。 作为之前的自定义值转换器,此自定义列需要知道正在显示的值的键。 通过将 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
}

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

使用自定义列类型在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>

在代码隐藏中,也可以轻松地在 GridView.Columns 属性中添加和删除列,而不是在XAML中声明列。 再次,不要忘记将ListView的 ItemsSource 属性设置为字典,通过XAML中的数据绑定或代码隐藏。

答案 1 :(得分:1)

如果您的列不是固定的,并且不想在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;
    }

然后Xaml很简单:

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