【问题标题】:How to display a Dictionary<string, Dictionary<string, CustomType>> in a Grid using DataTemplate?如何使用 DataTemplate 在网格中显示 Dictionary<string, Dictionary<string, CustomType>>?
【发布时间】:2015-09-03 16:42:13
【问题描述】:

我的文本条目既有类型又有类别。为了存储它们,我使用Dictionary&lt;string, Dictionary&lt;string, ModelEntry&gt;&gt; ModelEntries。不是我所知道的最漂亮的东西,但可以完美地用作带有字符串索引的二维数组。例如,通过这种方式,我可以调用ModelEntries["type1"]["category2"].Content 来获取相应条目的文本。类别和类型的数量固定为 3 和 4。

如何使用DataTemplate 将它们显示在类似Grid 的表格中?

我的目标是这样的: 网格的方框将被相应条目的内容填充。

到目前为止,我已经做到了:

    <StackPanel Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="4" Grid.RowSpan="3">
        <ItemsControl>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Grid />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <RichTextBox></RichTextBox>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </StackPanel>

我不知道如何继续。任何帮助将不胜感激。

旁注:我知道,由于行和列是固定数量,我可以用更多代码做我想做的事,并静态定义 12 个 RichTextBox。我想要 DataTemplate 方法的原因是当用户单击文本时将与其他字段进行交互。浏览字典将比手动检查所有静态定义的 RichTextBox 更容易。

【问题讨论】:

  • 您的项目控件模板需要另一个嵌套的项目控件。您在集合中有一个集合,因此您的视图需要相同。使用此模型作为模型,获取标题可能会更加复杂。您可能希望公开通过检查嵌套字典键属性构建的另一个标题集合。

标签: c# wpf dictionary datatemplate


【解决方案1】:

在进行 WPF 开发时,最常见的错误之一是人们试图将他们的视图适合他们现有的数据模型,而实际上应该由中间层(即视图模型)在两者之间进行调解。 XAML 非常强大,可以做一些聪明的事情,但是如果你发现自己不得不在视图中做太多的工作,或者过度依赖转换器之类的东西来将你的数据转换成更有用的形式,那么这通常是一个很好的迹象,表明你的视图模型没有正常工作。

退后一步,从另一个方向看:您心中有一个视图,您需要什么类型的数据才能使该视图起作用?首先你要显示两件事:你的标题(在顶部和左边)和你的单元格,所以首先在你的视图模型中为它​​们创建类:

public class CustomGridHeader
{
    public int Column { get; set; }
    public int Row { get; set; }
    public string Header { get; set; }
}

public class CustomGridCell
{
    public int Column { get; set; }
    public int Row { get; set; }
    public object Content { get; set; }
}

现在,您的视图模型将需要一组要显示的内容(标题和单元格)以及一个引用字典中数据的函数。我将把翻译步骤留给你,但这里已经足够开始了:

    private int _NumColumns;
    public int NumColumns
    {
        get { return this._NumColumns; }
        set { this._NumColumns = value; RaisePropertyChanged(() => this.NumColumns); }
    }

    private int _NumRows;
    public int NumRows
    {
        get { return this._NumRows; }
        set { this._NumRows = value; RaisePropertyChanged(() => this.NumRows); }
    }

    private object[] _GridData;
    public object[] GridData
    {
        get { return this._GridData; }
        set { this._GridData = value; RaisePropertyChanged(() => this.GridData); }
    }

    private string[] Types = new string[] { "Type1", "Type2", "Type3" };
    private string[] Categories = new string[] { "Cat1", "Cat2", "Cat3" };

    private void UpdateGridData()
    {
        this.NumColumns = this.Categories.Length + 1;
        this.NumRows = this.Types.Length + 1;
        this.GridData = new object[(this.Categories.Length + 1) * (this.Types.Length + 1)];

        // set category and type headers
        for (int i=0; i<this.Categories.Length; i++)
            this.GridData[i + 1] = new CustomGridHeader { Column = i + 1, Row = 0, Header = this.Categories[i] };
        for (int i=0; i<this.Types.Length; i++)
            this.GridData[(i + 1) * this.NumColumns] = new CustomGridHeader { Column = 0, Row = i + 1, Header = this.Types[i] };

        // fill the cells
        for (int i = 0; i < this.Categories.Length; i++)
            for (int j = 0; j < this.Types.Length; j++)
                this.GridData[(i + 1) * this.NumColumns + j + 1] = new CustomGridCell {Column = i+1, Row = j+1, Content = String.Format("{0}/{1}", i, j)};
    }

