Binding a DataTrigger to the IsChecked property of a checkbox - wpf

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

Related

WPF multiple-condition binding filtering

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

DataGrid.RowStyle only works on initial binding

I have a DataTrigger applying a style to the Visibility property of a DataGrid row. The DataTrigger is working just fine on the initial binding of the DataGrid (ie - it sets the row visibility to collapsed if FilteredOut is true).
I have a ComboBox that sets the FilteredOut property to true or false for each item in the ObservableCollection AllPartMalfunctions depending upon what the user has selected in the ComboBox.
Here is my problem: after selecting an item in the ComboBox and setting the FilteredOut property for each item, the DataGrid rows do not refresh to be visible or collapsed and everything on the UI looks the same as it did before selecting anything in the ComboBox. What am I missing?
Here is the XAML:
<DataGrid ItemsSource="{Binding AllPartMalfunctions}"
AutoGenerateColumns="False" Width="Auto">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding FilteredOut, Mode=TwoWay}" Value="True">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding FilteredOut, Mode=TwoWay}" Value="False">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<!--removed for brevity-->
</DataGrid.Columns>
</DataGrid>
Here is the ViewModel to which the DataGrid is binding:
public class Malfunctions : ViewModelBase {
public ObservableCollection<Model.PartMalfunction> AllPartMalfunctions {
get;
private set;
}
}
Here is the PartMalfunction Model:
public class PartMalfunction {
private bool _filteredOut = false;
public bool FilteredOut {
get {
return _filteredOut;
}
set {
_filteredOut = value;
}
}
}
Class "PartMalfucntion" needs to implement System.ComponentModel.INotifyPropertyChanged and fire off the PropertyChanged event when FilteredOut's value is changed.
public class PartMalfunction : System.ComponentModel.INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
#endregion
private bool _filteredOut = false;
public bool FilteredOut
{
get {
return _filteredOut;
}
set {
_filteredOut = value;
if (PropertyChanged != null)
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("FilteredOut"));
}
}
}

Content control, ContentTemplateSelector, how to choose a staticresource based on value comming through binding, without datatriggers

