Change WPF DataGrid column cell background color based on values

纵然是瞬间 提交于 2021-02-11 13:28:54

问题


I am trying to fill cells with color in certain column. Column name is "NRO" and I want to fill cells staring with 2 yellow color and cells starting with 3 blue color. I went through answer given here: Change DataGrid cell colour based on values

Also tried several other approaches but can't get any of them work. I also don't understand how to implement any of them in my setup. They all seems to have <DataGridTextColumn Binding="{Binding Name}">. What it should be in my case?

Here is my XAML:

<Window x:Class="DB_inspector_FilterTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="DB database inspector v.0.0.01" Height="600" Width="1000" Icon="logo_icon-small.jpg" Background="White">
    <Grid Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <DataGrid x:Name="DataGrid1" Margin="0,103,0,0" Background="White" BorderBrush="#FF38853F"/>
        <TextBox x:Name="NameSearch" HorizontalAlignment="Left" Height="20" Margin="22,41,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="437" TextChanged="NameSearch_TextChanged"/>
        <Button Content="Load" Margin="640,41,0,0" Click="Button_Click_1" BorderBrush="{x:Null}" Foreground="White" Background="#FF55B432" Width="66" Height="29" HorizontalAlignment="Left" VerticalAlignment="Top"/>
        <ProgressBar x:Name="ProgressBar" HorizontalAlignment="Left" Height="11" VerticalAlignment="Top" Width="992" BorderBrush="{x:Null}" Background="{x:Null}"/>
        <Label Content="Customer name" HorizontalAlignment="Left" Height="25" Margin="22,11,0,0" VerticalAlignment="Top" Width="154"/>
        <CheckBox x:Name="ActiveCustomer" Content="Active" HorizontalAlignment="Left" Height="24" Margin="486,63,0,0" VerticalAlignment="Top" Width="86" Click="ActiveCustomer_Click_1"/>
        <CheckBox x:Name="Only" Content="Leave only good" HorizontalAlignment="Left" Height="17" Margin="486,41,0,0" VerticalAlignment="Top" Width="115" Click="CheckBox_Click"/>
        <Image Margin="856,0,22,520" VerticalAlignment="Bottom" Source="logo_small.jpg" Height="27"/>

    </Grid>
</Window>

ADDITION:

If anybody have time, while I will be trying to figure it out myself, give me some hints how to proceed with my application, here is my full code:

using System.Data.Odbc;
using System.Windows;
using System.Data;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System;

namespace DB_inspector_FilterTest
{

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

        }

        private async void Button_Click_1(object sender, RoutedEventArgs e)
        {
            try
            {
                ProgressBar.IsIndeterminate = true;

                DataGrid1.ItemsSource = await GetDataAsync();

                ProgressBar.IsIndeterminate = false;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private Task<DataView> GetDataAsync()
        {
            return Task.Run(() =>
            {

                string connectionStringDE = "Driver={Pervasive ODBC Client Interface};ServerName=DB123;dbq=@DBFSSE;Uid=ADMIN;Pwd=123;";

                string queryStringDE = "select NRO,NAME,NAMEA,NAMEB,ADDRESS,POSTA,POSTN,POSTB,CORPORATION,COUNTRY,ID,ACTIVE from COMPANY";

                string connectionStringFR = "Driver={Pervasive ODBC Client Interface};ServerName=DB123;dbq=@DBFSFI;Uid=ADMIN;Pwd=123;";

                string queryStringFR = "select NRO,NAME,NAMEA,NAMEB,ADDRESS,POSTA,POSTN,POSTB,CORPORATION,COUNTRY,ID,ACTIVE from COMPANY";

                DataTable dataTable = new DataTable("COMPANY");
                // using-statement will cleanly close and dispose unmanaged resources i.e. IDisposable instances
                using (OdbcConnection dbConnectionDE = new OdbcConnection(connectionStringDE))
                {
                    dbConnectionDE.Open();
                    OdbcDataAdapter dadapterDE = new OdbcDataAdapter();
                    dadapterDE.SelectCommand = new OdbcCommand(queryStringDE, dbConnectionDE);

                    dadapterDE.Fill(dataTable);

                }
                using (OdbcConnection dbConnectionFR = new OdbcConnection(connectionStringFR))
                {
                    dbConnectionFR.Open();
                    OdbcDataAdapter dadapterFR = new OdbcDataAdapter();
                    dadapterFR.SelectCommand = new OdbcCommand(queryStringFR, dbConnectionFR);

                    var newTable = new DataTable("COMPANY");
                    dadapterFR.Fill(newTable);

                    dataTable.Merge(newTable);

                }

                return dataTable.DefaultView;

            });
        }

        private Dictionary<string, string> _conditions = new Dictionary<string, string>();

        private void UpdateFilter()
        {
            try
            {
                var activeConditions = _conditions.Where(c => c.Value != null).Select(c => "(" + c.Value + ")");
                DataView dv = DataGrid1.ItemsSource as DataView;
                dv.RowFilter = string.Join(" AND ", activeConditions);
            }
            catch (Exception)
            {
                //MessageBox.Show(ex.Message);
            }
        }

        private void NameSearch_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
        {
            string filter = NameSearch.Text;
            if (string.IsNullOrEmpty(filter))
                _conditions["name"] = null;
            else
                _conditions["name"] = string.Format("NAME Like '%{0}%'", filter);
            UpdateFilter();
        }

        private void ActiveCustomer_Click_1(object sender, RoutedEventArgs e)
        {
            if (ActiveCustomer.IsChecked == true)
            {
                _conditions["active"] = string.Format("ACTIVE Like '%{0}%'", "1");
                UpdateFilter();
            }
            else
            {
                _conditions["active"] = null;
                UpdateFilter();
            }
        }

        private void CheckBox_Click(object sender, RoutedEventArgs e)
        {
            if (OnlyFIandSE.IsChecked == true)
            {
                _conditions["onlyfrandde"] = string.Format("NRO Like '2%' OR NRO Like '3%'");
                UpdateFilter();
            }
            else
            {
                _conditions["onlyfrandde"] = null;
                UpdateFilter();
            }
        }
    }
}

