【问题标题】:How to set background of a datagrid cell during AutoGeneratingColumn event depending on its value?如何在 AutoGeneratingColumn 事件期间根据其值设置数据网格单元格的背景?
【发布时间】:2013-05-14 18:50:17
【问题描述】:

我仍在与单元格背景的操纵作斗争,所以我提出了一个新问题。

用户“H.B.”写道我实际上可以在AutoGeneratingColumn 事件期间设置单元格样式 - Change DataGrid cell colour based on values。问题是我不知道该怎么做。

我想要什么: 根据每个单元格的值设置不同的背景颜色。如果值为null,我还希望它可点击(我猜是可聚焦的)。

我拥有/正在尝试做的事情:

private void mydatagrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
    foreach (Cell cell in e.Column)
    {
        if (cell.Value < 1)
        { 
            cell.Background = Color.Black; 
            cell.isFocusable = false; 
        } 
        else
        {
            cell.Background = Color.Pink;
        }
    }
}

这只是伪代码。在列自动生成期间是否有可能发生这样的事情?如果是这样,我该如何编辑我的代码以使其有效?

我阅读了有关值转换器的信息,但我想知道它是否可以通过编程方式实现,而无需编写 XAML。

请理解我还是 C#/WPF/DataGrid 的初学者。

解决方案第 1 部分:

我使用了我接受的答案。把它放进去

<Window.Resources> 
<local:ValueColorConverter x:Key="colorConverter"/>
        <Style x:Key="DataGridCellStyle1" TargetType="{x:Type DataGridCell}"> 
            <Setter Property="Padding" Value="5"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type DataGridCell}">
                        <Border Padding="{TemplateBinding Padding}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True">
                            <Border.Background>
                                <MultiBinding Converter="{StaticResource colorConverter}">
                                    <Binding RelativeSource="{RelativeSource AncestorType=DataGridCell}" Path="Content.Text"/>
                                    <Binding RelativeSource="{RelativeSource AncestorType=DataGridCell}" Path="IsSelected"/>
                                </MultiBinding>
                            </Border.Background>
                            <ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
</Window.Resources>

并为其制作了一个MultiBinding 转换器,因此我还可以为选定的单元格设置背景颜色。

问题:

现在我只需要解决设置空单元格焦点的问题。有什么提示吗?

  <Style.Triggers>
        <Trigger Property="HasContent" Value="False">
            <Setter Property="Focusable" Value="False"/>
        </Trigger>
    </Style.Triggers>

这不起作用。我在空单元格中有空字符串,但它们充满了'null',所以它应该可以工作,对吧?或者我做错了什么:| ?

解决方案第 2 部分:

所以只要单元格值是“TextBox”,上面的代码就无法工作,所以我决定找到另一种处理方法,可以在我的答案中找到:https://stackoverflow.com/a/16673602/2296407

感谢您尝试帮助我:)

