MultiDataTrigger with multiple ComboBoxes WPF - c#

I would like to enable button only when all specified ComboBoxes have values. However, as soon as I add a second condition, button is enabled from the start
Here is my code
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=SensorPartNumberComboBox, Path=SelectedValue}" Value="{x:Null}"/>
<Condition Binding="{Binding ElementName=SensorTypeComboBox, Path=SelectedValue}" Value="{x:Null}"/>
<Condition Binding="{Binding ElementName=SensorBrandNameComboBox, Path=SelectedValue}" Value="{x:Null}"/>
<Condition Binding="{Binding ElementName=DimmingProtocolComboBox, Path=SelectedValue}" Value="{x:Null}"/>
<Condition Binding="{Binding ElementName=Wired_WirelessComboBox, Path=SelectedValue}" Value="{x:Null}"/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="False"/>
</MultiDataTrigger>
</Style.Triggers>
I would appreciate any help!

MultiDataTrigger requires all of the conditions to be true for it to take effect. I.e. it's equivalent to a logical AND.
In your example, if any value is non-null, the trigger won't take effect and the button will remain enabled.
For a logical OR, instead of using MultiDataTrigger, just use multiple DataTrigger. If any condition of any trigger is true, then that trigger will take effect, with precedence over the default setter for the property in the style.
For example:
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=SensorPartNumberComboBox, Path=SelectedValue}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
<DataTrigger/>
<DataTrigger Binding="{Binding ElementName=SensorTypeComboBox, Path=SelectedValue}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
<DataTrigger/>
<DataTrigger Binding="{Binding ElementName=SensorBrandNameComboBox, Path=SelectedValue}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
<DataTrigger/>
<DataTrigger Binding="{Binding ElementName=DimmingProtocolComboBox, Path=SelectedValue}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
<DataTrigger/>
<DataTrigger Binding="{Binding ElementName=Wired_WirelessComboBox, Path=SelectedValue}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
<DataTrigger/>
</Style.Triggers>
Alternatively, you might consider putting the logic in your view model, with a single bool property you bind to, and which is set according to the bound SelectedValue properties for the various ComboBox controls.
Yet another alternative would be to use MultiBinding to bind the five view model properties that are bound to the ComboBox.SelectedValue properties, with a IMultiValueConverter that implements the logic.
Of course, these last two options work only if you've got a proper view model with binding set up in the first place (something I strongly encourage, if you haven't already).

Related

WPF treeview item visibility binding to property + static variable

I have a WPF treeview, with nodes visibility dependent on their "Deleted" binding property:
<Style x:Name="SelectedTreeItem" TargetType="TreeViewItem">
<EventSetter Event="MouseRightButtonDown" Handler="TreeSetup_MouseRightButtonDown"/>
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Deleted}" Value="True">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
I would like to make add some way to ignore this deleted property value. This would be achieved by a button "Show deleted items".
I do not want to change the "Deleted" value, but rather add a global condition to that forces this binding to ignore untill the button is pressed again.
You can do that via MultiDataTrigger.Conditions. Here is an example for visibility:
<Style>
<Setter Property="Control.Visibility" Value="Visible" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=IsPropA}" Value="false" />
<Condition Binding="{Binding Path=IsPropB}" Value="false" />
</MultiDataTrigger.Conditions>
<Setter Property="Control.Visibility" Value="Hidden" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
I used the other answer in conjuction with a static resource
<Style>
<Setter Property="Control.Visibility" Value="Visible" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=IsPropA}" Value="false" />
<Condition Binding="{Binding Source={StaticResource ClassName},Path=PropertyName}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Control.Visibility" Value="Hidden" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
Just had to create the local resource under Window resources:
<Window.Resources>
<local:ClassName x:Key="ClassName"/>
And then the button used to show deleted or not:
private void btnApply_Click(object sender, RoutedEventArgs e)
{
//ApplyGeneralChanges();
var item = this.FindResource("ClassName") as ClassName;
item.DisplayDeleted = !item.DisplayDeleted;
}

DataTrigger setter will not update property. Why?

XAML :
<RadioButton x:Name="RadioButtonA">
<RadioButton.Style>
<Style TargetType="RadioButton">
<Setter Property="IsChecked" Value="{Binding Path=RadioAValue}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=CheckBoxA, Path=IsChecked}" Value="True">
<Setter Property="IsChecked" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</RadioButton.Style>
</RadioButton>
C# Code behind
public bool RadioAValue
{
get
{
...
}
set
{
...
}
}
set section of RadioAvalue will not be called when CheckBoxA is checked.
Any reason why?
Problem in your code
You are setting the binding in Style Setter
<Setter Property="IsChecked" Value="{Binding Path=RadioAValue}" />
and on clicking the checkbox you have overwriting the Binding with the value False. Now there is no more Bindings over the element and it wont call the set anymore.
Also, if you try to bind it over the RadioButton itself instead of setters that will take more precedence and your Triggers won't work there.
Solution
I would recommend you to have Command binding over the Checkbox and update the value RadioAValue from your ViewModel
If you want conditional setter use MultiBinding, right now you block binding with already setted value
<Setter Property="IsChecked" Value="{Binding Path=RadioAValue}" />
Must be something like this (didn't test thought):
<RadioButton x:Name="YouRadioButton">
<RadioButton.Style>
<Style TargetType="RadioButton">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=CheckBoxA, Path=IsChecked}" Value="True"/>
<Condition Binding="{Binding RadioAValue}" Value="True"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="IsChecked" Value="True"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<DataTrigger Binding="{Binding ElementName=CheckBoxA, Path=IsChecked}" Value="False">
<Setter Property="IsChecked" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</RadioButton.Style>
</RadioButton>
<CheckBox x:Name="CheckBoxA"/>

