【问题标题】:Highlight cells if found如果找到则突出显示单元格
【发布时间】:2013-12-18 14:17:46
【问题描述】:

我有一个数据网格,它在绑定到数据表后输出 90 行。行值是 1-90 升序。

我想要达到的目标:

如果在数组/范围/数字列表中找到该数字,则将其突出显示为绿色。

我已经设法让它根据 1 个值突出显示单元格,但是如果在范围内找到多个单元格,我想突出显示它们。

<DataGrid Name="grid" ItemsSource="{Binding}" Height="300" Width="900"
          AutoGenerateColumns="False"
          VerticalScrollBarVisibility="Disabled" HorizontalAlignment="Center" VerticalAlignment="Top" RowHeight="40">
            <DataGrid.Resources>
                <Style x:Key="BackgroundColourStyle" TargetType="{x:Type TextBlock}">
                    <Style.Triggers>
                        <Trigger Property="Text" Value="1">
                            <Setter Property="Background" Value="LightGreen" />
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </DataGrid.Resources>
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Path=Number}" 
            ElementStyle="{StaticResource BackgroundColourStyle}" MinWidth="40">
                </DataGridTextColumn>
            </DataGrid.Columns>

            <DataGrid.ItemsPanel>
                <ItemsPanelTemplate>                    
                    <WrapPanel Orientation="Vertical" />
                </ItemsPanelTemplate>
            </DataGrid.ItemsPanel>
        </DataGrid>

你可以看到我有一个值为 1 的触发器属性。这工作正常,但如上所述,我如何更改它,所以如果一个单元格在后端的 c# 中设置的范围内,则将其突出显示为绿色。

绑定到数据表:

calledGrid.DataContext = calledNumbers.DefaultView;

数据表的实际制作:

DataSet dataSet = new DataSet("myDS");
            this.bingoCalls(dataSet);
            DataTable numbersTable = new DataTable("Numbers");
            numbersTable.Columns.Add("Number", typeof(Int32));

            for (int i = 1; i < 91; i++)
            {
                numbersTable.Rows.Add(i);
            }
            dataSet.Tables.Add(numbersTable);

感谢您的任何帮助。如果您需要更多信息,或者我对任何事情都含糊不清或不清楚,请询问,我会尽快回复。也请原谅我可能有的任何无知,我对 wpf 很陌生。我会尽力而为。

【问题讨论】:

  • 很难不了解周围的环境。例如,这个搜索是如何完成的。我会在视图模型中触发对该数据网格的搜索,它只是在每个 ViewModel 上设置一个值(例如 IsHighlighted),并且在 xaml 中有一个简单的触发器。但这实际上取决于您的其余代码。
  • @Ryan Murphy - 您可以使用IMultiValueConverter 实现这一目标。我已经添加了一个答案。看看有没有帮助..!!
  • 由于这是 WPF,我实际上会尝试向填充有 @987654326 的数据行对象添加一个 IsGreen 属性(或根据您的业务逻辑命名的类似名称) @/false,然后让您的 UI 反映这一点。

标签: c# wpf datagrid


【解决方案1】:

您可以使用IMultiValueConverter 来实现,如果 Text 在范围内,则返回 true,否则返回 false。 Based on the value returned by converter, change background color.

将三个参数传递给转换器:

  1. 实际文本值。
  2. 最小值。
  3. 最大值。

转换器

public class ItemExistInRangeConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType,
                          object parameter, CultureInfo culture)
    {
        bool itemsExistInRange = false;
        if (values.Length == 3)
        {
            int outputValue = int.MinValue;
            if (Int32.TryParse(values[0].ToString(), out outputValue))
            {
                int minValue = (int)values[1];
                int maxValue = (int)values[2];
                itemsExistInRange = minValue <= outputValue
                                     && outputValue <= maxValue;
            }
        }
        return itemsExistInRange;
    }

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

XAML

