How to enable or disable the checkbox based on property value? - c#

In my DataGrid I've a DataGridCheckBoxColumn implementation:
<DataGrid ItemsSource="{Binding Collection}">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding Selected}"
ElementStyle="{StaticResource MaterialDesignDataGridCheckBoxColumnStyle}"
EditingElementStyle="{StaticResource MaterialDesignDataGridCheckBoxColumnEditingStyle}">
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell">
<EventSetter Event="CheckBox.Unchecked" Handler="Match_Unchecked"/>
<Style.Triggers>
<!-- Enabling -->
<DataTrigger Binding="{Binding DataContext.IsAdding,
RelativeSource={RelativeSource AncestorType=Window}}" Value="false">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
<!-- Disabling -->
<DataTrigger Binding="{Binding DataContext.IsAdding,
RelativeSource={RelativeSource AncestorType=Window}}" Value="True">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style>
</DataGridCheckBoxColumn.CellStyle>
as you can see I've inserted a DataTrigger on the Style of DataGridCheckBoxColumn, so essentially when the property IsAdding is false, the Cell with the content inside should be enabled, otherwise, disabled.
This doesn't seems to working, the Cell is even enabled, why?

I suspect the problem is with the LogicalTree of your application. The code you have there should actually work just fine, but it appears that you may be testing this inside of the demo application. Normally this would be fine, however there was an addition of a control to display XAML that is breaking the logical tree (I have a fix in progress but it is not complete yet).
You can work around this by making two changes to the demo application:
In the MainWindow.xaml replace the XamlDisplayerHost with a regular ContentControl
In Grids.xaml replace the XamlDisplayerPanel with a regular StackPanel
After that your style should work as you expect.

Related

WPF Changing DataGrid Row Background Colour if JobTimeStart Column is 1 hour away from JobTimeFinish Column

In my database "JobTimeStart" and "JobTimeFinish" are both stored as 'Time' Datatypes. I need the row background colour to change to red in my DataGrid if "JobTimeStart" is 1 hour away from "JobTimeFinish".
I made a trigger for my "JobDate" column so that if it matches todays date it will change colour to orange. I have been trying to see if I could maybe do it the same way using triggers, but have had no luck finding anything similar to what I want.
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Height" Value="40"></Setter>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding JobDate.Date}" Value="{x:Static System:DateTime.Today}">
<Setter Property="Background" Value="#ff8d00"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
..
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID}"/>
<DataGridTextColumn Header="Job" Binding="{Binding JobType}"/>
<DataGridTextColumn Header="Date" Binding="{Binding Path=JobDate, StringFormat=\{0:dd/MM/yyyy\}}" x:Name="JobDate"/>
<DataGridTextColumn Header="Start Time" Binding="{Binding JobTimeStart}"/>
<DataGridTextColumn Header="Finish Time" Binding="{Binding JobTimeFinish}" x:Name="JobTimeFinish"/>
</DataGrid.Columns>
Anything to help push me in the right direction would be appreciated.
If those properties are never to be changed, you can bind the trigger to the data item itself, rather than the DateProperty and then use a converter to check whether those properties match the predicate. However, if those are changed at run-time, the data trigger would not be notified:
<Style.Triggers>
<DataTrigger Binding="{Binding, Converter={StatocResource SomeConverter}}" Value="true">
<Setter Property="Background" Value="#ff8d00"/>
</DataTrigger>
</Style.Triggers>
If using triggers is not a requirement, I would use the RowStyleSelector feature.
There is no way to subtract times and compare the result to a pre-defined TimeSpan in XAML.
What you should to is to add a property to your view model or model that returns a value that indicates whether Background property should be set in the view:
public bool Warn => JobTimeFinish.Subtract(JobTimeStart).TotalHours >= 1;
You can then trigger on this property:
<DataTrigger Binding="{Binding Warn}" Value="True">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
This way you also keep your logic in your view model or model where it belongs and can be tested separately. XAML is a markup language.

using triggers as resources

I want to define triggers as resources to use them later in my controls.
Like this:
<Window.Resources>
<DataTrigger x:Key="Trigger1" Binding="{Binding ViewModelProperty1}" Value="Val1">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
<DataTrigger x:Key="Trigger2" Binding="{Binding ViewModelProperty2}" Value="Val2">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
...
</Window.Resources>
However, when I try to run the code, the compiler complains that IsEnabled is not a valid member. I think this is because it cannot know if the control in question will even have the property "IsEnabled". Like with styles, I think I need to somehow specifiy the TargetType (which would be, in my case, FrameworkElement). But how?
NOTE:
Please do not suggest to use styles instead of triggers as resources. Since a control can only have ONE style, but I need to give SEVERAL triggers to one control, styles are no option here:
In my actual code I have a Button that should have trigger 1, 2 and 4 and a TextBox that should have trigger 1 and 3 and a Label that should have trigger 2, 3 and 4... I think you get it.
You can do it like this (note how I prepend IsEnabled with FrameworkElement and also how I reference those resources from style triggers):
<Window.Resources>
<DataTrigger x:Key="Trigger1"
Binding="{Binding ViewModelProperty1}"
Value="Val1">
<Setter Property="FrameworkElement.IsEnabled"
Value="False" />
</DataTrigger>
<DataTrigger x:Key="Trigger2"
Binding="{Binding ViewModelProperty2}"
Value="Val2">
<Setter Property="FrameworkElement.IsEnabled"
Value="False" />
</DataTrigger>
</Window.Resources>
<Button>
<Button.Style>
<Style>
<Style.Triggers>
<StaticResource ResourceKey="Trigger1" />
<StaticResource ResourceKey="Trigger2" />
</Style.Triggers>
</Style>
</Button.Style>
</Button>