MultiDataTrigger with Custom DependencyProperty

I have a MultiDataTrigger. I can bind to a DependencyProperty (DP) of the control and a view model property like this
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="True"/>
<Condition Binding="{Binding PerformTextSearchesInCommentary}" Value="True"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="LightGray"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
Great. But now, I want to use my own custom DP "HasTextMatch", which is defined in a static class. I can do this with a standard trigger like
<Style.Triggers>
<Trigger Property="Helpers:DataGridTextSearch.HasTextMatch" Value="True">
<Setter Property="Background" Value="LightGray"/>
</Trigger>
</Style.Triggers>
But I now want to include another Property. I have tried
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<!--<Condition Property="Helpers:DataGridTextSearch.HasTextMatch" Value="True"/>--> This obviously won't work.
<Condition Binding="{Binding Helpers:DataGridTextSearch.HasTextMatch, RelativeSource={RelativeSource Self}}" Value="True"/>
<Condition Binding="{Binding PerformTextSearchesInCommentary}" Value="True"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="LightGray"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
But this does not work. I have searched but cannot seem to find out how to do this. How can I get the binding to my custom DP?
Thanks for your time.
The DataGridTextSearch.HasTextMatch property is an attached property. Please use the following Condition:
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=(Helpers:DataGridTextSearch.HasTextMatch), RelativeSource={RelativeSource Self}}>
...
Additional information about property path (including attached properties) can be found here: PropertyPath XAML Syntax, MSDN.

One setting is determined by the second settings

I have the same options in different WPF dialog. One option that depends on another. If I click on the first, I also want to check the second. Both options correspond to the private bool variable in different classes. What is the best solution for this problem?
You could do that in WPF/XAML with Style.Triggers. For example the following code is an example on how to bind a textblock control's IsEnabled status based on the status of a checkbox (checked/unchecked)
<TextBlock Text="mytextblockText: ">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked,ElementName=myCheckbox}" Value="False">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsChecked, ElementName=myCheckbox}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
For a checkbox being checked when another checkbox is checked, it looks like this (I guess)
<CheckBox>
<CheckBox.Style>
<Style TargetType="CheckBox">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked,ElementName=myCheckbox}" Value="False">
<Setter Property="IsChecked" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsChecked, ElementName=myCheckbox}" Value="True">
<Setter Property="IsChecked" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
*edit
can the properties in the classes be changed by something different than by those checkboxes? If yes, you could fire an event when the property is changed or directly manipulate the checkstate of the according box. Also, are the boxes dependent on each other? Or is just one dependent on the other (A <=> B or A => B)?
For manipulating the properties when one of the boxe's state is changed you could use the Checked/Unchecked events. Or use DependencyProperty as described in Pat's answere over here

Simplification of this XAML code possible? (MultiDataTrigger, DependencyProperty)

finally I got my custom button almost act like I want it to act.
The problem is the code :D (IsChecked is a DependencyProperty)
<ControlTemplate.Triggers>
<!-- EventTriggers for LMBUp/LMBDown/MouseEnter/MouseLeave -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsMouseOver}" Value="True"/>
<Condition Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=IsChecked}" Value="False"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource MouseEnter}"/>
</MultiDataTrigger.EnterActions>
<MultiDataTrigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource MouseLeave}"/>
</MultiDataTrigger.ExitActions>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsMouseOver}" Value="True"/>
<Condition Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=IsChecked}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="LayoutRoot" Property="Background" Value="Red"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsMouseOver}" Value="False"/>
<Condition Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=IsChecked}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="LayoutRoot" Property="Background" Value="Red"/>
</MultiDataTrigger>
</ControlTemplate.Triggers>
I think it is a bit "dirty" and it doesn't fully act like expected.
Everytime the user clicks on the button IsChecked gets switched from true to false or vice versa.
Now if the following happens
IsChecked == true
User clicks to switch IsChecked to false
All the time the mouse is ON the button
then I would expect that the button becomes $MouseOverColor$ instead of $ButtonNormalBackground$ because of the first MultiDataTrigger.
My questions are if it is possible to simplify the code and to correct the last problem?
Thanks in advance!
You probably have 'value resolution strategy' issue. DependencyProperty has a set strategy of how to resolve the value, see here.
If your animations set the value of Background of LayoutRoot then other setters will not have affect until you stop the animation (in this case the setters are treated as "Local Value").
You can use <StopStoryboard> element in EnterActions of second and third triggers. Note that this will write a warning in the output window if the storyboard is already stopped when the trigger was activated.
--Alternatively--
You can use FillBehavior="Stop" in your storyboard.

Categories