【问题讨论】:

  • 你可以在grid的rowdatabound事件中试试这个。 stackoverflow.com/questions/3146456/…
  • 自动生成的单元格当然有“内容” - 它是 (这就是我在回答中使用 Content.Text 的原因):) 尝试使用
  • @morincer 感谢您的帮助。我设法以其他方式处理空单元格,因为 DataTrigger 不想和我一起工作,我已经很生气了:D。但真的很感谢您的支持。
  • @goodfriend,没问题。为您的答案添加了一些代码风格的注释
  • 这更像是假代码 = 伪代码 :) (虽然我确实有同样的问题,但我正在寻找代码隐藏中的解决方案

标签: c# wpf datagrid background autogeneratecolumn


【解决方案1】:

我不确定此属性 (Cell.Style) 在您的 WPF Datagrid 中是否可用。在您的情况下可能存在一些替代方案。它适用于 WinForms 数据网格。

 cell.Style.BackColor = System.Drawing.Color.Black;

【讨论】:

    【解决方案2】:

    我的意思是您可以设置列的CellStyle 属性,您不能直接操作单元格,因为它们在此事件中不可用。该样式可以包含DataTriggers 形式的条件逻辑(将需要一个转换器,因为您有“小于”和不等于)和Setters

    此外,如果逻辑不特定于列,您可以设置 style globally on the grid 本身。使用该事件的目的是操作您无法访问的列属性。

    【讨论】:

      【解决方案3】:

      我可以为您的问题提出两种不同的解决方案:第一种是“代码隐藏样式”(您要求但我个人认为这不是 WPF 中的正确方法)和更多 WPF 样式(更棘手但保持代码隐藏干净并利用样式、触发器和转换器)

      解决方案 1. 用于着色的事件处理和代码隐藏逻辑

      首先,您选择的方法不会直接起作用 - AutoGeneratingColumn 事件旨在用于更改整个列的外观,而不是逐个单元格地进行。因此,它可以用于根据其显示索引或绑定属性将正确的样式附加到整个列。

      一般来说,第一次引发事件时,您的数据网格将根本没有任何行(因此 - 单元格)。如果您真的需要捕捉事件 - 请考虑您的 DataGrid.LoadingRow 事件。而且您将无法轻松获得单元格:)

      那么,你要做的事情:处理 LoadingRow 事件,获取行(它有 Item 属性,它持有(令人惊讶的:))你的绑定项目),获取绑定项目,使所有需要计算,得到你需要改变的单元格,最后改变目标单元格的样式。

      这是代码(作为项目,我使用了一个带有 int “Value”属性的示例对象,用于着色)。

      XAML

         <DataGrid Name="mygrid" ItemsSource="{Binding Items}" AutoGenerateColumns="True" LoadingRow="DataGrid_OnLoadingRow"/>
      

      .CS

          private void DataGrid_OnLoadingRow(object sender, DataGridRowEventArgs e)
          {
              Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() => AlterRow(e)));
          }
      
          private void AlterRow(DataGridRowEventArgs e)
          {
              var cell = GetCell(mygrid, e.Row, 1);
              if (cell == null) return;
      
              var item = e.Row.Item as SampleObject;
              if (item == null) return;
      
              var value = item.Value;
      
              if (value <= 1) cell.Background = Brushes.Red;
              else if (value <= 2) cell.Background = Brushes.Yellow;
              else cell.Background = Brushes.Green;
          }
      
          public static DataGridRow GetRow(DataGrid grid, int index)
          {
              var row = grid.ItemContainerGenerator.ContainerFromIndex(index) as DataGridRow;
      
              if (row == null)
              {
                  // may be virtualized, bring into view and try again
                  grid.ScrollIntoView(grid.Items[index]);
                  row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
              }
              return row;
          }
      
          public static T GetVisualChild<T>(Visual parent) where T : Visual
          {
              T child = default(T);
              int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
              for (int i = 0; i < numVisuals; i++)
              {
                  var v = (Visual)VisualTreeHelper.GetChild(parent, i);
                  child = v as T ?? GetVisualChild<T>(v);
                  if (child != null)
                  {
                      break;
                  }
              }
              return child;
          }
      
          public static DataGridCell GetCell(DataGrid host, DataGridRow row, int columnIndex)
          {
              if (row == null) return null;
      
              var presenter = GetVisualChild<DataGridCellsPresenter>(row);
              if (presenter == null) return null;
      
              // try to get the cell but it may possibly be virtualized
              var cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(columnIndex);
              if (cell == null)
              {
                  // now try to bring into view and retreive the cell
                  host.ScrollIntoView(row, host.Columns[columnIndex]);
                  cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(columnIndex);
              }
              return cell;
      
          }
      

      解决方案 2. WPF 样式

      此解决方案仅将代码隐藏用于值到颜色的转换(假设着色逻辑比相等比较更复杂 - 在这种情况下,您可以使用触发器而不要与转换器混淆)。

      您的操作:使用包含数据触发器的样式设置 DataGrid.CellStyle 属性,该触发器检查单元格是否在所需列内(基于其 DisplayIndex),如果是 - 通过转换器应用背景。

      XAML

      <DataGrid Name="mygrid" ItemsSource="{Binding Items}" AutoGenerateColumns="True">
              <DataGrid.Resources>
                  <local:ValueColorConverter x:Key="colorconverter"/>
              </DataGrid.Resources>
              <DataGrid.CellStyle>
                  <Style TargetType="DataGridCell">
                      <Style.Triggers>
                          <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Column.DisplayIndex}" Value="1">
                              <Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.Text, Converter={StaticResource colorconverter}}"/>
                          </DataTrigger>
                      </Style.Triggers>
                  </Style>
              </DataGrid.CellStyle>
          </DataGrid>
      

      .CS

      public class ValueColorConverter : IValueConverter
      {
          public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
          {
              var str = value as string;
              if (str == null) return null;
      
              int intValue;
              if (!int.TryParse(str, out intValue)) return null;
      
              if (intValue <= 1) return Brushes.Red;
              else if (intValue <= 2) return Brushes.Yellow;
              else return Brushes.Green;
          }
      
          public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
          {
              throw new NotImplementedException();
          }
      }
      

      UPD:如果您需要为整个数据网格着色,XAML 会容易得多(无需使用触发器)。使用以下 CellStyle:

          <DataGrid.CellStyle>
                  <Style TargetType="DataGridCell">
                       <Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content.Text, Converter={StaticResource colorconverter}}"/>
                  </Style>
          </DataGrid.CellStyle>
      

      【讨论】:

      • 你应该实现一个“is-less-than-converter”或类似的,因为IsFocusable也应该改变。
      • 感谢您提供如此长且有用的回答。我认为转换器运行良好,但那些电池只是不想着色。我尝试编辑设置,并且在那里使用双值。 - 稍微编辑了一下。但还是。我对数据绑定不够友好。你觉得可能有问题吗?我正在使用数据表从数据集填充我的 c# 代码中的数据网格。我正确地获取了转换器中的值,但它不会为我的单元格背景着色:'(
      • 能否请您发布一段用于绑定网格并为其着色(XAML + 代码隐藏)的代码,以便我检查出了什么问题?
      • 好吧,有什么傻事就别笑了。但问题是我在数据网格中需要的东西并不总是相同的数量(行数/列数),所以我用这些东西填充数据表并将该表放入数据集中只是因为它以某种方式工作。我总是使用以下代码通过单击按钮来填充数据网格:mydatagrid.ItemsSource = dataset.Tables[0].DefaultView; 它可以工作,但我不确定如何将这种填充网格与背景颜色的设置联系起来。我没有在 xaml 中写任何关于绑定的内容,只要这样做就可以了。
      • 我正在使用您发布的代码。只是在任何地方都用 double 替换了 int,因为我的值是双倍的。我只是不太明白 XAML 中的代码应该如何工作。我得到了触发器,但我不明白绑定是如何工作的。 中的那个。请你帮我理解一下好吗?
      猜你喜欢
      • 2017-02-05
      • 1970-01-01
      • 1970-01-01
      • 2011-07-13
      • 1970-01-01
      • 2013-11-23
      • 2019-11-21
      • 1970-01-01
      • 2013-03-23
      相关资源
      最近更新 更多