WPF multiple-condition binding filtering - wpf

I am new to WPF and all this magical binding and datatriggers stuff, so i ask you for a little help.
I have a simple wpf app shown on picture below.
I want my datagrid contents to reflect conditions and date filter. I already figured out how to bind datagrid rows visibility depending on event codes and checkboxes (start, stop, error). But i cant't figure out how to implement date filtering. All i want is: when "Filter by date" checkbox is checked, in my datagrid only those rows remain visible, which have date in "server time" field (i guess i need to parse it somehow from datetime) equal to selected date combobox.
Can i achieve that using xaml only? Can enyone help me to do that?
Here is xaml for my datagrid:
<DataGrid
Grid.Row="1"
Margin="5"
AutoGenerateColumns="False"
IsReadOnly="True"
ItemsSource="{Binding LogEntries}"
Style="{DynamicResource Helvetica}">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding event_code}" Value="1">
<Setter Property="Background" Value="LightGreen" />
<Setter Property="Visibility" Value="{Binding IsChecked, ElementName=StartShowChecked, Converter={StaticResource BooleanToVisibilityConverter}}" />
</DataTrigger>
<DataTrigger Binding="{Binding event_code}" Value="2">
<Setter Property="Background" Value="LightGray" />
<Setter Property="Visibility" Value="{Binding IsChecked, ElementName=StopShowChecked, Converter={StaticResource BooleanToVisibilityConverter}}" />
</DataTrigger>
<DataTrigger Binding="{Binding event_code}" Value="3">
<Setter Property="Background" Value="#FFEA816F" />
<Setter Property="Visibility" Value="{Binding IsChecked, ElementName=ErrorShowChecked, Converter={StaticResource BooleanToVisibilityConverter}}" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding server_datetime, StringFormat=\{0:dd.MM.yy HH:mm:ss\}}" Header="Server time" />
<DataGridTextColumn Binding="{Binding user_datetime, StringFormat=\{0:dd.MM.yy HH:mm:ss\}}" Header="Client time" />
<DataGridTextColumn
Width="*"
Binding="{Binding log_entry}"
Header="Entry" />
</DataGrid.Columns>

Can i achieve that using xaml only?
No you can't because XAML is a markup language and nothing else.
What you shold do is to bind the SelectedItem of the date ComboBox to a DateTime property of your view model and bind the IsChecked property of the "filter by" CheckBox to a bool property of your view model and filter the LogEntries source collection when the IsChecked source property is set, e.g.:
public class ViewModel : INotifyPropertyChanged
{
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
OnPropertyChanged();
//filter collection:
LogEntries = allLogEntries.Where(x => x.ServerTime == SelectedDate).ToList();
}
}
private List<LogEntry> _logEntries;
public List<LogEntry LogEntries
{
get { return _logEntries; }
set
{
_logEntries = value;
OnPropertyChanged();
}
}
//...
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

I doubt there is a way to do it in XAML only, since filtering requires you to specify how to filter (for example by creating a predicate).
What I recommend is getting an ICollectionView from your ItemsSource (which I assume is an ObservableCollection) and set it's Filter property.
Check out this answer for more details : Filter a DataGrid in WPF

Related

Change Data bound WPF Datagrid row background/foreground color depending on the boolean property of an object

I have a Datagrid in WPF application (Using MVVM {Caliburn Micro}) which is bounded to property of type ObservableCollection<Student> where Student class looks like this:
public class Student
{
public int ID { get; set; }
public String FullName { get; set; }
public bool Passed { get; set; }
}
Depending on the fact - student passed the exam or not - I want to change the Background/Foreground of the row of the corresponding student to red (if didn't passed).
Below is shown my DataGrid:
<DataGrid Grid.Column="1"
RowBackground="White"
Visibility="Visible"
Grid.Row="15"
ColumnWidth="auto"
IsReadOnly="False"
AutoGenerateColumns="False"
BorderBrush="{StaticResource GridBorder}"
VerticalScrollBarVisibility="Auto"
HorizontalGridLinesBrush="LightGray"
HorizontalScrollBarVisibility="Disabled"
VerticalGridLinesBrush="LightGray"
Name="Students"
CanUserAddRows="True"
BorderThickness="0.8"
SelectionUnit="FullRow"
cal:Message.Attach="[Event MouseDoubleClick] = [Action GetRow($dataContext)]"
SelectionMode="Single" Grid.ColumnSpan="4">
these are the column Definitions:
<DataGridTextColumn Binding="{Binding ID}" Header="PersonalNumber"/>
<DataGridTextColumn Binding="{Binding FullName}" Header="FullName"/>
To solve this, I tried something like this, but doesn't work:
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Passed}" Value="false">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Passed}" Value="true">
<Setter Property="Background" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
What can I do?
Your DataGrid is setting RowBackground="White" which is overriding the RowStyle, remove that setting and the Style will behave as you're expecting it to.

Disable specific DataGridCheckBoxColumn cells via ViewModel

