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