Trouble setting a DataTrigger in WPF - c#

I have a ComboBox and a Button on my main view, and I want to apply a style to the button such that when the combobox index is set to 1, the button becomes visible (initially it's hidden). This is my XAML code:
<Grid>
<StackPanel Orientation="Vertical" Margin="10">
<ComboBox Name="comboBox"/>
<Button Name="myBtn" Content="Hello" Visibility="Hidden">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=comboBox, Path=SelectedIndex}" Value="1">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
</Grid>
Someone already asked a question about this here, and I'm doing pretty much the same thing, but it doesn't work, the button remains hidden even when the index is changed to 1. The comobox is initially being populated in the code behind with 2 items. Any help is appreciated.

The problem is that dependency property values set locally (like you've done with visibility) have a higher precedence then those set from a style trigger. As such, even when the trigger is hit, it won't override the value you've already set.
The simple solution is to instead set the default value in a style Setter:
<Button Name="myBtn" Content="Hello">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=comboBox, Path=SelectedIndex}" Value="1">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
And now your trigger will override the property value when it is hit.
While you're at it, you should have a look at this link that lists the precedence order for setting DP values.

Related

DataTrigger on an Image that should change the Image.Source property does not work

I reproduced this question/answer as given below:
<Button Command="Play" ToolTip="Execute Macro">
<Image DataContext="{Binding ElementName=UserControlMacroEdit}" Source="/ParametricStudySharedGui;component/Image/arrowRight32x32.png" Height="24" Width="24">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsMacroRunning, Converter={StaticResource FakeTransparentConverter}, Mode=OneWay}" Value="True">
<DataTrigger.Setters>
<Setter Property="Source" Value="/ParametricStudySharedGui;component/Image/Run24.png"></Setter>
<Setter Property="Opacity" Value=".5"></Setter>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Button>
However, there is a strange behavior.
The "Opacity" changed but the image itself stay the same. It look like that I can't modify the Image.Source from a DataTrigger. Why?
Your original code does not work because of how dependency properties are handled. In particular, since they can be set from multiple places, WPF implements the idea of "priority" for such properties. "Locally set" properties (i.e. where they are set as part of the declaration of the element in XAML) have higher precedence over any setters found in styles.
Please see Dependency Property Value Precedence for more details.
You can work around the problem by initializing the Source property in the Style itself, rather than as part of the Image declaration:
<Image DataContext="{Binding ElementName=UserControlMacroEdit}" Height="24" Width="24">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="/ParametricStudySharedGui;component/Image/arrowRight32x32.png"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsMacroRunning, Converter={StaticResource FakeTransparentConverter}, Mode=OneWay}" Value="True">
<DataTrigger.Setters>
<Setter Property="Source" Value="/ParametricStudySharedGui;component/Image/Run24.png"></Setter>
<Setter Property="Opacity" Value=".5"></Setter>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>

ContentControl switch between two buttons

