【问题标题】:Bind GridView with Dictionary in WPF C#在 WPF C# 中将 GridView 与字典绑定
【发布时间】:2013-11-28 10:11:01
【问题描述】:

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

gridview的来源设置为result

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

有点像 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
--------------------------------------
一个 | 1 | 2 | 3
乙| 4 | 5 | 6
C | 7 | 8 | 9

【问题讨论】:

  • 1.为什么哦,为什么你想用代码来做呢? XAML 有什么问题? 2. 那么,你的问题是什么?有什么例外吗?有什么投诉吗?更具体...
  • 我只想将内部字典的值绑定到一列,我无法做到这一点,就像我如何引用内部字典的值一样(result[xyz].values().值)
  • 你能稍微简化一下你的问题吗?目前尚不清楚您想要什么。您想为字典中的每个键创建一个列(现在无论字典存储在哪里都无关紧要)?那么每个 row 的值是多少?
  • 对不起,这是一个错误,我在问题中给出了字典结构
  • 我明白了。我用 DataGrid 做了类似的事情。这并不简单直接,因为您不能直接绑定到字典的值(您需要密钥)。它涉及数据单元格的转换器,这些转换器使用与特定列关联的键提取字典的相应值(表示一行的字典)。如果 GridView 相同,我会调查它,但我今天没有时间去做。也许其他人可以更快地提供答案;如果没有,明天我会回到你身边......

标签: c# wpf visual-studio gridview dictionary


【解决方案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" />

【讨论】:

    【解决方案2】:

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

    GridView 基本上只是 ListView 控件的视图模式。 ListView 控件可视化对象/值(无论这些对象是什么)的集合(或列表)。 稍后将解释这与要显示的字典的关系。

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

    Dictionary< string, Dictionary<string, int > >
    

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

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

    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 可能类似于以下内容。

    假设要显示的列是“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>
    

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

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


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

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

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

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

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

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

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

    【讨论】:

    • 非常感谢这么漂亮的解释,再好不过了,非常感谢......
    • 这一定是我在 stackoverflow 上读过的最彻底的答案!
    猜你喜欢
    • 2010-11-09
    • 2011-03-27
    • 2012-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-20
    相关资源
    最近更新 更多