DataGrid:动态 DataGridTemplateColumn 的动态 DataTemplate

发布于 2024-10-11 11:28:31 字数 1258 浏览 10 评论 0原文

我想在数据网格中显示数据,其中数据是

public class Thing
{
    public string Foo { get; set; }
    public string Bar { get; set; }
    public List<Candidate> Candidates { get; set; }
}

public class Candidate
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    ...
}

候选人列表中候选人数量在运行时变化的集合。

所需的网格布局如下所示

Foo | Bar | Candidate 1 | Candidate 2 | ... | Candidate N

我希望为每个候选人提供一个DataTemplate,因为我计划在运行时更改它 - 用户可以选择以不同的方式显示有关候选人的信息列(候选人只是一个例子,我有不同的对象)。这意味着我还想在运行时更改列模板,尽管这可以通过一个大模板并折叠其各个部分来实现。

我知道如何实现我的目标有两种方法(两者都非常相似):

  1. 使用 AutoGenerateColumn 事件并创建 Candidates
  2. 手动添加列

在这两种情况下,我都需要加载 DataTemplate 来自带有 XamlReader 的字符串。在此之前,我必须编辑字符串以将绑定更改为想要的Candidate

是否有更好的方法来创建具有未知数量的 DataGridTemplateColumn 的 DataGrid?

注意: 这个问题基于 带有 valueconverter 的动态数据模板

编辑: 由于我需要支持 WPF 和 Silverlight,因此我创建了自己的 DataGrid 组件,它具有用于绑定列集合的DependencyProperty。当集合发生变化时,我会更新列。

I want to show data in a datagrid where the data is a collection of

public class Thing
{
    public string Foo { get; set; }
    public string Bar { get; set; }
    public List<Candidate> Candidates { get; set; }
}

public class Candidate
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    ...
}

where the number of candidates in Candidates list varies at runtime.

Desired grid layout looks like this

Foo | Bar | Candidate 1 | Candidate 2 | ... | Candidate N

I'd like to have a DataTemplate for each Candidate as I plan changing it during runtime - user can choose what info about candidate is displayed in different columns (candidate is just an example, I have different object). That means I also want to change the column templates in runtime although this can be achieved by one big template and collapsing its parts.

I know about two ways how to achieve my goals (both quite similar):

  1. Use AutoGeneratingColumn event and create Candidates columns
  2. Add Columns manually

In both cases I need to load the DataTemplate from string with XamlReader. Before that I have to edit the string to change the binding to wanted Candidate.

Is there a better way how to create a DataGrid with unknown number of DataGridTemplateColumn?

Note: This question is based on dynamic datatemplate with valueconverter

Edit: As I need to support both WPF and Silverlight, I've created my own DataGrid component which has DependencyProperty for bindig a collection of columns. When the collection changes, I update the columns.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

七婞 2024-10-18 11:28:31

例如,我们创建 2 个 DataTemplate 和一个 ContentControl:

<DataTemplate DataType="{x:Type viewModel:VariantA}"> <dataGrid...> </DataTemplate>
<DataTemplate DataType="{x:Type viewModel:VariantB}"> <dataGrid...> </DataTemplate>

<ContentControl Content="{Binding Path=GridModel}" />

现在,如果您将 GridModel 属性(例如类型对象)设置为 VariantA 或 VariantB,它将切换 DataTemplate。

变体A& B 实施示例:

public class VariantA
{
    public ObservableCollection<ViewModel1> DataList { get; set; }
}

public class VariantB
{
    public ObservableCollection<ViewModel2> DataList { get; set; }
}

希望这会有所帮助。

For example we create 2 DataTemplates and a ContentControl:

<DataTemplate DataType="{x:Type viewModel:VariantA}"> <dataGrid...> </DataTemplate>
<DataTemplate DataType="{x:Type viewModel:VariantB}"> <dataGrid...> </DataTemplate>

<ContentControl Content="{Binding Path=GridModel}" />

Now if you set your GridModel Property (for example type object) to VariantA or VariantB, it will switch the DataTemplate.

VariantA & B example Implementation:

public class VariantA
{
    public ObservableCollection<ViewModel1> DataList { get; set; }
}

public class VariantB
{
    public ObservableCollection<ViewModel2> DataList { get; set; }
}

Hope this helps.

も让我眼熟你 2024-10-18 11:28:31