<DataGrid.Resources>
    <local:ItemExistInRangeConverter x:Key="ItemExistInRangeConverter"/>
    <sys:Int32 x:Key="MinimumValue">1</sys:Int32>
    <sys:Int32 x:Key="MaximumValue">50</sys:Int32>
    <Style x:Key="BackgroundColourStyle" TargetType="{x:Type TextBlock}">
       <Style.Triggers>
          <DataTrigger Value="True">
             <DataTrigger.Binding>
                 <MultiBinding Converter="{StaticResource ItemExistInRangeConverter}">
                     <Binding Path="Text" RelativeSource="{RelativeSource Self}"/>
                     <Binding Source="{StaticResource MinimumValue}"/>
                     <Binding Source="{StaticResource MaximumValue}"/>
                 </MultiBinding>
             </DataTrigger.Binding>
             <Setter Property="Background" Value="LightGreen" />
          </DataTrigger>
        </Style.Triggers>
    </Style>
</DataGrid.Resources>

确保在根级别添加相应的命名空间:

xmlns:local="clr-namespace:NamespaceOfConverter"
// Replace NamespaceOfConverter with namespace where converter resides.
xmlns:sys="clr-namespace:System;assembly=mscorlib"

更新(如果想在数字数组中查找项目)

假设您的数字属性数组存在于代码后面或视图模型类中。

首先你需要set ItemsSource of DataGrid to DataTable 而不是DataContext。

第二,set DataContext of DataGrid to this from code behind or an instance of viewModel class

另外,要刷新 GUI,您的类应该实现 INotifyPropertyChanged 接口。

现在,假设你有一个属性说:

public int[] RangeNumbers { get; set; }

默认情况下,它将包含您要突出显示的数字列表。

XAML 将如下所示:

<DataGrid.Resources>
    <local:ItemExistInRangeConverter x:Key="ItemExistInRangeConverter"/>
       <Style x:Key="BackgroundColourStyle" TargetType="{x:Type TextBlock}">
           <Style.Triggers>
              <DataTrigger Value="True">
                 <DataTrigger.Binding>
                    <MultiBinding Converter="{StaticResource ItemExistInRangeConverter}">
                       <Binding Path="Text" RelativeSource="{RelativeSource Self}"/>
                       <Binding Path="DataContext.RangeNumbers" 
                                RelativeSource="{RelativeSource FindAncestor,
                                                  AncestorType=DataGrid}"/>
                    </MultiBinding>
                 </DataTrigger.Binding>
               <Setter Property="Background" Value="LightGreen" />
            </DataTrigger>
        </Style.Triggers>
    </Style>
</DataGrid.Resources>

转换器代码:

public class ItemExistInRangeConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType,
                          object parameter, CultureInfo culture)
    {
        bool itemsExistInRange = false;
        if (values.Length == 2)
        {
            int outputValue = int.MinValue;
            int[] rangeNumbers = (int[])values[1];
            if (rangeNumbers != null && 
                Int32.TryParse(values[0].ToString(), out outputValue))
            {
                itemsExistInRange = rangeNumbers.Contains(outputValue);
            }
        }
        return itemsExistInRange;
     }

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

现在,项目将根据RangeNumbers 中的初始数字突出显示。但是假设您之后更新数组,您需要引发属性更改事件,以便 GUI 刷新如下:

RangeNumbers = new int[] { 23, 45, 47, 69 };
OnPropertyChanged("RangeNumbers");

【讨论】:

  • xaml sn-p 是替换我的数据网格还是在它下面?
  • 用我的 DataGrid.Resources 的 xaml sn-p 替换您的 DataGrid.Resources 部分。
  • 所以这是为范围之间的值做的,但是如果一个数字在数组中或类似的问题中呢?
  • 您可以将Range as one of the bindings 传递给转换器并修改Converter 代码以签入已传递的数组。如果您需要帮助,请告诉我。
  • 请检查更新后的答案,看看是否适合您。
【解决方案2】:

这是一个示例,如何通过使用 MVVM 模式(如果编辑/更改任何数字或突出显示更改的标准也可以)和带有 MultiBinding 的样式设置器来完成此操作:

MainWindowModel.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;

namespace WpfApplication
{
    class MainWindowModel : INotifyPropertyChanged
    {
        private DataTable numbersTable;
        private Predicate<int> highlightCriteria;

        public event PropertyChangedEventHandler PropertyChanged;

        public DataTable NumbersTable
        {
            get { return this.numbersTable; }
            set { this.SetValue(ref this.numbersTable, value, "NumbersTable"); }
        }

