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.
Related
I have a button with a dynamic context menu (i.e. fed from an ItemsSource). I would like to use its ContextMenu's .HasItems property as a trigger to disable it when the context menu is empty. The following does not seem to work, even though the debugger shows no issues with the binding:
<Style x:Key="ContextMenuButtonStyle" TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},Path=ContextMenu.HasItems}" Value="False">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
Used like this:
<Button Style="{StaticResource ContextMenuButtonStyle}" Content="Items" Click="ShowContext">
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding MyItems}" Placement="Top" VerticalOffset="-1" />
</Button.ContextMenu>
</Button>
This always just behaves as if there were no items, i.e. the button stays disabled. However, if I comment out that trigger I can immediately see that the context menu clearly contains items at that point.
Interestingly, I have a second, similar trigger, based on ContextMenu.IsOpen that is working fine:
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},Path=ContextMenu.IsOpen}" Value="True">
<Setter Property="Background" Value="White" />
<Setter Property="Foreground" Value="Black" />
</DataTrigger>
So maybe the issue isn't actually the binding in the trigger but the querying/updating of the ItemsSource that is somehow impacted by the disabled state? Any other ideas or hints on how to resolve this?
Update: I have by now found out that the Items collection is apparently not populated from ItemsSource until the context menu is actually shown, so that certainly explains why my HasItems approach doesn't work. So, is there maybe a way to have the trigger react to the contents of the referenced ItemsSource - but without explicitly referencing that source in the Style so that it could be reused for other buttons with different items sources?
OK, I just realised the obvious answer as I was posting my update to the question:
<Style x:Key="ContextMenuButtonStyle" TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},Path=ContextMenu.ItemsSource.Count}" Value="0">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
Have you thought about doing this instead?
<Style x:Key="ContextMenuButtonStyle" TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=MyItems.Count}" Value="0">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
I have a Button which uses a ControlTemplate, this template contains multiple MultiDataTriggers which change the appearance depending on User Interaction and View Model properties. Therefore, I cannot use traditional Triggers.
The Triggers in question (examples)
XAML:
<DataTrigger Binding="{Binding IsPressed, RelativeSource={RelativeSource TemplatedParent}}" Value="True">
<Setter TargetName="Text" Property="Content" Value="IsPressed"/>
</DataTrigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="Text" Property="Content" Value="IsPressed"/>
</Trigger>
The DataTrigger fails, yet the normal Trigger does not. Why?
Note:
I have tried multiple approaches to binding, such as AncestorType, but have had no success. These triggers have also been tested independently with the same problem persisting.
Try {RelativeSource Self}:
<DataTrigger Binding="{Binding IsPressed, RelativeSource={RelativeSource Self}}" Value="True">
<Setter TargetName="Text" Property="Content" Value="IsPressed"/>
</DataTrigger>
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.
I need to set the tabstop of a textbox based on whether or not the bound property has data or not. The properties are nearly all strings - I want to disable the tabstop if the property is null or empty.
I am using a style for these textboxes.
Here is the style I am currently using:
<Style TargetType="TextBox" x:Key="FauxLabel">
<Setter Property="Background" Value="Transparent" />
<Setter Property="IsTabStop" Value="True" />
<Setter Property="IsReadOnly" Value="True" />
<!-- rest of setters truncated -->
</Style>
And here is an example of my usage:
<TextBox
Name="Account"
Style="{StaticResource ResourceKey=FauxLabel}"
Text="{Binding
Path=SelectedItem.AccountNumber,
ElementName=CrfResults}"/>
In this exmple, if the AccountNumber property is null or empty, I want to disable the tabstop. I am using Visual Studio 2010 and .Net 4.0. Can anyone help me out?
Update:
Thanks to Rachel for her answer. I was able to update the style to handle all textboxes using that style using by adding the trigger below which binds to the text property, rather than the underlying bound property:
<Style.Triggers>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="IsTabStop" Value="False">
</Setter>
</Trigger>
<Trigger Property="Text" Value="">
<Setter Property="IsTabStop" Value="False">
</Setter>
</Trigger>
</Style.Triggers>
Use a DataTrigger which checks if the value is {x:Null}
<Style TargetType="TextBox" x:Key="FauxLabel">
<Setter Property="IsTabStop" Value="True" />
<Style.Triggers>
<DataTrigger Property="{Binding ElementName=CrfResults, Path=SelectedItem.AccountNumber}" Value="{x:Null}">
<Setter Property="IsTabStop" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
You could probably do it with a regular Trigger instead of a DataTrigger too
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>