好的,在 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属性的简单绑定就足够了。
要访问存储在 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属性设置为字典。