【问题标题】:WPF DataGrid with formatted text cells带有格式化文本单元格的 WPF DataGrid
【发布时间】:2016-07-14 12:09:22
【问题描述】:

AutoGenerateColmns=True 的设置中,我试图找到一种方法,让我可以在 WPF 中的 C# DataGrid 的单个单元格中显示多色文本。使用自定义单元格模板或在单元格内使用 HTML 解释器的解决方案都可以;出于技术原因,我更喜欢第一个选项,但我花了很多时间试图让其中任何一个工作,但无济于事。

另一个问题是我在这里处理了几个表,我必须使用相同的用户控件来显示它们。只有一张表需要文本格式。我已经知道如何根据我选择的标准动态更改表格的性质,但我不知道如何根据其中的实际文本填充这些修改过的单元格。文本本身包含一个用于分割字符串的标记("|||"—三个管道);一个显示为蓝色,另一个显示为红色。

让我给你看我的代码。我将从App.xaml 的摘录开始,以便清楚我是如何设置DataGrid 风格的。

我的App.xaml

如您所见,我需要让 DataGrid 自动生成列,因为我正在处理一个在编译时我不知道其架构的数据库。由于表格标题可能包含下划线,我创建了一个自定义模板,它不会以通常的方式处理下划线,即作为与 Alt 的简写键组合。

<Style x:Key="DataGridStyle" TargetType="DataGrid">
  <Setter Property="AutoGenerateColumns" Value="True" />
  <Setter Property="CanUserAddRows" Value="False" />
  <Setter Property="ColumnHeaderStyle">
    <Setter.Value>
      <Style TargetType="DataGridColumnHeader">
        <Setter Property="ContentTemplate">
          <Setter.Value>
            <DataTemplate>
              <TextBlock Text="{TemplateBinding Content}" 
                         HorizontalAlignment="Center"/>
            </DataTemplate>
          </Setter.Value>
        </Setter>
      </Style>
    </Setter.Value>
  </Setter>
  <Setter Property="CanUserSortColumns" Value="True" />
</Style>

DataGrid 本身

这有一些修改,其中一个是我正在使用基于另一个用户控件的自定义RowDetailsTemplate。这无关紧要(我希望如此),但为了完整起见,我把它留在这里(以防我遗漏了一些副作用)。 ExpView 是我正在使用的命名空间; “Exp”本身就是软件的名称(这就是为什么DataGrid的祖先项被称为“ExpView:ExpResultsTable”)。

你看我在这里订阅了两个事件。我已经尽可能地尝试让我的代码隐藏为空(如 MSDN 文档中建议的干净的 MVVM 实现),但我担心单独使用 XAML 只能做这么多……请随时纠正我,尽管。我使用这些事件来确定DataGrid 是用于特殊表还是普通表。

<DataGrid Name="DataGrid_Calibs"
          Style="{StaticResource DataGridStyle}"
          SelectedItem="{Binding Path=SelectedCalib,
                                 RelativeSource={RelativeSource AncestorType=ExpView:ExpResultsTable}}"
          AutoGeneratingColumn="DataGrid_Calibs_AutoGeneratingColumn"
          LoadingRow="DataGrid_Calibs_LoadingRow">
  <DataGrid.RowDetailsTemplate>
    <DataTemplate>
      <ExpView:CalibrationDetails/>
    </DataTemplate>
  </DataGrid.RowDetailsTemplate>
</DataGrid>

我还在此 XAML 文件的开头有以下资源声明,以防我必须显示“特殊”表。

<UserControl.Resources>
  <ResourceDictionary>
    <DataTemplate x:Key="DifferenceDataTemplate">
      <StackPanel Orientation="Vertical">
        <TextBlock x:Name="FieldValue_Old" Foreground="Blue"/>
        <TextBlock x:Name="FieldValue_New" Foreground="Red"/>
      </StackPanel>
    </DataTemplate>
  </ResourceDictionary>
</UserControl.Resources>

如果必须显示正常数据,计划是用正常数据绑定填充表。 (我没有用RowViews 填充DataGrid;而是将DataGrid 绑定到正确显示公共属性的对象。您将在下面的事件处理程序代码中看到它的效果。)如果数据是“特殊的”,我想用上面资源中定义的StackPanel 替换单元格,并设置TextBlocks(或Labels,如果这些结果更好)的内容关于应该进入单元格的字符串的第一部分和第二部分,在"|||"处拆分。

代码隐藏中的事件处理程序

用于自动生成DataGrid 列的 WPF 事件处理程序的美妙之处在于,除非您明确覆盖该列,否则它会正常工作。这就是为什么下面的代码可以很好地用前面提到的StackPanel 替换DataGrid 的单元格,以防我发现“特殊表”。

private void DataGrid_Calibs_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e) {
  ParameterDetails parameterDetailsControl = ((ScrollViewer)((DockPanel)((StackPanel)((DataGrid)sender).Parent).Parent).Parent).Parent as ParameterDetails;
  ExpUserInterface parent = Window.GetWindow((DependencyObject)parameterDetailsControl) as ExpUserInterface;
  ExpResultsTable resultsTableControl = parent.ResultsTable.Content as ExpResultsTable;
  string selector = resultsTableControl.SelectedTabItem.Header.ToString();

  if (selector.Equals("special")) {
    DifferenceDataTemplateColumn col = new DifferenceDataTemplateColumn();
    col.Header = e.PropertyName;
    col.CellValue = e.PropertyName;
    col.CellTemplate = (DataTemplate)FindResource("DifferenceDataTemplate");
    col.CellTemplate.DataType = typeof(MyDatabaseObject);
    col.IsReadOnly = true;
    e.Column = col;
  }
}