My MVVM project has this ViewModel:
class ListViewModel : ViewModelBase {
public ObservableCollection<ListItemviewModel> Items { ... }
}
class ListItemViewModel : ViewModelBase {
public String Name { ... }
public Boolean IsChecked { ... }
public Boolean IsEnabled { ... }
}
Writing the XAML seemed straightforward:
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridCheckBoxColumn Header="Is checked" Binding="{Binding IsChecked}" />
</DataGrid>
However, how can I get it so when a ListItemViewModel's IsEnabled property is false the DataGridCheckBoxColumn's cell in that row is disabled?
I tried setting IsReadOnly={Binding IsDisabled} (and adding an IsDisabled property to ListItemViewModel, however to no avail) - and I recognise that would disable/enable the whole column, not individual cells.
I also tried these instructions ( How to disable a cell in a DataGrid while binding to an ObservableCollection ):
<DataGridCheckBoxColumn Header="Is checked" Binding="{Binding IsChecked}" />
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled}" Value="False">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridCheckBoxColumn.CellStyle>
However this has no effect and no binding errors are displayed in the Output window.
It turns out the question I linked to ( How to disable a cell in a DataGrid while binding to an ObservableCollection ) was almost-right, but the XAML was somewhat cargo-cultish. The correct XAML is merely:
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
</Style>
</DataGridCheckBoxColumn.CellStyle>
Sorted!

Binding a DataTrigger to the IsChecked property of a checkbox

I believe what I'm trying to do is "simple" enough, so I'm probably just missing something obvious.
In a DataGrid, I am trying to bind a CheckBox so that when it is checked, the Background color of its row will change. Every row has a CheckBox. I am basically implementing my own select-multiple-rows functionality (it's a product requirement, don't ask), and I have everything else working but this visual indication of a selected row.
I've read this question but where I lack my answer is what exactly to put as "BooleanPropertyOnObjectBoundToRow". I've also looked at this question and tried messing with a RelativeSource but with no luck.
I create my grid in my code-behind, but here is my current style used for rows (which has my DataTrigger defined):
<Style x:Key="MyRowStyle" TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked}" Value="True">
<Setter Property="Background" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
Now in my code-behind, I create my DataGridTemplateColumn and use a Factory to create my checkboxes, and here is my Binding-relevant code:
Binding checkBinding = new Binding("IsChecked");
checkBinding.Mode = BindingMode.OneWayToSource;
RelativeSource relativeSource = new RelativeSource();
relativeSource.AncestorType = typeof(DataGridRow);
relativeSource.Mode = RelativeSourceMode.FindAncestor;
checkBinding.RelativeSource = relativeSource;
factory.SetBinding(CheckBox.IsCheckedProperty, checkBinding);
What may be of interest is the fact that I set the ItemsSource of my DataGrid to a DataTable, but my CheckBox column does NOT have a corresponding column in the DataTable. I simply add the template column separately, maybe this lack of underlying storage is affecting this?
In any case if you need any more info, please let me know. Thanks!
Here's an example that works for me using C# classes, not a DataSet.
Xaml
<Page.Resources>
<Style x:Key="RowStyle" TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, UpdateSourceTrigger=PropertyChanged}" Value="True">
<Setter Property="Background" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Page.Resources>
<Page.DataContext>
<Samples:DataGridRowHighlightViewModels/>
</Page.DataContext>
<Grid>
<DataGrid ItemsSource="{Binding Items}" RowStyle="{StaticResource RowStyle}" CanUserAddRows="False" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridCheckBoxColumn Header="Selected" Binding="{Binding IsChecked}"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
C#
public class DataGridRowHighlightViewModels
{
public DataGridRowHighlightViewModels()
{
Items = new List<DataGridRowHighlightViewModel>
{
new DataGridRowHighlightViewModel {Name = "one"},
new DataGridRowHighlightViewModel {Name = "two"},
new DataGridRowHighlightViewModel {Name = "three"},
new DataGridRowHighlightViewModel {Name = "four"},
};
}
public IEnumerable<DataGridRowHighlightViewModel> Items { get; set; }
}
// ViewModelBase and Set() give INotifyPropertyChanged support (from MVVM Light)
public class DataGridRowHighlightViewModel : ViewModelBase
{
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set { Set(()=>IsChecked, ref _isChecked, value); }
}
private string _name;
public string Name
{
get { return _name; }
set { Set(()=>Name, ref _name, value); }
}
}

WPF DataGrid bind data between columns