WPF Disabling DataGridCheckBoxColumn based on observable collection property

I am a new WPF starter and am having trouble achieving a minor thing.
I have a viewModel that has an ObservableCollection
Order and that has two props
IsSelected(to indicate that order has been selected in grid)
EnableOrder(to indicate if an order can be selected).
However the checkbox is not disabled when Orders/EnableOrder is false.
<DataGrid ItemsSource="{Binding Orders}">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding IsSelected}">
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="IsEnabled" Value="{Binding Orders/EnableOrder}" />
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
</DataGrid.Columns>
</DataGrid>
It works when I hard-code False like below
<DataGrid ItemsSource="{Binding Orders}">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding IsSelected}">
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="IsEnabled" Value="False" />
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
</DataGrid.Columns>
</DataGrid>
Also I was not able to specify binding as below as EnableOrder is not visible in XAML intellisense.
<Setter Property="IsEnabled" Value="{Binding EnableOrder}" />
Please note that there were other simple DataGridTextColumn bindings that I had inside the grid and the bindings worked without any problem
I think the binding was wrong. Check with below code. Also check the output window for any binding errors.
<DataGrid ItemsSource="{Binding Orders}">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding IsSelected}">
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="IsEnabled" Value="{Binding EnableOrder}" />
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
</DataGrid.Columns>
</DataGrid>
Copied Comment as Answer Since the OP Found it to Work:
I would imagine that intellisense wouldn't pull up EnableOrder since you are in a Style and that Style may apply to multiple items which may have different DataContexts. So if you bind to EnableOrder even though there isn't any intellisense are you still able to build and run, and if so does it work?

"Cascade" Databinding

i am beginner in wpf and i want to help with this piece of XAML code.
<DataGrid ItemsSource="{Binding Elements[person]}" >
<DataGrid.Columns>
<DataGridTextColumn x:Name="headerPhone" Binding="{Binding Element[phone].Value}">
<DataGridTextColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}" x:Name="headerPhoneCStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding Element[phone].Attribute[changed].Value}" Value="yes">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
Is there some way how to reduce this:
Binding="{Binding Element[phone].Attribute[changed].Value}"
To just something like this:
Binding="{Binding Attribute[changed].Value}"
To do that you would need to change the DataContext of the items. Currently, your DataGrid.ItemsSource property is data bound to the Elements[person] object... this must be a collection of some kind and so the DataContext of each item is set to an element from this collection.
It's hard to tell the exact structure of your data class because you didn't show us [you should always show all the relevant code], but if your Binding works, then it seems as though each item has an indexed property named Element in it. Now if you want to data bind directly with the object that is returned when using the index phone, you can just add that to the ItemsSource Binding instead:
<DataGrid ItemsSource="{Binding Elements[person].Element[phone]}">
<DataGrid.Columns>
<DataGridTextColumn x:Name="headerPhone" Binding="{Binding Value}">
<DataGridTextColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}" x:Name="headerPhoneCStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding Attribute[changed].Value}"
Value="yes">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
However, if Elements[person].Element[phone] is not a collection, there is not much point in using a DataGrid to display it... if it is a collection, then this should work just fine. Let me know how you get on. Also, you should see the Property Path Syntax and Binding.Path Property pages on MSDN for more help with Binding.Path syntax.

Datagrid multitrigger referencing values from code-behind

I'm still bloody green in WPF and have not yet fully grasped the concept behind it. I've got the following problem:
I want to set triggers in a datagrid depending on a precondition.
Example:
In my code-behind, I have a string variable, let's call it variableString. Now depending on the the value of variableString, I want to enable/disable the triggers inside a datagrid, that I have defined in XAML like:
if(variableString == "a")
then
XAML
<DataGrid AutoGenerateColumns="False" Margin="5,5,0,75" Name="dataGrid1" ItemsSource="Binding}">
<DataGrid.ItemContainerStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SomeColumnName}" Value="someValue">
<Setter Property="Background" Value="White"/>
<DataTrigger Binding="{Binding Path=SomeColumName}" Value="someOtherValue">
<Setter Property="Background" Value="Red"/>
</Style.Triggers>
</Style>
</DataGrid.ItemContainerStyle>
Otherwise, if
if(variableString == "b")
then
Do Nothing`
I've already tried binding the string to the datacontext of the datagrid, but that was rather contra-productive, as it removes my binding to the database.
Can anyone help me here. An example, a push in the right direction etc...
I really like the options that WPF gives you, however it's that fundamental things, that were so easy to handle in WinForms, that drive me mad in WPF.
Thanks
I think you want a MultiDataTrigger, which allows you to base your trigger off of multiple values
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=SomeColumnName}" Value="someValue" />
<Condition Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=variableString}" Value="A" />
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="White" />
</MultiDataTrigger>
To find your string in the code behind, you'll probably have to use some kind of RelativeSource binding to find the class containing that property. My example assumes there is a public property called variableString on the Window class

Categories