我不知道这是否是一种“更好”的方式,因为这仍然很丑陋,但我个人是这样做的:

  • 使 xaml 中的模板
  • 使用多重绑定,该多重绑定采用当前绑定 + 到列的绑定来获取“正确的”dataContext(即:单元格而不是行)
  • 在此绑定上使用转换器来获取您喜欢的属性的值,如果您有许多属性要检索,则可以选择添加一个参数。

例如:(抱歉,我没有调整我的代码来适合您的项目,但您应该能够从那里自己做)

这是我的数据模板:

<DataTemplate x:Key="TreeCellTemplate">
    <Grid>
        <TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Margin="5,0,0,0">
            <TextBlock.Text>
                <MultiBinding Converter="{StaticResource RowColumnToCellConverter}" ConverterParameter="Text">
                    <Binding />
                    <Binding RelativeSource="{RelativeSource AncestorType=DataGridCell}" Path="Column" />
                </MultiBinding>
            </TextBlock.Text>
        </TextBlock>
    </Grid>
</DataTemplate>

这是我的转换器:

   public class RowColumnToCellConverter : MarkupExtension, IMultiValueConverter
   {
      public RowColumnToCellConverter() { }

      public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
      {
         XwpfRow row = values[0] as XwpfRow;
         XwpfTreeColumn column = values[1] as XwpfTreeColumn;

         if (row == null || column == null) return DependencyProperty.UnsetValue;

         TreeCell treeCell = (TreeCell)row[column.DataGrid.Columns.IndexOf(column)];
         switch ((string)parameter)
         {
            case "Text": return treeCell.Text;
            case "Expanded": return treeCell.Expanded;
            case "ShowExpandSymbol": return treeCell.ShowExpandSymbol;
            case "CurrentLevel": return new GridLength(treeCell.CurrentLevel * 14);

            default:
               throw new MissingMemberException("the property " + parameter.ToString() + " is not defined for the TreeCell object");
         }
      }

      public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
      {
         throw new NotSupportedException();
      }

      public override object ProvideValue(IServiceProvider serviceProvider)
      {
         return new RowColumnToCellConverter();
      }
   }

这保存了 MVVM 模型,我更喜欢这种方式因为我真的不喜欢使用 xaml 解析器来制作“动态”数据模板,但从我的角度来看,它仍然是一个丑陋的 Hack。

我希望 MS 的人能给我们一种方法来获取单元格而不是行作为 dataContexts,以便能够动态生成模板列...

希望这有助于

编辑:在您的情况下,转换器实际上应该简单得多(如果我没记错的话,你可以直接返回单元格的实例,并且不需要任何参数),但我仍然保留了更复杂的版本,以防其他人遇到类似的问题

I don't know if this is a "better" way, since this remains pretty ugly, but I personnaly did like this:

  • make the template in xaml
  • use a multibind that takes the current binding + a binding to the column to get the "correct" dataContext (i.e.: the cell instead of the row)
  • use a converter on this binding to get the value of the property you like, an optionally add a parameter if you have many properties to retrieve.

e.g.: (sorry, I did not adapt my code to suit your project, but you should be able to do it yourself from there)

here is my dataTemplate:

<DataTemplate x:Key="TreeCellTemplate">
    <Grid>
        <TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Margin="5,0,0,0">
            <TextBlock.Text>
                <MultiBinding Converter="{StaticResource RowColumnToCellConverter}" ConverterParameter="Text">
                    <Binding />
                    <Binding RelativeSource="{RelativeSource AncestorType=DataGridCell}" Path="Column" />
                </MultiBinding>
            </TextBlock.Text>
        </TextBlock>
    </Grid>
</DataTemplate>

and here is my converter:

   public class RowColumnToCellConverter : MarkupExtension, IMultiValueConverter
   {
      public RowColumnToCellConverter() { }

      public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
      {
         XwpfRow row = values[0] as XwpfRow;
         XwpfTreeColumn column = values[1] as XwpfTreeColumn;

         if (row == null || column == null) return DependencyProperty.UnsetValue;

         TreeCell treeCell = (TreeCell)row[column.DataGrid.Columns.IndexOf(column)];
         switch ((string)parameter)
         {
            case "Text": return treeCell.Text;
            case "Expanded": return treeCell.Expanded;
            case "ShowExpandSymbol": return treeCell.ShowExpandSymbol;
            case "CurrentLevel": return new GridLength(treeCell.CurrentLevel * 14);

            default:
               throw new MissingMemberException("the property " + parameter.ToString() + " is not defined for the TreeCell object");
         }
      }

      public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
      {
         throw new NotSupportedException();
      }

      public override object ProvideValue(IServiceProvider serviceProvider)
      {
         return new RowColumnToCellConverter();
      }
   }