Things I don't understand at least now: How in my case I should setup ItemSource for binding? Should I import databases to List first and then Bind to the list?


ATTEMPT 3:

Here is my latest MVVM attempt.

C#:

using System;
using System.ComponentModel;
using System.Data;
using System.Data.Odbc;
using System.Windows;
using System.Windows.Input;

namespace DB_inspector
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {

    }

    public class ViewModel : INotifyPropertyChanged
    {
        public ICommand myCommand => new RelayCommand(obj =>
        {
            try
            {
                string connectionStringDE = "Driver={Pervasive ODBC Client Interface};ServerName=DB123;dbq=@DBFSSE;Uid=ADMIN;Pwd=123;";

                string queryStringDE = "select NRO,NAME,NAMEA,NAMEB,ADDRESS,POSTA,POSTN,POSTB,CORPORATION,COUNTRY,ID,ACTIVE from COMPANY";

                string connectionStringFR = "Driver={Pervasive ODBC Client Interface};ServerName=DB123;dbq=@DBFSFI;Uid=ADMIN;Pwd=123;";

                string queryStringFR = "select NRO,NAME,NAMEA,NAMEB,ADDRESS,POSTA,POSTN,POSTB,CORPORATION,COUNTRY,ID,ACTIVE from COMPANY";

                DataTable dataTable = new DataTable("COMPANY");
                // using-statement will cleanly close and dispose unmanaged resources i.e. IDisposable instances
                using (OdbcConnection dbConnectionDE = new OdbcConnection(connectionStringDE))
                {
                    dbConnectionDE.Open();
                    OdbcDataAdapter dadapterDE = new OdbcDataAdapter();
                    dadapterDE.SelectCommand = new OdbcCommand(queryStringDE, dbConnectionDE);

                    dadapterDE.Fill(dataTable);

                }
                using (OdbcConnection dbConnectionFR = new OdbcConnection(connectionStringFR))
                {
                    dbConnectionFR.Open();
                    OdbcDataAdapter dadapterFR = new OdbcDataAdapter();
                    dadapterFR.SelectCommand = new OdbcCommand(queryStringFR, dbConnectionFR);

                    var newTable = new DataTable("COMPANY");
                    dadapterFR.Fill(newTable);

                    dataTable.Merge(newTable);

                }

                _ = dataTable.DefaultView;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        });

        private bool _allowUIChanges = true;
        public bool AllowUIChanges
        {
            get => _allowUIChanges;
            set
            {
                _allowUIChanges = value;
                OnPropertyChanged(nameof(AllowUIChanges));
                OnPropertyChanged(nameof(IsReadOnlyDataGrid));
            }
        }

        private void OnPropertyChanged(string v)
        {
            throw new NotImplementedException();
        }

        public bool IsReadOnlyDataGrid
        {
            get => !_allowUIChanges;
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

    public class RelayCommand : ICommand
    {
        private readonly Action<object> _execute;
        private readonly Func<object, bool> _canExecute;

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
        public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
        {
            _execute = execute;
            _canExecute = canExecute;
        }
        public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);

        public void Execute(object parameter) => _execute(parameter);
    }
}

XAML:

<Window x:Class="DB_inspector.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="DB database inspector" Height="595.404" Width="1005.571">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <DataGrid IsReadOnly="{Binding IsReadOnlyDataGrid}" AutoGenerateColumns="False" ItemsSource="{Binding MyCollection}" Width="998" Margin="0,98,0,0" >
        </DataGrid>
        <Image Height="41" Margin="0,21,10,0" Width="141" Source="logo_small.jpg" HorizontalAlignment="Right" VerticalAlignment="Top"/>
        <Button Content="Go" Command="{Binding myCommand}" Width="80" Height="30" Margin="48,42,870,492"/>

    </Grid>
</Window>

Any suggestions what is still wrong here? No errors, but button does not process anything.


回答1:


I suggest IValueConverter or IMultiValueConverter binding for Cell's Background property.

I'm not sure how it works with autogenerated columns set but with manual it looks like this. I'm providing here not a working copy but a markup example.

XAML

<Window.Resources>
    <local:MyCellBackgroundConverter x:Key="myCellBackgroundConverter"/>
</Window.Resources>
<Window.DataContext>
    <local:ViewModel/>
</Window.DataContext>
<Grid>
<!-- some your markup here -->
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding MyCollection}">
    <DataGridTextColumn Header="Column1" Binding="{Binding Value1}">
        <DataGridTextColumn.CellStyle>
            <Style TargetType="DataGridCell">
                <Setter Property="Background">
                    <Setter.Value>
                        <MultiBinding Converter="{StaticResource myCellBackgroundConverter}">
                            <Binding Path="Value1"/>
                            <Binding Path="Value2"/>
                        </MultiBinding>
                    </Setter.Value>
                </Setter>
            </Style>
        </DataGridTextColumn.CellStyle>
    </DataGridTextColumn>
    <DataGridTextColumn Header="Column2" Binding="{Binding Value2}"/>