        public Predicate<int> HighlightCriteria
        {
            get { return this.highlightCriteria; }
            set { this.SetValue(ref this.highlightCriteria, value, "HighlightCriteria"); }
        }

        public MainWindowModel()
        {
            this.NumbersTable = new DataTable("Numbers")
            {
                Columns = 
                {
                    { "Number", typeof(int) }
                },
            };

            for (int i = 1; i < 91; i++)
                this.NumbersTable.Rows.Add(i);

            // By default only numbers larger than 10 and lower than 50 will be highlighted.
            this.HighlightCriteria = num => num > 10 && num < 50;
        }

        protected void SetValue<T>(ref T field, T value, string propertyName)
        {
            if (!EqualityComparer<T>.Default.Equals(field, value))
            {
                field = value;
                this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
            }
        }

        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            var handler = this.PropertyChanged;
            if (handler != null)
                handler(this, e);
        }
    }
}

NumberTextToBackgroundConverter.cs

using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;

namespace WpfApplication
{
    class NumberTextToBackgroundConverter : IMultiValueConverter
    {
        public static readonly IMultiValueConverter Instance = new NumberTextToBackgroundConverter();

        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            int number;
            var numberText = values[0] as string;
            if (!string.IsNullOrEmpty(numberText) && int.TryParse(numberText, NumberStyles.Integer, CultureInfo.InvariantCulture, out number))
            {
                var highlightCriteria = values[1] as Predicate<int>;
                if (highlightCriteria != null && highlightCriteria(number))
                    return Brushes.LightGreen;
            }

            return Brushes.Transparent;
        }

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

MainWindow.xaml

<Window x:Class="WpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication">
    <Window.DataContext>
        <local:MainWindowModel/>
    </Window.DataContext>
    <DataGrid ItemsSource="{Binding NumbersTable}">
        <DataGrid.Resources>
            <Style TargetType="DataGridCell">
                <Style.Triggers>
                    <Trigger Property="IsSelected" Value="False">
                        <Setter Property="Background">
                            <Setter.Value>
                                <MultiBinding Converter="{x:Static local:NumberTextToBackgroundConverter.Instance}">
                                    <Binding RelativeSource="{RelativeSource Self}" Path="Content.Text"/>
                                    <Binding RelativeSource="{RelativeSource AncestorType=DataGrid}" Path="DataContext.HighlightCriteria"/>
                                </MultiBinding>
                            </Setter.Value>
                        </Setter>
                    </Trigger>
                </Style.Triggers>
            </Style>
        </DataGrid.Resources>
    </DataGrid>
</Window>

【讨论】:

  • 您好,感谢您提供详细的代码 sn-ps,看起来不错,问题是我不了解您所做的大部分更改。它似乎完全改变了我的代码,因为也许你建议这是一种更好的方法?
【解决方案3】:

您必须使用条件转换器。见this article

【讨论】:

    【解决方案4】:

    一种做法是这样的:

    假设您将行绑定到一个名为 Items 的属性,该属性是一个列表。您应该像建议的那样创建一个类似于“IsHighlighted”的属性,并且您的代码应该根据用户输入将其设置为 true 或 false。

    然后,要真正突出显示该行,您应该在 DataGrid 中附加一个 LoadingRow 事件,例如:

    MyDataGrid.LoadingRow
      += new EventHandler<System.Windows.Controls.DataGridRowEventArgs>(myMethod);
    

    然后在 myMethod 中执行以下操作:

    void myMethod(object sender, System.Windows.Controls.DataGridRowEventArgs e)
    {
            RowClass item = (RowClass)e.Row.Item;
            if (item.IsHighlighted)
            {
                e.Row.Background = new SolidColorBrush(Colors.Green);
            }
            else
            {
                e.Row.Background = new SolidColorBrush(Colors.White);
            }
    }
    

    【讨论】:

      【解决方案5】:

      我会加入这个选项,并且可能会更简单...您已经有一个包含第 1-90 行的列的数据表。为什么不将另一列作为“ShowColor”列添加到数据表中。对于所有行上的任何条件,对列表进行一次爆炸,并将背景颜色绑定到“ShowColor”的背景颜色。然后,不需要特殊的“转换器”。

