Reuse ToggleButton-Style in WPF - c#

I have the following ToggleButton-Style:
<Style x:Key="GraphToggleButtonStyle" TargetType="ToggleButton">
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource Graph}" />
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource Graph_Off}" />
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
I'm trying to reuse this for my ToggleButtons (it's a list) - so every item in my list has one ToggleButton. The problem is, that when I click on my ToggleButton, the icon of the buttons, which aren't clicked disappears. Only my clicked button shows the desired image... Am I doing something wrong with this style?
My ToggleButton-Implementation:
<ToggleButton Style="{StaticResource GraphToggleButtonStyle}"
ToolTip="{x:Static res:Resources.UseGraphToggle}"
Visibility="{Binding Selected,
Converter={StaticResource BoolToVisibilityConverter}}" />

You don't need to add child (Image) into Visual Tree whenever button IsChecked property changes.
Issue is Style's are shared by default unless you set x:Shared="False" and second any Visual can be added only in one Visual tree. If you add Visual in another Visual tree, it will be removed form previous Visual tree. Visual in your case is Image control.
In your case setting x:Shared="False" will work but that will break the re-usability feature of resources. So, I would suggest instead of creating new instance of Image control everytime, create Image control only once and in triggers change the Image Source.
<Style x:Key="GraphToggleButtonStyle" TargetType="ToggleButton">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Image x:Name="image"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsChecked,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=ToggleButton}}" Value="True">
<Setter TargetName="image" Property="Source"
Value="{StaticResource Graph}"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsChecked,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=ToggleButton}}" Value="False">
<Setter TargetName="image" Property="Source"
Value="{StaticResource Graph_Off}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>

The Image in your style is a visual. One visual can only be used once in the visual tree. Instead of reusing the style let WPF create a new one each time its referenced:
<Style x:Key="GraphToggleButtonStyle" TargetType="ToggleButton" x:Shared="False">
....

Related

Change property of controls child object by using Styles or DataTemplate in WPF application

Let's say I have a button and I have an image (though it could be any controls) inside of it:
<Button Width="150" Height="80">
<Image Source="..." Width="50" Height="50" />
</Button>
I want to change the style of button when mouse is over it and make it so that I could also change properties of the image inside when it happens.
<Style TargetType="Button">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Opacity" Value="0.5" />
<!-- Here I want to change alignment of the image inside the button if there is one -->
<Setter Property="Image.HorizontalAlignment" Value="Right" />
</Trigger>
</Style.Triggers>
</Style>
But it doesn't work this way, and for some reason any child controls start to change alignment, not only images but also text and other controls. The other things I tried just didn't compile. Probably DataTemplate should be used; I'm new to WPF and XAML and I still don't understand it enough and couldn't find what I need yet.
You should use HorizontalContentAlignment to set the content alignment of a button:
<Setter Property="HorizontalContentAlignment" Value="Right" />
Based on your comment below, I can give an example:
<Button Grid.Row="2" Width="150" Height="80">
<DockPanel Height="80" Width="150"><!-- A more complex control here-->
<Image Width="50" Height="50">
<Image.Source>
<BitmapImage DecodePixelWidth="50" UriSource="https://www.gravatar.com/avatar/9eae4ae63d70142e81f0365de42474ae?s=32&d=identicon&r=PG&f=1" />
</Image.Source>
</Image>
</DockPanel>
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
</Style.Triggers>
<Style.Resources>
<Style TargetType="Image"><!-- The style target to the image control only in the button control -->
<Style.Triggers>
<!-- Binding to IsMouseOver of the button-->
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType={x:Type Button}}}" Value="True">
<Setter Property="HorizontalAlignment" Value="Right" />
</DataTrigger>
</Style.Triggers>
</Style>
</Style.Resources>
</Style>
</Button.Style>
</Button>

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>

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}" />

ToggleButton Style only works on last ToggleButton