</DataGrid>
</Grid>

The ViewModel Class

using System.Collections.ObjectModel;
using System.ComponentModel;

// ...

public class ViewModel : INotifyPropertyChanged
{
    private ObservableCollection<MyItem> _myCollection = new ObservableCollection<MyItem>();
    public ObservableCollection<MyItem> MyCollection
    {
        get => _myCollection;
        set
        {
            _myCollection = value;
            OnPropertyChanged(nameof(MyCollection));
        }
    }

    public ViewModel()
         // you may load or add the data to MyCollection here
    {

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

Item

using System.ComponentModel;

// ...

public class MyItem : INotifyPropertyChanged
{
    private string _value1 = string.Empty;
    private string _value2 = string.Empty;

    public string Value1
    {
        get => _value1;
        set { _value1 = value; OnPropertyChanged(nameof(Value1)); }
    }

    public string Value2
    {
        get => _value2;
        set { _value2 = value; OnPropertyChanged(nameof(Value2)); }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

And finally the Converter

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

//...

public class MyCellBackgroundConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values[0] is string value1 && values[1] is string value2)
        {
            if (value1.Length > 0)
            {
                return Brushes.Green;
            }
            else
            if (value2.Length > 0)
            {
                return Brushes.Yellow;
            }
            else
                return Brushes.Red;
        }
        else return Brushes.White;
    }
    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => null;
}

In alternative way you may use Style.DataTriggers directly in XAML.

For additional information about bindings and properties look for MVVM programming pattern. In short you're not need x:Name anymore because in MVVM pattern you interacting only with ViewModel class data instances and can't interact with contols directly there (and that's fine). Meanwhile Contols automatically sync with data binded to them. Calling OnPropertyChanged("PropertyName") here simply cause the GUI refresh.

In relation to markup of your XAML example, try wrapping the Control groups in StackPanel and learn about it. It will save your time spent fighting with margins. Simply set few colums and/or rows in Window's Grid and place StackPanels there assigning Grid.Column and Grid.Row to them.


ADDITION:

How in my case I should setup ItemSource for binding? Should I import databases to List first and then Bind to the list?

ObservableCollection<> is same as List<> and you may use it in the same way. The difference that first one implements CollectionChanged event that notifies DataGrid if any items was added or removed from the collection.

Your Button.Click event handler contains redundant async/await declaration.

Let's move forward and see how it can be done with MVVM.

XAML

<Button Content="Go" Command="{Binding myCommand}"/>

Command must implement ICommand interface. You have to do 2 things for proper implementation:

1) Add RelayCommand separate Class implementing ICommand interface

public class RelayCommand : ICommand
{
    private readonly Action<object> _execute;
    private readonly Func<object, bool> _canExecute;

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }
    public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }
    public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);

    public void Execute(object parameter) => _execute(parameter);
}

2) Add the Command instance to your ViewModel class

public ICommand myCommand => new RelayCommand(obj =>
{
    // do the same here as in Button.Click above
});

Next, you may need some blocking UI thing that will prevent user from any actions while data is loading.

ViewModel

private bool _allowUIChanges = true;
public bool AllowUIChanges
{
    get => _allowUIChanges;
    set
    {
        _allowUIChanges = value;
        OnPropertyChanged(nameof(AllowUIChanges));
        OnPropertyChanged(nameof(IsReadOnlyDataGrid));
    }
}
public bool IsReadOnlyDataGrid
{
    get => !_allowUIChanges;
}

Finally bind your Control properties to it

XAML

<Button Content="Go" Command="{Binding myCommand}" Enabled="{Binding AllowUIChanges}"/>
<DataGrid IsReadOnly="{Binding IsReadOnlyDataGrid}" AutoGenerateColumns="False" ItemsSource="{Binding MyCollection}">

Then set AllowUIChanges to false while data is loading.



来源:https://stackoverflow.com/questions/60286718/change-wpf-datagrid-column-cell-background-color-based-on-values

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!