I have two GroupBoxes, which use CheckBoxes A and B as their header. What I want is when B is checked, I want A to be checked as well. A is enabled only when B is unchecked. I have the following code:
<GroupBox>
<GroupBox.Header>
<CheckBox Name="A">
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=B, Path=IsChecked}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="False" />
<Setter Property="IsChecked" Value="True" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=B, Path=IsChecked}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
Check Box A
</CheckBox>
</GroupBox.Header>
</GroupBox>
<GroupBox>
<GroupBox.Header>
<CheckBox Name="B">
Check Box B
</CheckBox>
</GroupBox.Header>
</GroupBox>
The problem I have is when I uncheck Check Box B, Check Box A will also be unchecked. What is wrong with my triggers?
The trigger system will apply a setter when the trigger condition is satisfied. When the trigger condition is not satisfied anymore all trigger's setters are reverted to the original value. This tells you that you can not use triggers for this purpose. Best way to accomplish this is to set it from code behind in Checked eventhandler.
Related
I know that is possible disable a Control if the CheckBox is not checked, generally I did this:
<CheckBox x:Name="myCheckBox" />
<Button IsEnabled="{Binding ElementName=myCheckBox, Path=IsChecked}" />
this will enable the Button only if the CheckBox is checked, but there is a way in Xaml to enable the Button if the CheckBox is unchecked without create any Converter?
Pseudo code:
<CheckBox x:Name="myCheckBox" />
<Button IsEnabled="{Binding ElementName=myCheckBox, Path=IsUnChecked}" />
Best regards.
You may use the Binding with a DataTrigger:
<CheckBox x:Name="myCheckBox"/>
<Button x:Name="button">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=myCheckBox}"
Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Putting this answer to update visitors about the new syntax of DataTrigger to achieve the same. It worked for me on "Visual Studio 2015" or later.
<CheckBox x:Name="myCheckBox"/>
<Button x:Name="button">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=myCheckBox, Path=IsChecked}"
Value="True">
<DataTrigger.Setters>
<!--it would make the Button in Disabled state since checkbox is in Checked state-->
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=myCheckBox, Path=IsChecked}"
Value="False">
<DataTrigger.Setters>
<!--it would make the Button in Enabled state since checkbox is in Unchecked state-->
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
I hope it helps all. Thanks!
I have multiple datatriggers of the form:
<Style x:Key="VerifyCheckBox" TargetType="{x:Type CheckBox}">
<Setter Property="Height" Value="14" />
<Setter Property="FontSize" Value="12" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=PrimaryInsuranceCompany, Path=(Validation.HasError)}" Value="True">
<Setter Property="IsEnabled" Value="False" />
<Setter Property="IsChecked" Value="False" />
</DataTrigger>
The IsEnabled property on the CheckBox is set correctly. However, the IsChecked property does not get unchecked if it had been manually checked in the first place.
Can IsChecked be unchecked from a Setter?
Edit#1
To complicate matters a bit more, the checkbox is bound to a property in my viewmodel as:
<CheckBox
Style="{StaticResource VerifyCheckBox}"
IsChecked="{Binding PrimaryInsurance.SelectedInsurance.Verify}"
Content="Verify" HorizontalAlignment="Left" Margin="789,92,0,676" Width="46" />
And there are multiple DataTrigger s in the Style.Triggers each checking a different element on the UI. In short, the UI has all the data validation. For instance, the element named above, PrimaryInsuranceCompany is:
<wc:AutoFilteredComboBox
Name="PrimaryInsuranceCompany"
IsEnabled="{Binding PrimaryInsurance.Enabled}"
ItemsSource="{Binding PrimaryInsurance.AllCompanies}"
ItemTemplate="{StaticResource CompanyTemplate}"
IsEditable="True"
IsCaseSensitive="False"
IsTextSearchEnabled="True"
TextSearch.TextPath="Companyname"
Text="{Binding PrimaryInsurance.NewCompanyName, UpdateSourceTrigger=PropertyChanged}"
theFilter="{Binding PrimaryInsurance.TheFilter}"
SelectedItem="{Binding PrimaryInsurance.SelectedInsurance.Company}"
h:ComboBoxRegexValidator.RegexText="{x:Static h:RegexLibrary.NonEmptyRegex}"
HorizontalAlignment="Left" Margin="590,138,0,0" VerticalAlignment="Top" Width="363" />
So with the combobox element in particular, I was trying to avoid duplicating the validation process in the view model since it is already being done directly in the view. Can this be done?
This quite often causes a confusion. You might want to read this: https://msdn.microsoft.com/en-us/library/vstudio/ms743230%28v=vs.100%29.aspx.
Basically, dependency properties in wpf might be set in different places: locally (IsChecked=True), in style, in trigger, and so on. When property is set in multiple places, specific resolution order is used to resolve the conflict. Here is an example:
<Window.Resources>
<Style x:Key="VerifyCheckBox" TargetType="{x:Type CheckBox}">
<Setter Property="Height" Value="14" />
<Setter Property="FontSize" Value="12" />
<Style.Triggers>
<DataTrigger Binding="{Binding TestValue}" Value="True">
<Setter Property="IsChecked" Value="False" />
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<CheckBox Content="Hey there" IsChecked="True" Style="{StaticResource VerifyCheckBox}" />
Here we set IsChecked in two places: locally (IsChecked=True) and in trigger. Local value has higher priority and because of that, when TestValue will become true checkbox will not be unchecked. However we did not set IsEnabled locally, so it will have it's value from trigger. Note that if we did set IsEnabled=True locally, trigger would have no effect at all.
Now let's try like this:
<Window.Resources>
<Style x:Key="VerifyCheckBox" TargetType="{x:Type CheckBox}">
<Setter Property="Height" Value="14" />
<Setter Property="FontSize" Value="12" />
<Setter Property="IsChecked" Value="True"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding TestValue}" Value="True">
<Setter Property="IsChecked" Value="False" />
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<CheckBox Content="Hey there" Style="{StaticResource VerifyCheckBox}" />
We moved IsChecked initial setter in style and now it works as expected, because trigger has priority over values in style setter.
Update: user clicking does not change behavior above. So, if you have local IsChecked=True, then user clicked twice, then TestValue becomes true - it will not be unchecked. And if you do the same but initialize IsChecked=True in style setter - it will be unchecked.
I have a tool window in my application. there are some text input available and i have some validation rule on those text boxes.
say,
validation rule 1
Validation rule 2
validation rule 3
I have a button on that tool window and a style like the bellow
<StackPanel.Resources>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource ButtonStyle}">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=HeightTextbox, Path=(Validation.HasError)}" Value="false" />
<Condition Binding="{Binding ElementName=WeightTextBox, Path=(Validation.HasError)}" Value="false" />
<Condition Binding="{Binding ElementName=HeartRateTextBox, Path=(Validation.HasError)}" Value="false" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="true"/>
</MultiDataTrigger>
</Style.Triggers>
<Setter Property="IsEnabled" Value="false" />
</Style>
</StackPanel.Resources>
and the ok button
<Button Content="Ok" x:Name="OkButton" Height="32" Width="80" Command="{Binding OkCommand}"/>
It is working fine but I want the button to be disabled every time user opens no matters where it is satisfying validation rule or not but the rule should be applied too.
You can try adding some DataTrigger listening to Visibility property using OneTime mode binding and set the IsEnabled to false. This trigger should be placed after your multitrigger so that it can override that trigger:
<Style.Triggers>
<!-- your multi trigger ... -->
<DataTrigger Property="{Binding Visibility,
RelativeSource={RelativeSource Self},
Mode=OneTime}" Value="Visible">
<Setter Property="IsEnabled" Value="false" />
</DataTrigger>
</Style.Triggers>
The above works only when you hide your tool window by calling Close() method, so next time when open the window, it will be loaded first.
If you use something like Hide() method to hide the tool window and open it again using Show(), you can try using the following code instead:
<sys:Boolean x:Key="f">false</sys:Boolean>
<Style.Triggers>
<!-- your multi trigger ... -->
<Trigger Property="Visibility" Value="Visible">
<Setter Property="IsEnabled" Value="{Binding Resources[f],
RelativeSource={RelativeSource AncestorType=StackPanel},
Mode=OneTime}"/>
</Trigger>
</Style.Triggers>
Note about the prefix sys used here, you have to import the namespace System into the XAML code. I hope you know how to do this.
The following triggers work almost as expected:
<Style.Triggers>
<Trigger Value="True" Property="IsSelected">
<Setter Property="Foreground" Value="White" />
</Trigger>
<DataTrigger Value="True" Binding="{Binding UpdateSourceTrigger=PropertyChanged, Converter={StaticResource InUseConverter}}">
<Setter Property="Foreground" Value="OrangeRed" />
</DataTrigger>
</Style.Triggers>
After loading the view, the colors are correct.
Then I execute an async taks en when it has finished I give the propertychanged on the object that has the binding to my datagrid-row.
But why is the DataTrigger not fired (I have to refresh the view to see the effect)?
EDIT:
My problem is that I don't now which property I have to give the PropertyChanged.
Some details about the datagrid (Projects is an ObservableCollection):
DataGrid SelectedItem="{Binding Project}" ItemsSource="{Binding Projects}">
The property of object Project that the binding must use is:
Project.Variants[0].InUse
I tried also the triggers:
<DataTrigger Value="True" Binding="{Binding Path=Variants[0].InUse, Converter={StaticResource NotNullConverter}}">
<DataTrigger Value="True" Binding="{Binding Path=., Converter={StaticResource InUseConverter}}">
In the view model I have tried after Project.Variants[0].InUse = null;:
Project.OnPropertyChanged("InUse");
Project.Variants[0].OnPropertyChanged("InUse");
raisePropertyChanged("Project.Variants[0].InUse");
raisePropertyChanged("Variants[0].InUse");
raisePropertyChanged("Projects");
raisePropertyChanged("Project");
raisePropertyChanged("InUse");
At last it works using:
<DataTrigger Value="True" Binding="{Binding Path=Variants[0].InUse, Converter={StaticResource NotNullConverter}}">
<Setter Property="Foreground" Value="OrangeRed" />
</DataTrigger>
Project.Variants[0].OnPropertyChanged("InUse");
Try to set Foreground property in your style to change it dynamically at runtime
<Setter Property="Foreground" Value="White"/>
<Style.Triggers>
<Trigger Value="True" Property="IsSelected">
<Setter Property="Foreground" Value="White" />
</Trigger>
<DataTrigger Value="True" Binding="{Binding UpdateSourceTrigger=PropertyChanged, Converter={StaticResource InUseConverter}}">
<Setter Property="Foreground" Value="OrangeRed" />
</DataTrigger>
</Style.Triggers>
I've been struggling with this code for some time now and can't seem to find any complete answers to my question. I've created a small sample to illustrate the problem:
<ListView >
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Margin="0,0,20,0" IsItemsHost="True" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.Items>
<TextBlock>Test1</TextBlock>
<TextBlock>Test2</TextBlock>
<TextBlock>Test3</TextBlock>
<TextBlock>Test4</TextBlock>
<TextBlock>Test5</TextBlock>
</ListView.Items>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Grid>
<ContentPresenter/>
</Grid>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsMouseOver}" Value="True" />
<Condition Property="IsSelected" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Visibility" Value="Collapsed"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
According to the MultiTrigger settings, the selected item should reappear when the mouse is no longer over the selected item. This code, however, produces an InvalidOperationException with the message "Must have non-null value for 'Property'." If you remove the Condition that uses the "Binding" attribute the exception is not thrown. In the MSDN documentation it states that you must have either the Property or Binding attribute set. The above code functions like the Binding attribute is not set. In fact, in all my test cases, it doesn't matter what the Binding attribute is set to; the exception is still thrown. Any thoughts?
This is one of those times when you have to suck it up and admit that you've made a bonehead mistake. However, to save some other unlucky soul from the same fate, I'll reveal my epiphany.
First, if I had read all of the documentation I would have read the part that said if you're using the condition's "Binding" attribute, it needs to be included in a MultiDataTrigger element (instead of the MutiTrigger element in my posted example).
Second, upon making those changes, the MultiTrigger element is replace with the following code:
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsMouseOver}" Value="True" />
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsSelected}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Collapsed"/>
</MultiDataTrigger>
Now the example works but because the selected item is collapsed, the trigger condition switches back and forth causing the selected item to flicker in and out of view. Makes sense but admittedly not what I intended.
At any rate, hope this helps someone from making the same bonehead mistake!
On a very similar note, pulling IsMouseOver from a border as the main data template content, and pulling the IsSelected from the Ancestor. Its interesting that both conditions have to have a relative path, I would assume that the default path would be the local datacontext. Thanks for the above solution.
Broken Code
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}, Path=IsSelected}"
Value="True" />
<Condition SourceName="Border"
Property="IsMouseOver"
Value="True" />
</MultiDataTrigger.Conditions>
<Setter TargetName="Border"
Property="Background"
Value="{StaticResource OnBrushSelected}" />
</MultiDataTrigger>
Working Code
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Mode=OneWay, RelativeSource={RelativeSource Self}, Path=IsMouseOver}"
Value="True" />
<Condition Binding="{Binding Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}, Path=IsSelected}"
Value="True" />
</MultiDataTrigger.Conditions>
<Setter TargetName="Border"
Property="Background"
Value="{StaticResource OnBrushSelected}" />
</MultiDataTrigger>