Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Highlight cells if found

Tags:

c#

wpf

datagrid

I have a datagrid that outputs 90 rows after being bound to a datatable. The rows values are the 1-90 ascending.

What I want to achieve:

If the number is found in an array/range/list of numbers then highlight it green.

I have managed to get it to highlight the cell based on 1 value, but I want to highlight several cells if they are found in the range.

<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>

you can see I have a trigger property of the value 1. This works fine, but as above, how do I change this so if a cell is in the range set in c# in backend then highlight it green.

binding to datatable:

calledGrid.DataContext = calledNumbers.DefaultView;

The actual making of the datatable:

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);

Thanks for any help guys. If you need any more info or I have been to vague or unclear about anything please do ask and i will do my best to respond ASAP. Also please excuse any ignorance I may have, I am very new to wpf. I will do my best.

like image 377
RSM Avatar asked Dec 18 '13 14:12

RSM


People also ask

How do I highlight another cell if the condition is met?

On the Home tab, click Conditional Formatting, point to Highlight Cells Rules, and then click Text that Contains. In the box next to containing, type the text that you want to highlight, and then click OK.

Can I use IF function to highlight cells?

While working with a large worksheet in Microsoft Excel, we need to highlight cells using the If statement in Excel. You can use various ways to highlight cells based on their value in Excel. Conditional Formatting is one of the tools to highlight cells. You also can use the ISERROR and VLOOKUP functions.


2 Answers

You can achieve that using IMultiValueConverter which will return true if Text lies within the range otherwise return false. Based on the value returned by converter, change background color.

Pass three parameters to converter:

  1. Actual Text value.
  2. Minimum value.
  3. Maximum value.

Converter

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>

Make sure you add corresponding namespace at root level:

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

UPDATE (In case want to find item in an array of numbers)

Assuming your array of numbers property exist in code behind or view model class.

First of all you need to set ItemsSource of DataGrid to DataTable instead of DataContext.

Second, set DataContext of DataGrid to this from code behind or an instance of viewModel class.

Also, to refresh GUI your class should implement INotifyPropertyChanged interface.

Now, assume you have a property say:

public int[] RangeNumbers { get; set; }

By default it will contain list of numbers which you want to highlight.

XAML will look like this:

<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>

Converter code:

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();
     }
}

Now, items will be highlighted based on initial numbers in RangeNumbers. But suppose you update array afterwards, you need to raise property changed event so that GUI gets refreshed something like this:

RangeNumbers = new int[] { 23, 45, 47, 69 };
OnPropertyChanged("RangeNumbers");
like image 53
Rohit Vats Avatar answered Oct 06 '22 01:10

Rohit Vats


Here is an example how to accomplish this by using MVVM pattern (which would also work if any number is edited / changed or criteria for highlighting changes) and Style Setter with 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>
like image 27
Stipo Avatar answered Oct 06 '22 00:10

Stipo