this saves the MVVM model, and I prefer this way of doing things because I really dislike using xaml parsers to make "dynamic" datatemplates, but it's still an ugly Hack from my point of view.

I wish the guys at MS would give us a way to get cells instead of rows as dataContexts to be able to generate templated columns on the fly...

hope this helps

EDIT: In your case, the converter ought to be a lot simpler actually (you can return the cell's instance directly if I'm not mistaken, and you don't need any parameter), but I left the more complex version nonetheless, just in case somebody else has a similar issue

只有一腔孤勇 2024-10-18 11:28:31

我一直在研究类似的问题,但只发现了一些有用的模式。整个“动态列”问题在 silverlight 中是一个有趣的问题。

昨天,我在 Silverlight DataGrid with Dynamic Columns 上找到了此页面我搜索期间 Travis Pettijohn 的网站。

以前我一直在使用 Colin Eberhardt 效果非常好......只要您使用 DataGridTextColumn。一切都可以在代码隐藏中完成,并且我在运行时应用样式没有遇到任何问题。然而,我现在的要求是应用一些“单元格级别”格式 - 更改单元格的背景等 - 这意味着需要 DataGridTemplateColumn

对于我来说,DataGridTemplateColumn 的一个大问题是我无法在代码中设置绑定。我知道我们可以通过解析 xaml 来构建它,但就像其他人一样,这似乎是一个巨大的黑客攻击并且无法维护。

Travis(上面的第一个链接)概述的模式完全不同。在“运行时”(即页面加载时),在网格中创建所需的列。这意味着迭代您的集合,并为每个项目添加一个具有适当标题等的列。然后为 RowLoaded 事件实现一个处理程序,并且在加载每一行时只需设置 DataContext 每个单元格到父级的适当属性/属性索引。

private void MyGrid_RowLoaded(object sender, EventArgs e)
{
    var grid = sender as DataGrid;
    var myItem = grid.SelectedItem as MyClass;
    foreach (int i = 0; i < myItem.ColumnObjects.Count; i++)
    { 
        var column = grid.Columns[i]; 
        var cell = column.GetCellContent(e.Row)
        cell.DataContext = myItem.ColumnObjects[i];
    }
}

这使我不再需要使用索引转换器。在设置 cell.DataContext 时,您可能可以使用 Binding,但对我来说,让模板直接绑定到底层对象会更容易。

我现在计划拥有多个模板(其中每个模板都可以绑定到我的单元格对象上的相同属性)并在页面加载时在它们之间切换。非常整洁的解决方案。

I've been looking at a similar problem and have only found a handful of useful patterns. The whole 'dynamic column' problem is an interesting one in silverlight.

Yesterday I found this page Silverlight DataGrid with Dynamic Columns on Travis Pettijohn's site during my searches.

Previously I'd been using the 'index converter' pattern outlined by Colin Eberhardt which works fantastically well... as long as you use DataGridTextColumn. Everything can be done in code behind, and I had no trouble applying styles at run time. However my requirement is now to apply some 'cell level' formatting - change the background for the cell, etc - which means a DataGridTemplateColumn is required.

The big problem with a DataGridTemplateColumn for me was that I can't set the binding in code. I know we can build it by parsing xaml, but like everyone else that seems like a massive hack and unmaintainable to the nth.

The pattern outlined by Travis (the first link above) is completely different. At 'run time' (i.e. page load time), create the columns you need in your grid. This means iterate through your collection, and add a column for each item with the appropriate header etc. Then implement a handler for the RowLoaded event, and when each row is loaded simply set the DataContext for each cell to the appropriate property / property index of the parent.

private void MyGrid_RowLoaded(object sender, EventArgs e)
{
    var grid = sender as DataGrid;
    var myItem = grid.SelectedItem as MyClass;
    foreach (int i = 0; i < myItem.ColumnObjects.Count; i++)
    { 
        var column = grid.Columns[i]; 
        var cell = column.GetCellContent(e.Row)
        cell.DataContext = myItem.ColumnObjects[i];
    }
}

This has removed the need for me to use the index converter. You can probably use a Binding when setting the cell.DataContext but for me it's easier to have the template simply bind directly to the underlying object.

I now plan on having multiple templates (where each can bind to the same properties on my cell object) and switching between them at page load. Very tidy solution.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文