In a resource dictionary, I have few Viewboxes, and a datatemplate which has content control in it. I’m using style to switch between the viewboxes like this
<DataTemplate x:Key="foo">
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Viewbox}" Value="SageCashYes">
<Setter Property="Content" Value="{StaticResource SageCashYesViewbox}" />
</DataTrigger>
<DataTrigger Binding="{Binding Viewbox}" Value="SageCashNo">
<Setter Property="Content" Value="{StaticResource SageCashNoViewbox}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
Is there anyway to do something like this
<DataTemplate x:Key="foo">
<ContentControl ContentTemplateSelector="{Binding Viewbox}" >
</DataTemplate>
So I’d like to be able to tell it to choose appropriate Viewbox based on this binding but without these data triggers?
I’m asking it because the view boxes have vectors inside to draw images, and these data trigger will grow huge in no time. So if there is something out there which will make my life easier I’d like to know about it.
EDIT
<ListBox ItemTemplate="{StaticResource Foo}
ItemsSource="{Binding MyList}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
public class MyViewModel
{
public MyViewModel()
{
MyList = new List<MyList>()
{
new MyItem() {BoolValue = false, Heading = "Bank Payment", Viewbox = "SageCashNo"},
new MyItem() {BoolValue = true, Heading = "Cash Payment", Viewbox = "SageCashYes"}
};
}
public List<MyItem> MyList { get; set; }
}
public class MyItem
{
public bool {BoolValue { get; set; }
public string Heading { get; set; }
public string Viewbox { get; set; }
}
Kind Regards
Daniel

WPF customizing cell appearance in DataGrid or GridView using code behind

I am developing an application that needs to present a read only list of data, in this case the current status of various patrol units. I have experimented with both the DataGrid and the GridView for this and have not made a final choice on which to use as of yet. Basically it will come down to whichever one has the fewest roadblocks.
The data being populated to the grid will ultimately come in the form of an flat file that will be parsed and assembled into a DataTable or some form of collection that I will bind to the grid. For testing I am just binding a sample DataSet to the grid.
Based on the value in different cells I need to change the appearance of the cell containing the data. For instance, if any cell in column "Priority" has value less than "5" change background to red or if any cell in column "Type" has value of "FIELD" change background to green.
A particullar conditional formatting will apply to all cells in the defined column only. A particular column may have multiple conditional formatting applied to it.
I would like something that will work with the the standard DataGrid or GridView controls as they are included with WPF. I am using Visual Studio 2010.
A biggest obstacle I have is that the conditional formatting that will be applied is not known at development time. This is something that is configurable by the user. There will be an XML file will all of the conditional formats defined in it.
Any examples I have seen, which don't seem to work for me, use converters with hard coded conditional formatting attributes. I need to do this completely dynamic. I prefer to use code behind if possible.
Any help is greatly appreciated.
Updated
I have developed a solution to my original problem. Here is what I did.
foreach (ColumnEntity column in ColumnList)
{
DataGridTemplateColumn newColumn = CreateDataGridColumn(column, ColumnList.FormatConditionGroups);
newColumn.Header = column.Header;
newColumn.Width = new DataGridLength(column.DisplayWidth);
_dataGrid.Columns.Add(newColumn);
}
Then in CreateDataGridColumn I have.
private DataGridTemplateColumn CreateDataGridColumn(ColumnEntity dataColumn, FormatConditionGroup formatConditionGroup)
{
DataGridTemplateColumn newColumn = new DataGridTemplateColumn();
DataTemplate dataTemplate = new DataTemplate();
String triggerValue;
// Create the label that will display the cell contents
FrameworkElementFactory labelFNFactory;
labelFNFactory = new FrameworkElementFactory(typeof(Label));
Binding binding = new Binding();
binding.Path = new PropertyPath(dataColumn.Tag);
labelFNFactory.SetBinding(Label.ContentProperty, binding);
labelFNFactory.Name = dataColumn.Name;
labelFNFactory.SetValue(Label.VerticalContentAlignmentProperty, VerticalAlignment.Center);
labelFNFactory.SetValue(Label.MarginProperty, new Thickness(-2));
dataTemplate.VisualTree = labelFNFactory;
if (formatConditionGroup != null)
{
foreach (FormatCondition formatCondition in formatConditionGroup.FormatConditionItems)
{
// Check to see if this is the target column for the format condition
if (formatCondition.GetPropertyValue("Target") == dataColumn.Tag)
{
// Create a data trigger to apply the format condition
DataTrigger trigger = new DataTrigger();
// Set up the binding to the source column, typically this will be the current column but it could be different
trigger.Binding = new Binding { Path = new PropertyPath(formatCondition.Source) };
trigger.Value = formatCondition.Value;
// Check to see if we need to change the text color
if ((triggerValue = formatCondition.TextColor) != null)
trigger.Setters.Add(new Setter(Label.ForegroundProperty, new SolidColorBrush((Color)ColorConverter.ConvertFromString(triggerValue)), textBlockName));
// Check to see if we need to change the background color
if ((triggerValue = formatCondition.BackColor) != null)
trigger.Setters.Add(new Setter(Label.BackgroundProperty, new SolidColorBrush((Color)ColorConverter.ConvertFromString(triggerValue)), textBlockName));
dataTemplate.Triggers.Add(trigger);
}
}
}
newColumn.CellTemplate = dataTemplate;
return newColumn;
}
So, this does excatly what I need and it has been thoroughly tested. If anyone has ideas on how this could be optimized for better performance or reduced code I'd appreciate the feedback.
Now I have a new problem.
Keeping in mind it has to be dynamic how can this be expanded to accept multiple conditions? For example, if "Type" is "PT" or "Type is "FT" set background to black or if "Type" is "PS" and "Priority" is "10" set background to blue.
Further to this, how could I apply a format condition to the entire row?
i created a sample....
Xaml code...
<DataGrid Name="myDataGrid" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Priority" Width="Auto" MinWidth="100" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="FIELD">
<Setter Property="Grid.Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="NOT A FIELD">
<Setter Property="Grid.Background" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding Type}"></TextBlock>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Type" Width="Auto" MinWidth="100" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Priority}" Value="1">
<Setter Property="Grid.Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Priority}" Value="2">
<Setter Property="Grid.Background" Value="Green"/>
</DataTrigger>
<DataTrigger Binding="{Binding Priority}" Value="3">
<Setter Property="Grid.Background" Value="Blue"/>
</DataTrigger>
<DataTrigger Binding="{Binding Priority}" Value="4">
<Setter Property="Grid.Background" Value="Yellow"/>
</DataTrigger>
<DataTrigger Binding="{Binding Priority}" Value="0">
<Setter Property="Grid.Background" Value="Orange"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding Priority}">
</TextBlock>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Code behind...
List<Temp> items = new List<Temp>();
Random rand = new Random();
for (int i = 0; i < 20; i++)
{
items.Add(new Temp { Priority = rand.Next(0, 5), Type = i % 2 == 0 ? "FIELD" : "NOT A FIELD" });
}
myDataGrid.ItemsSource = items;
public class Temp
{
public string Type { get; set; }
public int Priority { get; set; }
}
out put looks like this......

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

Resources