      另外,您指的是要为许多单元格着色,这点有点含糊。您的意思是在可能暴露 10 列的单行上,您希望第 1 行、第 1、2、5、8、10 列中的单元格显示为绿色(不太可能)?或者...对于符合条件的第 1 行,您希望该行的所有列都为绿色背景。会有两个完全不同的答案。

      【讨论】:

        【解决方案6】:

        我不是真正的 WPF 程序员,但下面的示例应该很容易转换,因为这些概念与 Windows 窗体相同,可以完成您需要完成的工作。以下示例在启动时完成以下操作:

        1. 创建一个名为m_dataSet 的类级别DataSet
        2. 将名为Numbers 的两列DataTable 添加到DataSet 并加载样本数据。
          • 这些列被命名为ValueActive
          • 数据类型分别为intbool
        3. Numbers 表分配为DataGridView 控件的DataSource。这会导致控件自动添加Numbers 表中的两列。所以之后,dataGridView1.Columns["Active"].Visible 属性的值被设置为 false。这让Numbers 表绑定到DataGridView 控件,而不会向用户显示隐藏列。显示的列数由您决定。
        4. 为以下内容创建事件处理程序:
          • dataGridView1_CellClick - 这只是提供了一种简单的方法来选择随机单元格并证明单元格BackColor 选择更改。这是数据源中的“活动”列在真假之间切换的地方。
          • dataGridView1_CellFormatting - 每次单元格需要格式化以显示时,都会抛出此问题(无论如何在 Windows 窗体中)。通过处理此事件来分配Cell.BackColor,我们不必遍历每一行来刷新颜色,因为Windows 已经在为任何无效的单元格执行此操作。在绘制单元格之前立即更新BackColor。当手动调用 Invalidate 或发生非您引起的意外重绘事件时,它同样有效。
          • button1_Click - 最后,它展示了如何清除DataGridView 控件中的所有选定项。

            public Form1() {
                InitializeComponent();
                LoadGridData();
            }
        
            private DataSet m_dataSet = new DataSet("myDS");
        
            private void LoadGridData() {
        
                //--------------------------------------------------
                // Build Numbers Table
                //--------------------------------------------------
                DataTable numbersTable = new DataTable("Numbers");
        
                numbersTable.Columns.Add("Value", typeof(Int32));
                numbersTable.Columns.Add(new DataColumn(
                    "Active", 
                    typeof(bool)) { 
                    DefaultValue = false 
                });
        
                for (int i = 1; i < 91; i++) {
                    numbersTable.Rows.Add(i);
                }
        
                m_dataSet.Tables.Add(numbersTable);
        
                dataGridView1.DataSource = m_dataSet.Tables["Numbers"];
                dataGridView1.Columns["Active"].Visible = false;
            }
        
            private void button1_Click(object sender, EventArgs e) {
                // Reset all Active settings to false.
                foreach (DataRow row in m_dataSet.Tables["Numbers"].Rows) {
                    row["Active"] = false;
                }
        
                dataGridView1.Invalidate();
            }
        