I'm trying to customize my ToggleButtons so that when checked they say 'Yes' in green and when not checked, say 'No' in red.
I've created the following style which is sitting in my Styles resource dictionary.
<!-- ToggleButtons -->
<Style x:Key="YesNoToggleStyle" TargetType="ToggleButton">
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Background" Value="SpringGreen" />
<Setter Property="Content">
<Setter.Value>
<TextBlock Text="Yes"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Background" Value="Crimson" />
<Setter Property="Content">
<Setter.Value>
<TextBlock Text="No"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
This works ... sort of. If the ToggleButton is the last one of either value, then it displays correctly. All previous buttons with the same value are blank. The height was also shrinking, but I fixed that with the 'Height' Setter above the triggers. To illustrate, when a new record is being created it looks like:
and after I've clicked buttons 1, 2, and 3 and 1 again:
I originally had the style referenced from the surrounding grid:
<Grid>
...
<Grid.Resources>
<Style BasedOn="{StaticResource YesNoToggleStyle}" TargetType="{x:Type ToggleButton}" />
</Grid.Resources>
But changing that so each ToggleButton references the style individually (<ToggleButton Style="{StaticResource YesNoToggleStyle}" ... />) hasn't made a difference.
I looked at Customizing the toggle state of a toggle button in wpf, and Override ToggleButton Style where the effect is the same, but they talk about external images, and my issues is all within wpf.
I also looked at the second answer to: i want to change backcolor of toggle button when toggle button ischecked and viceversa in WPF but a) I only have the blend + sketchflow preview that comes with VS2012, and b) i'm a total noob with blend and can't get from Select the "Checked State" to Reset the Background Color instruction in the answer (plus i'd be surprised if this task requires the blend tool).
Can anyone show me what to do to get multiple ToggleButtons to use the same style properly?
This works for me. Somewhere in Dictionary1.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="YesNoToggleStyle" TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource {x:Static ToolBar.ToggleButtonStyleKey}}">
<Style.Triggers>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Background" Value="Crimson" />
<Setter Property="Content" Value="No"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Background" Value="SpringGreen" />
<Setter Property="Content" Value="Yes"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
Note, that style is based on ToolBar.ToggleButtonStyle.
<Grid>
<Grid.Resources>
<ResourceDictionary Source="pack://application:,,,/Dictionary1.xaml"/>
</Grid.Resources>
<ItemsControl ItemContainerStyle="{StaticResource YesNoToggleStyle}">
<ToggleButton />
<ToggleButton />
<ToggleButton />
</ItemsControl>
</Grid>
try to replace Content property to ContentTemplate:
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="Yes"/>
</DataTemplate>
</Setter.Value>
</Setter>
In my case I wanted to have a "Locked" ToggleButton in a common dll defined and reused across my Apps.
Here's my result, which worked for me. Maybe someone find it useful (put this in a Resourcedictionary.xaml):
<BitmapImage x:Key="LockedLock"
UriSource="/...;component/Resources/Lock_closed_16p.png" />
<BitmapImage x:Key="OpenLock"
UriSource="/...;component/Resources/Lock_open_16p.png" />
<Style x:Key="LockButton"
TargetType="ToggleButton">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Image Source="{DynamicResource OpenLock }"
Width="12"
Height="12"
Name="contentimage" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ToggleButton , AncestorLevel=1, Mode=FindAncestor }, Path=IsChecked}"
Value="True">
<Setter Property="Image.Source"
TargetName="contentimage"
Value="{DynamicResource LockedLock }" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Credits to:
Setting Button's Content to <Image> via Styles
Setter Target Name not recognized

pass Validation error to UI element in WPF?

I am using IDataErrorInfo to validate my data in a form in WPF. I have the validation implemented in my presenter.
The actual validation is happening, but the XAML that's supposed to update the UI and set the style isn't happening.
Here it is:
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
The problem is that my binding to Validation.Errors contains no data. How do I get this data from the Presenter class and pass it to this XAML so as to update the UI elements?
EDIT:
Textbox:
<TextBox Style="{StaticResource textBoxInError}" Name="txtAge" Height="23" Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Width="150">
<TextBox.Text>
<Binding Path="StrAge" Mode="TwoWay"
ValidatesOnDataErrors="True"
UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
The validation occurs, but the style to be applied when data is invalid is not happening.
Have you watched the output window as your form is being bound? a significant number of validation issues can be found by reviewing the output as the binding occurs.
One quick note as well:
use
Path=(Validation.Errors).CurrentItem.ErrorContent
rather than
Path=(Validation.Errors)[0].ErrorContent
It will save you some further binding excecption when a valid value is provided to the control
I noticed that your Style is not completely finished.
The Style needs a control template that defines a "Validation.ErrorTemplate" for it to work when a validation error occurs. Try making the following changes to see how it goes.
Paul Stovell has a very good article on WPF validation here that will cover most things you need. I have also written an article here to simplify validation that you might also like.
BEFORE
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
AFTER
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>

Categories