I have a Command bound to a Button in XAML. When executed, the command changes a property value on the underlying DataContext. I would like the button's Content to reflect the new value of the property.
This works*:
<Button Command="{x:Static Member=local:MyCommands.TestCommand}"
Content="{Binding Path=TestProperty, Mode=OneWay}" />
But this doesn't:
<Button Command="{x:Static Member=local:MyCommands.TestCommand}">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=TestProperty, Mode=OneWay}" Value="True">
<DataTrigger.Setters>
<Setter Property="Content" Value="Yes"/>
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Binding="{Binding Path=TestProperty, Mode=OneWay}" Value="False">
<DataTrigger.Setters>
<Setter Property="Content" Value="No"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Why is that?
* By "works" I mean the Content gets updated whenever I click the button. I don't know if it's important or not, but just in case it is, I'm using .NET 3.5 SP1.
UPDATE:
I also tried another variation (removing the second DataTrigger in favor of default value), still to no avail:
<Button Command="{x:Static Member=local:MyCommands.TestCommand}">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Content" Value="No"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=TestProperty, Mode=OneWay}" Value="True">
<DataTrigger.Setters>
<Setter Property="Content" Value="Yes"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
TIA
Try to set your default Content to No and to use only the first DataTrigger. Avoid the second one.
I solved the problem myself. It turned out I was looking in the wrong place. The bound CLR property was of different type than what I'd assumed in XAML. My stupidity, really.
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 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 a StatusCell style for DataGridCell that I would like to use in several place in my application. I would like to externalize the Style tag so that I can reuse it easily without having to duplicate the code in my XAML everywhere.
Every other source I've found has required me to bind the trigger off the property from my ViewModel. But across the application, the column might be bound to MyStatusProperty or SubObject.MyStatusProperty, etc, so I want to do this to allow me to have one style that will apply to all of these without having to specify where it's binding from.
I am able to do this with a TextBlock with the following style. This lets me bind the TextBlock to whatever I want and the style binding doesn't matter where it's coming from.
<Style x:Key="StatusLabel" TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="Completed">
<Setter Property="Foreground" Value="Green"/>
</Trigger>
</Style.Triggers>
</Style>
and when I create a textblock that I want to use this styling, all I have to do is
<TextBlock Style="{StaticResource StatusLabel}" Text="{Binding Whatever}" />
But with a DataGridCell it doesn't let me do this
<Style x:Key="StatusCell" TargetType="DataGridCell">
<Style.Triggers>
<Trigger Property="Content" Value="Completed">
<Setter Property="Background" Value="Green"/>
<Setter Property="Foreground" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
<DataGridTextColumn Header="Status" Binding="{Binding MyStatusProperty}"
CellStyle="{StaticResource StatusCell}" />
I also tried setting up the trigger like this:
<DataTrigger Binding="{Binding Content, RelativeSource={RelativeSource Self}}" Value="Reviewed">
<Setter Property="Background" Value="Green"/>
<Setter Property="Foreground" Value="White" />
</DataTrigger>
But neither of these work. I have also tried swapping out "Content" in the last example for "Binding" and "Text"
Is there another property I can bind to in the DataGridCell that will let bind the style trigger to the contents of the cell without knowing the binding path?
As usual, I found a workaround shortly after asking. Since it's working with TextBlocks, I just have to use TemplateColumns instead of TextColumns, although I'd still prefer to be able to use TextColumns since they'd use 6 less lines of XAML.
<DataGridTemplateColumn Header="Status">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding MyProperty}" Style="{StaticResource StatusCellTextBlock}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Using the style:
<Style x:Key="StatusCellTextBlock" TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="Completed">
<Setter Property="Background" Value="Green"/>
<Setter Property="Foreground" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
The following dims the image when I disable the button and show clearly when enabled:
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type UIElement}, AncestorLevel=1}, Path=IsEnabled}" Value="False">
<Setter Property="Opacity" Value="0.25"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
...
<Button Name="btnPageFirst" IsEnabled="False">
<Image Source="..\Resources\imgMoveFirst.png" />
</Button>
I want to do a similar effect but with Path. I want to gray the image. But it does not gray and there is no error.
<Style TargetType="{x:Type Path}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type UIElement}, AncestorLevel=1}, Path=IsEnabled}" Value="False">
<Setter Property="Fill" Value="Gray"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
...
<Button Name="btnPageFirst" IsEnabled="False">
<Path Data="F1M820.557,535.025L838.189,535.024 817.857,555.36 857.82,555.36 857.82,568.301 817.998,568.301 838.226,588.531 820.557,588.499 793.82,561.765 820.557,535.025z" Stretch="Uniform" Fill="DodgerBlue" Width="16" Height="16" Margin="0,0,0,0" />
</Button>
There is a precedence order which is used to calculate the values of the dependency properties during runtime.
The oversimplified precedence list:
Local value (what you set on the control)
Triggers
Style setters
Your problem is that you set Fill="DodgerBlue" on your path and because it has higher precedence then the Trigger that is way you don't see the fill change. Also that is why it works for your Image because there you don't set the Opacity directly.
To make it work:
Remove the Fill="DodgerBlue" from your path
Set it in your style:
<Style TargetType="{x:Type Path}">
<Style.Setters>
<Setter Property="Fill" Value="DodgerBlue"/>
</Style.Setters>
<Style.Triggers>
<!-- ... -->
</Style.Triggers>
</Style>
As a side note: if you always "inside" in a button you can rewrite the RelativeSource to:
RelativeSource={RelativeSource AncestorType={x:Type Button}}
I have a TextBox that needs to have its border change color and then display a message below the text box. This message should be displayed/hidden based on a bool value in the model. What is the best way to achieve this?
There are a ton of different ways of doing this. If you're only going to do this once, the simplest way is to add the TextBlock to the layout and use a style to hide it, e.g.:
<StackPanel>
<TextBox Text="{Binding Text, Mode=TwoWay}">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="BorderBrush" Value="Lightgray"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsValid}" Value="False">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBlock Text="This is the message displayed if IsValid is false.">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsValid}" Value="False">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
If this is something you want to be able to repeat, you'll want to make this into a template or a user control.
Also, note that changing the visibility from collapsed to visible will change the overall layout, which could have all kinds of undesirable effects. Depending on your design, you might make the visibility default to hidden.
You can use a DataTrigger to set the text, visibility, and appearance of the textbox based on the value in the ViewModel. This seems like the simplest solution.
<TextBox>
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=TheBoolean}" Value="True">
<Setter Property="Visibility" Value="Visible" />
<Setter Property="Background" Value="Red" />
...
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Another option is to create an IValueConverter to convert the bool to get the text, visibility, and color.