I've been banging my head over this all night. All I want to do is have a content control that can switch between showing two different buttons according to a boolean in the ViewModel.
Basically I have a task running in the background with a cancel button. Once you hit cancel and the task stops, the cancel button should change to a back button. These are two separate button elements, not just changing a single button's properties. I'm using MahApps TransitioningContentControl so I want to be able to use the transitions.
I would be amazing if this could mostly be done all in XAML. I really don't want to have to add a bunch of boilerplate code for what should be a simple thing.
Edit: Here's the snippet of code. There isn't much because I just deleted everything that wasn't working.
<Controls:TransitioningContentControl x:Name="CancelBackButtonControl" HorizontalAlignment="Left" VerticalAlignment="Top" Width="80" Height="80">
<Canvas>
<Button x:Name="CancelButton" HorizontalAlignment="Left" VerticalAlignment="Top" Width="80" Style="{DynamicResource MetroCircleButtonStyle}" Height="80" IsCancel="True" Content="{StaticResource appbar_close}" BorderBrush="Black" Command="{Binding CancelCommand}"/>
<Button x:Name="GoBackButton" HorizontalAlignment="Left" VerticalAlignment="Top" Width="80" Style="{DynamicResource MetroCircleButtonStyle}" Height="80" IsCancel="True" Content="{StaticResource appbar_arrow_left}" BorderBrush="Black" Command="{Binding GoBackCommand}"/>
</Canvas>
</Controls:TransitioningContentControl>
There are a staggering amount of ways to accomplish this (that's the beauty of WPF); however, in my personal opinion, you would be working less "against the grain" if you simply use a control that derives from the ToggleButton, such as RadioButton, especially since you want it to be done all in XAML. An important thing is not to think of any of the controls by their visuals, but by how they function. I've done some incredible things with RadioButton before, so that's the first thing I'll demonstrate; however, the regular button approach is included in the second half of this answer.
Here are the complete examples of both approaches (RadioButton and Button), done completely in XAML:
Preview:
Code:
<Window x:Class="Sample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="{x:Type RadioButton}" x:Key="FlatRadioButtonStyle">
<Setter Property="Width" Value="100"/>
<Setter Property="Background" Value="#FF346FD6"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Padding" Value="5,2,5,3"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RadioButton}">
<Border Background="{TemplateBinding Background}"
Width="{TemplateBinding Width}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}">
<ContentPresenter Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#FF6696E9"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<RadioButton x:Name="BackButton" Content="Back">
<RadioButton.Style>
<Style TargetType="{x:Type RadioButton}" BasedOn="{StaticResource FlatRadioButtonStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=CancelButton}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</RadioButton.Style>
</RadioButton>
<RadioButton x:Name="CancelButton" Content="Cancel" IsChecked="True">
<RadioButton.Style>
<Style TargetType="{x:Type RadioButton}" BasedOn="{StaticResource FlatRadioButtonStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=BackButton}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</RadioButton.Style>
</RadioButton>
</Grid>
</Window>
Most of the resource style code is visual styling and templating, focused on making radio buttons look like regular buttons, so it's not of great importance. The areas we'll focus on are Triggers. You'll notice that in the resource style, I ensure that when a RadioButton gets checked, it collapses. However, in the local style of each button, I ensure that when the other RadioButton gets checked, it makes the current RadioButton visible. This has to be done in the local style, since we need to pass the ElementName into the Binding. So, when a RadioButton gets checked, it collapses and makes the other RadioButton visible. You'll also note that I check the button I wish to hide by default. Obviously, you can wire that up with bindings.
Similar approaches may be applied to regular buttons:
Preview:
Code:
<Window x:Class="Sample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="{x:Type Button}" x:Key="ToggleButtonStyle">
<Setter Property="Width" Value="100"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Style.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Button x:Name="CancelButton" Content="Cancel">
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource ToggleButtonStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsFocused, ElementName=BackButton}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button x:Name="BackButton" Content="Back">
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource ToggleButtonStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsFocused, ElementName=CancelButton}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</Window>
Here, instead of IsChecked, I'm working with IsFocused. You may accomplish a similar thing by working with the EventTrigger and Click event... but, there's more work. IsPressed may appear like a good candidate, but the problem is that once you press the button, the other one will appear almost instantaneously, and that one will have IsPressed set to true almost instantaneously. So, you'll end up with this cyclic behavior where it seems like nothing is happening. Note that I use Grid to place these buttons on top of each other, with the the one I want to be visible by default at the top, that way I don't have to worry about default visibility or focus. However, you may use any other panel, just set the Visibility of the button you want to hide by default to Collapsed.
If you don't want to work with multiple controls (two buttons in this case), you may also set the Content property of a button based on a condition through DataTrigger to display different text. You just have to ensure that you handle the Command appropriately.
Since you're asking for a XAML only way to switch the button in a content control based on a ViewModel property, how about this:
First define button style to route both buttons to the same event:
<Style x:Key="buttonClickButton" TargetType="Button">
<EventSetter Event="Click" Handler="Button_Click"/>
</Style>
Then define your content control style with a data trigger switching the content based on a ViewModel property called "IsCancelButton" (bool):
<Style x:Key="ButtonSwitchContentCtrl" TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding IsCancelButton}" Value="True">
<Setter Property="Content">
<Setter.Value>
<Button x:Name="CancelButton" Style="{StaticResource buttonClickButton}"
HorizontalAlignment="Left" VerticalAlignment="Top"
Width="80" Height="80" IsCancel="True" Content="Cancel Button" BorderBrush="Black" />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding IsCancelButton}" Value="False">
<Setter Property="Content">
<Setter.Value>
<Button x:Name="GoBackButton" Style="{StaticResource buttonClickButton}"
HorizontalAlignment="Left" VerticalAlignment="Top"
Width="80" Height="80" IsCancel="True" Content="Go Back Button" BorderBrush="Black" />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
Then make your button(s) appear like so:
<ContentControl Style="{StaticResource ButtonSwitchContentCtrl}"/>
You can implement such functionality with just one Button (e.g. declared in XAML as Name="btnCancel") and simple code snippet in C# code-behind using Lambda-style event subscription like:
btnCancel.Click+=(s,e)=>{
if (btnCancel.Text=="Cancel")
{
// SOME CODE CORRESPONDING TO "CANCEL" CLICK EVENT
btnCancel.Text ="GoBack"
}
else
{
// CORRESPONDING TO "GoBack" CLICK EVENT
btnCancel.Text ="Cancel"
}
}
In case you want to use some graphic content (images) on the Button, then use Tag property of that Button instead of Text and also programmatically switch between images.
Couple other considerations: the business logic you have described could be implemented in XAML using DataTriggers (as other folks did), but instead of writing megaton of XAML for 2-Buttons solutions it would be reasonable to implement a single-button solution, either like this one, or using .NET ToggleButton Class (re: https://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.togglebutton%28v=vs.110%29.aspx); also, it could be a CheckBox control just properly styled.
In any way, you most likely will need the event handlers to do some actual job in addition to just changing the visual state of the Button, so that compact Lambda-style event subscription would be handy.
Hope this may help. Best regards,

WPF Style Triggers for DataTemplates

I would like to set Triggers for the controls in a DataTemplate. Whenever I set a property of the control within the DataTemplate, it seems not working. However, If do not set the property within the TextBlock inside the DataTemplate, then I can see the effect of Trigger in the style (it works). I am not sure whether using Style Triggers with DataTemplate is good or not! The XAML is below;
<Grid>
<Grid.Resources>
<Style TargetType="TextBlock" x:Key="BlockOf">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="FontWeight" Value="ExtraBold" />
<Setter Property="FontSize" Value="22" />
</Trigger>
</Style.Triggers>
</Style>
</Grid.Resources>
...........
DataTemplate for the button,
<Button.ContentTemplate>
<DataTemplate DataType="Button">
<TextBlock Style="{DynamicResource BlockOf}" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}"
FontStyle="Italic" FontSize="9"/>
</DataTemplate>
</Button.ContentTemplate>
I can see two problems here. First one is that current trigger will work only for TextBlock inside Button, not over whole Button. You can change that by using DataTrigger with RelativeSource binding. Second problem is that even when mouse is over TextBlock Style.Trigger cannot overwrite local value that you set against TextBlock so you need to bring default values as Setter into your Style. Check Dependency Property Setting Precedence List
<Style TargetType="TextBlock" x:Key="BlockOf">
<Setter Property="FontStyle" Value="Italic"/>
<Setter Property="FontSize" Value="9"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}, Path=IsMouseOver}" Value="True">
<Setter Property="FontWeight" Value="ExtraBold" />
<Setter Property="FontSize" Value="22" />
</DataTrigger>
</Style.Triggers>
</Style>
and then TextBlock simply
<TextBlock Style="{DynamicResource BlockOf}" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}" />