            private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e) {
        
                if (e.RowIndex > -1 && e.ColumnIndex == 0) {
                    int cellValue = (int)dataGridView1.Rows[e.RowIndex].Cells[0].Value;
        
                    //---------------------------------------------------
                    // Create a query that selects any rows that have the 
                    // cellValue in the "Value" field. This will only return 
                    // one row as long as long as you Numbers table stays
                    // unique. You have to query for the number because 
                    // e.RowIndex returns the wrong row when sorted descending.
                    //---------------------------------------------------
                    var query = 
                        m_dataSet.Tables["Numbers"].AsEnumerable()
                        .Where(row => row.Field<int>("Value") == cellValue);
        
                    // Update Active field
                    foreach(var row in query)
                        row["Active"] = (bool)row["Active"] == false;
                }
        
                // Take focus off the control to avoid dark blue 
                // selection color. -- could also set default backcolor
                // to forecolor to create the obvious selection immediately
                //  when a cell gets clicked.
                dataGridView1.ClearSelection();
        
            }
        
            private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) {
                DataRowView drv = (DataRowView)dataGridView1.Rows[0].DataBoundItem;
        
                //---------------------------------------------------
                // Set the BackColor of the cell based on the underlying 
                // data's Active setting.
                //---------------------------------------------------
                if (e.RowIndex < m_dataSet.Tables["Numbers"].Rows.Count) {
                    if ((bool)drv.DataView[e.RowIndex]["Active"] == true)
                        e.CellStyle.BackColor = Color.PowderBlue;
                    else
                        e.CellStyle.BackColor = SystemColors.Window;
                }
            }
        

        这是输出在 Windows 窗体中的样子。我希望这个答案可以帮助您解决问题。保重。

        此外,根据我的阅读,您可以在 WPF 应用程序中托管 Windows 窗体控件。 DataGridView 具有DataGrid 控件的显着性能优势。尤其是对于大型数据集。

        【讨论】:

          【解决方案7】:

          如果您只使用DataTrigger,这应该很简单。使用IValueConverter 根据行返回真/假值。您可以在转换器中对包含数字的列表/范围进行硬编码,也可以将它们作为参数从 XAML 传入:

          public class HighlightConverter : IValueConverter
          {
              private int[] _highlightedNumbers = { 1, 5, 12, 15 };
          
              public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
              {
                  if (value as int? == null)
                      return false;
          
                  // get the highlighted numbers, either hard-coded locally, or passed in as the ConverterParameter
                  int[] highlightedNumbers;
                  var numbersCsv = parameter as string;
                  if (!string.IsNullOrEmpty(numbersCsv))
                      highlightedNumbers = numbersCsv.Split(',').Select(item => int.Parse(item.Trim())).ToArray();
                  else
                      highlightedNumbers = _highlightedNumbers;
          
                  return highlightedNumbers.Contains((int)value) ? true : false;
              }
          
              public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
              {
                  throw new NotImplementedException();
              }
          }
          

          现在只需根据行值应用DataTrigger,使用此转换器检查突出显示条件:

          <DataGrid.Resources>
               <my:HighlightConverter x:Key="my:HighlightConverter" />
               <Style x:Key="BackgroundColourStyle" TargetType="{x:Type TextBlock}">
                   <Style.Triggers>
                       <DataTrigger Binding="{Binding Converter={StaticResource my:HighlightConverter},ConverterParameter='1,5,12,15'}" Value="True">
                           <Setter Property="Background" Value="LightGreen" />
                       </DataTrigger>
                   </Style.Triggers>
               </Style>
          </DataGrid.Resources>
          

          编辑(替代)

          如果您在编译时知道突出显示的数字列表,上述方法将起作用。或者,如果列表基于另一个运行时值,那么您将需要一个MultiBinding。思路类似,但这次转换器将应用两个值:允许列表和行值本身:

          public class HighlightConverter : IValueConverter, IMultiValueConverter
          {
              public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
              {
                  var includedNumbers = values[0] as int[];
                  if (includedNumbers == null || values[1] as int? == null)
                      return false;
                  return includedNumbers.Contains((int)values[1]);
              }
          
              public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
              {
                  throw new NotImplementedException();
              }
          }
          

          现在您只需绑定到包含突出显示的数字列表的属性。假设您当前的 Window 的 DataContext 是一个具有 int[] HighlightedNumbers 属性的视图模型。然后,您可以使用 RelativeSource 绑定。结果看起来像这样:

          <DataGrid.Resources>
              <converter:HighlightConverter x:Key="HighlightConverter" />
              <Style x:Key="BackgroundColourStyle" TargetType="{x:Type TextBlock}">
                  <Style.Triggers>
                      <DataTrigger Value="True">
                          <DataTrigger.Binding>
                              <MultiBinding Converter="{StaticResource HighlightConverter}">
                                  <Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="DataContext.HighlightedNumbers" />
                                  <Binding />
                              </MultiBinding>
                          </DataTrigger.Binding>
                          <Setter Property="Background" Value="LightGreen" />
                      </DataTrigger>
                  </Style.Triggers>
              </Style>
          </DataGrid.Resources>
          

          【讨论】:

          • 您好,感谢朋友的回答,不胜感激。我现在正在尝试这个,希望明天能给你反馈。谢谢。
          • 我最初看到的唯一问题是我将在 HighlightConverter 类之外设置“includedNumbers”。
          • @RyanMurphy 它是应用程序生命周期的常数值吗?如果是这样,那么也许在应用程序启动时设置它?否则,如果它发生变化,您可能需要多重绑定。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-10-11
          • 2021-12-30
          • 1970-01-01
          • 2020-02-19
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多