从代码方面来说,这就是您所需要的,您现在可以将您的数据以一种可以被您的视图使用的形式呈现出来。您想在网格中显示您的数据,但从功能上讲,您真正要做的是显示项目列表,因此您真正应该做的是使用 ItemsControl,但将 Grid 指定为面板模板。您还需要设置 ItemContainerStyle 以设置每个元素的网格列和行。最后,您需要两个数据模板:一个指定标题应如何显示,另一个指定单元格应如何显示:

<ItemsControl ItemsSource="{Binding GridData}" Margin="50">

    <ItemsControl.Resources>

        <DataTemplate DataType="{x:Type local:CustomGridHeader}">
            <TextBlock Text="{Binding Header}" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10"/>
        </DataTemplate>

        <DataTemplate DataType="{x:Type local:CustomGridCell}">
            <Border BorderBrush="Black" BorderThickness="1" Margin="0,0,-1,-1">
                <TextBlock Text="{Binding Content}" Background="AliceBlue" HorizontalAlignment="Center" VerticalAlignment="Center" Width="50" Height="50"/>
            </Border>
        </DataTemplate>

    </ItemsControl.Resources>

    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Grid
                xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
                xmlns:cmd ="http://www.galasoft.ch/mvvmlight">

                <i:Interaction.Behaviors>
                    <local:GridSizeBehavior Columns="4"  Rows="4"/>
                </i:Interaction.Behaviors>

            </Grid>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

    <ItemsControl.ItemContainerStyle>
        <Style>
            <Setter Property="Grid.Column" Value="{Binding Column}" />
            <Setter Property="Grid.Row" Value="{Binding Row}" />
        </Style>
    </ItemsControl.ItemContainerStyle>

</ItemsControl>

唯一缺少的部分是 GridSizeBehavior。 Grid 控件没有“NumColumns”或“NumRows”字段,但您可以通过附加行为自己有效地添加它(出于这个和其他原因,我不相信 Grid 是完成此任务的最佳控件,但这就是你所要求的,这就是我提供的):

public class GridSizeBehavior : Behavior<Grid>
{
    public int Columns
    {
        get { return (int)GetValue(ColumnsProperty); }
        set { SetValue(ColumnsProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Columns.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ColumnsProperty =
        DependencyProperty.Register("Columns", typeof(int), typeof(GridSizeBehavior), new PropertyMetadata(0, OnColumnsChanged));

    static void OnColumnsChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        var behaviour = depObj as GridSizeBehavior;
        if (behaviour == null)
            return;
        behaviour.SetColumns();
    }

    private void SetColumns()
    {
        var grid = this.AssociatedObject;
        if (grid == null)
            return;
        int columns = this.Columns;
        grid.ColumnDefinitions.Clear();
        for (int i = 0; i < columns; i++)
            grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
    }

    public int Rows
    {
        get { return (int)GetValue(RowsProperty); }
        set { SetValue(RowsProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Rows.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty RowsProperty =
        DependencyProperty.Register("Rows", typeof(int), typeof(GridSizeBehavior), new PropertyMetadata(0, OnRowsChanged));

    static void OnRowsChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        var behaviour = depObj as GridSizeBehavior;
        if (behaviour == null)
            return;
        behaviour.SetRows();
    }

    private void SetRows()
    {
        var grid = this.AssociatedObject;
        if (grid == null)
            return;
        int Rows = this.Rows;
        grid.RowDefinitions.Clear();
        for (int i = 0; i < Rows; i++)
            grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
    }


    protected override void OnAttached()
    {
        base.OnAttached();
        SetColumns();
        SetRows();
    }

}

把所有这些放在一起,你就会得到你想要的结果:

此处显示的确切实现的主要缺点是视图不会自动更新以响应对原始字典的更改,但鉴于字典不支持无论如何都不会发生的集合更改通知。是的,您完全可以在视图中使用转换器来枚举列和行并直接绑定到您的字典数据,但最终您会发现您的代码与我的代码非常匹配无论如何,我们已经在视图模型中完成了,所以您不妨以易于调试和单元测试的形式在那里完成。

【讨论】:

    猜你喜欢
    • 2012-03-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-22
    • 2012-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多