Trouble styling data-bound WPF ComboBoxItem

I have a ComboBox, bound to a DataTable. I'm trying to add an extra ComboBoxItem to the top of the list, where I can put a link to customize the list. Currently I am just inserting a dummy row to the top of my DataTable, and then using a DataTrigger on the ComboBox to make it appear correctly. However, I'm not quite getting the correct result.
I've tried two methods. In the first, my DataTrigger replaces the dummy item with a ControlTemplate, which contains a TextBlock.
<ComboBox IsEditable="True" Name="comboWell" ItemsSource="{Binding}" DisplayMemberPath="wellId">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding wellId}" Value="(settings)">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<TextBlock Text="Customize..." />
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
The result looks right, but there is no mouseover highlight on that item. The rest of the list works fine, but that one item doesn't react at all when I mouse over it. I've tried adding extra triggers and styles to apply a mouseover effect, but I get no change.
The second method I tried was just to alter the appearance of the item rather than completely replace it with a ControlTemplate.
<ComboBox IsEditable="True" Name="comboWell" ItemsSource="{Binding}" DisplayMemberPath="wellId">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding wellId}" Value="(settings)">
<Setter Property="Content" Value="Customize..." />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
This one functions like a regular list item, mouseover works fine. However, the item is blank. Neither the original text, nor the text I try to set in the DataTrigger, are there. No errors, just an empty list item.
Is there a better way to accomplish this?
Remove the DisplayMemberPath and add the default Content to the Style
<ComboBox IsEditable="True" Name="comboWell" ItemsSource="{Binding }">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="Content" Value="{Binding wellId}" />
<Style.Triggers>
<DataTrigger Binding="{Binding wellId}" Value="(settings)">
<Setter Property="Content" Value="Customize..." />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
DisplayMemberPath is actually a shortcut way of saying the item template should just be a TextBlock with it's Text bound to the DisplayMemberPath item, and I am guessing it was overwritting whatever you had in the Style.

Change property depending on property in other control - WPF

I want to use some kind of trigger to change a button in my window to IsEnabled = False when the textbox property Validation.HasErrors of my textbox is True.
Can I do this with Triggers of some kind on the Button?
If yes, some example would be nice!
You can do this by using a Style. If you want to tie it directly to the text box, you could do something like this:
<Style x:Key="DisabledOnErrors" TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding (Validation.HasErrors)}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
<TextBox x:Name="myTextBox" />
<Button Style="{StaticResource DisabledOnErrors}"
DataContext="{Binding ElementName=myTextBox}" />
This works if the button is not already binding to the DataContext for other properties. In this case, the Style is reusable for other button and text box pairings.
Yes, you can do this with triggers, but because you are referring to another element, you must use a DataTrigger rather than a normal Trigger:
<Button>
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding (Validation.HasError), ElementName=tb}" Value="True">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Note you must use a Style rather than the Button.Triggers collection, because Button.Triggers can contain only EventTriggers.

Categories