private void DataGrid_Calibs_LoadingRow(object sender, DataGridRowEventArgs e) {
  MyDatabaseObject calib = e.Row.DataContext as MyDatabaseObject;
  // anything I can do about the problem in here?
}

这里的第二个事件处理程序主要是为了向您展示我确实绑定到 MyDatabaseObject 而不是 RowView

我欣然承认前四行是丑陋的,但至少它们现在有效。不过,欢迎提出任何建议。

其余的工作也很好:“正常”表格按计划填充,“特殊”表格在每个单元格中都使用我自定义的StackPanels 格式化。但是,我没有成功填充这些单元格。

问题

这是我这几天一直在思考的问题,但我无法通过查询通常的嫌疑人(Stackoverflow、MSDN 等)来弄清楚:

  • 如何访问单元格的内容? LoadingRow 事件是我应该访问字符串、拆分它并设置FieldValue_Old.TextFieldValue_New.Text 的地方。我可以通过calib(见上文)访问该对象——调试器显示我就在那儿——但我终其一生都无法弄清楚如何访问单个的TextBlocks(“特殊”) 单元格,更不用说如何对当前行的所有列执行此操作了。
  • 我可以使用CellTemplateSelector 解决这个问题吗?怎么样?
  • 我是否使用了正确的事件处理程序?有没有我更喜欢这些的活动?
  • 还有其他方法可以达到这个结果吗?例如,我可以说服DataGrid 在其单元格中解释 HTML 吗? (这里我不需要担心代码注入,因为我只会在填写 DataGrid 的那一刻生成 HTML 代码。)

我们非常感谢任何帮助,并且在我为此撕扯头发时,我将不会迅速接近秃顶。谢谢!

更新

我忘记在前面提到的AutoGeneratingColumn 事件处理程序中添加自定义DataGridTemplateColumn 的代码。

public class DifferenceDataTemplateColumn : DataGridTemplateColumn {
    public string CellValue { get; set; }

    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem) {
      ContentPresenter presenter = (ContentPresenter)base.GenerateElement(cell, dataItem);
      BindingOperations.SetBinding(presenter, ContentPresenter.ContentProperty, new Binding(this.CellValue));
      return presenter;
    }
  }

【问题讨论】:

    标签: c# wpf xaml mvvm datagrid


    【解决方案1】:

    事实证明,我已经非常接近解决方案了;它可以在不诉诸DataGrid_Calibs_LoadingRowTemplateSelector 的情况下实现。我们仍然需要DataGrid_Calibs_AutoGeneratingColumn 事件处理程序,但只是为了正确应用带有两个TextBlocks 的自定义单元格模板,我们仍然需要自定义DataGridTemplateColumn

    新的用户控件 XAML 代码

    诀窍是扩展用户控件的资源定义。

    <UserControl.Resources>
      <ResourceDictionary>
        <DataTemplate x:Key="DifferenceDataTemplate">
          <StackPanel Orientation="Vertical">
            <TextBlock x:Name="FieldValue_Old" Foreground="Blue" Text="{Binding Converter={StaticResource fieldValueConverter}, ConverterParameter={StaticResource True}}"/>
            <TextBlock x:Name="FieldValue_New" Foreground="Red" Text="{Binding Converter={StaticResource fieldValueConverter}, ConverterParameter={StaticResource False}}"/>
          </StackPanel>
        </DataTemplate>
      </ResourceDictionary>
    </UserControl.Resources>
    

    App.xaml 的补充

    我已经以允许它同时用于TextBlocks 的方式定义了转换器,但为了这样做,我需要传递一个bool。由于 XAML 默认将ConverterParameters 传递为string,因此我在App.xaml 中定义了两个新资源,除了fieldValueConverter

    <Application ...
                 xmlns:s="clr-namespace:System;assembly=mscorlib"
                 xmlns:MyNamespace="clr-namespace:MyNamespace"
                 ... >
      <Application.Resources>
        <MyNamespace:FieldValueConverter x:Key="fieldValueConverter" />
        <s:Boolean x:Key="True">True</s:Boolean>
        <s:Boolean x:Key="False">False</s:Boolean>
        ...
      </Application.Resources>
    </Application>
    

    App.xaml.cs 的补充

    剩下要做的就是在App.xaml的代码隐藏中定义实际值转换器。

    [ValueConversion(typeof(string), typeof(string))]
    public class FieldValueConverter : IValueConverter {
      public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
        bool isOld = (bool)parameter;
        string[] fieldValues = value.ToString().Split(new string[] {"|||"}, System.StringSplitOptions.None);
    
        string fieldValue = "";
        if (isOld)
          fieldValue = fieldValues[0];
        else if (!isOld && fieldValues.Length == 2)
          fieldValue = fieldValues[1];
        return fieldValue;
      }
    
      public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
        throw new NotImplementedException("Back conversion of comparison fields is not supported.");
      }
    }
    

    fieldValues.Length == 2 检查的唯一原因是,对于不包含字符串 "|||" 的字段,我不希望第二个字段显示任何内容。另外,由于不包含差异的字段默认为空,所以我默认返回空字符串。

    这就是缺少的一切!它现在就像一个魅力。

    奖金问题

    我不明白为什么需要自定义 DataGridTemplateColumn。它提供了另一个属性来访问我上面所说的CellValue,因为否则我的数据绑定只会在绑定对象上调用ToString() 方法,即单元格包含对象名称,而不是字段值。但为什么呢?

    致谢

    非常感谢在 StackOverflow 上发布到 thisthis 问题的答案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-10-08
      • 2018-04-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-28
      • 2021-03-29
      相关资源
      最近更新 更多