Lets say I have 2 columns in my data Grid: Column A: Selected, and Column B: Name. The Selected column is a checkbox. And Name column is text field. I want to set the color of the text in 'Name' column as Blue if Column A's check box is checked, and Red otherwise.
Essentially I don't know how to bind data between columns of the datagrid. And sample code/link providing example would be useful.
I haven't used the WPF Toolkit's DataGrid much, but from what I can gather, one method is to use DataGridTemplateColumn's and then set up your DataTriggers based on the binding.
Here is an example that uses DataTriggers to set the style of the Foreground color as well the entire row's background color. Of note, you'll need a boolean Property in your ItemsSource's binding to make this work with this method.
XAML
<Window.Resources>
<Style TargetType="{x:Type tk:DataGridRow}">
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=IsSelected}" Value="True">
<Setter Property="Background" Value="Yellow" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type TextBlock}" x:Key="MyTextBlockStyle">
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=IsSelected}" Value="True">
<Setter Property="Foreground" Value="Blue" />
</DataTrigger>
<DataTrigger
Binding="{Binding Path=IsSelected}" Value="False">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<tk:DataGrid x:Name="MyGrid"
AutoGenerateColumns="False"
ItemsSource="{Binding}">
<tk:DataGrid.Columns>
<tk:DataGridTemplateColumn Header="Selected"
Width="75">
<tk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=IsSelected}"/>
</DataTemplate>
</tk:DataGridTemplateColumn.CellTemplate>
</tk:DataGridTemplateColumn>
<tk:DataGridTemplateColumn Header="Name" Width="100" >
<tk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"
Style="{StaticResource MyTextBlockStyle}" />
</DataTemplate>
</tk:DataGridTemplateColumn.CellTemplate>
</tk:DataGridTemplateColumn>
</tk:DataGrid.Columns>
</tk:DataGrid>
</Grid>
Code Behind
public partial class DataGridDataTrigger : Window
{
public List<Person> People { get; set; }
public DataGridDataTrigger()
{
InitializeComponent();
var names = new List<string> { "Joe", "Bob", "Frank", "Scott", "Mike" };
People = new List<Person>();
names.ForEach( x => People.Add( new Person { Name = x } ) );
People.ForEach( x =>
{
if( x.Name.Contains( "o" ) )
x.IsSelected = true;
} );
MyGrid.DataContext = People;
}
}
public class Person
{
public string Name { get; set; }
public bool IsSelected { get; set; }
}

WPF Datagrid Cell with Validation Error Style

I am trying to change the default style of a DataGridCell (within a WPF Toolkit DataGrid) when there is a validation error. The default is a red border. How can I put my own template?
Thanks.
Try this:
<!-- Cell Style -->
<Style x:Key="CellErrorStyle" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="Background" Value="Yellow"/>
</Trigger>
</Style.Triggers>
</Style>
And use it:
<DataGrid.Columns>
<DataGridTextColumn
ElementStyle="{StaticResource CellErrorStyle}">
</DataGridTextColumn>
</DataGrid.Columns>
There's a nice tutorial from Diederik Krols that does exactly what you're asking for the WPF Toolkit DataGrid.
One kind of solution is below, but first, let me share my findings.
It seems like the validation errors never reach inside the column's ElementStyle or CellStyle. The reason I suspect this is because it reaches and can be used in the column's EditingElementStyle and the datagrid's RowStyle.
For example, you can set the style based on Validation.HasError:
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
or you can set the Validation.ErrorTemplate as well:
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="Red" BorderThickness="3">
<AdornedElementPlaceholder />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>
and both work just fine. Same on the EditingElementStyle. Neither of these really solves the problem: changing the row style obviously doesn't show which cell the error is in, and the editing style is not visible once the text box is defocused.
Unfortunately, for some reason, the same method doesn't work on the ElementStyle or the CellStyle. I'm inclined to believe this is a bug because in this tutorial it says after showing an example of setting a Validation.HasError triggered style on the EditingElementStyle:
You can implement more extensive customization by replacing the
CellStyle used by the column.
The solution
One workaround is not to use a trigger but rather bind the background (or whatever style property you want) of the cell to a new property of the data object. I'll show what I mean.
In this example, there are products, they have a category, which will be displayed in a text column in the datagrid. Here's the XAML:
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Products}">
<DataGrid.Columns>
<!-- other columns -->
<DataGridTextColumn Header="Category">
<DataGridTextColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Background"
Value="{Binding Mode=OneWay, Path=CategoryErrorBackgroundColor}" />
</Style>
</DataGridTextColumn.CellStyle>
<DataGridTextColumn.Binding>
<Binding Path="Category" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
<!-- other columns -->
</DataGrid.Columns>
</DataGrid>
And here's the Product class:
public class Product : INotifyPropertyChanged
{
// ...other fields and properties
private string category;
private SolidColorBrush categoryErrorBackgroundColor;
public string Category
{
get
{
return category;
}
set
{
// validation checks
if (value.Lenght < 5)
{
CategoryErrorBackgroundColor = Brushes.Red;
// Notice that throwing is not even necessary for this solution to work
throw new ArgumentException("Category cannot be shorter than 5 characters.");
}
else
{
CategoryErrorBackgroundColor = Brushes.Transparent;
}
category = value;
}
}
// This is the property I'm binding to the cell's background
// It has to have the appropriate type
public SolidColorBrush CategoryErrorBackgroundColor
{
get
{
return categoryErrorBackgroundColor;
}
set
{
categoryErrorBackgroundColor = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Obviously, the huge downside of this solution is that it requires one (or more if you want more complex styles) style property for every property in the data object, and it needs a lot of manual settings of these properties. But it is kind of a solution nevertheless.

Resources