WPF/XAML: accessing element outside ControlTemplate from ControlTemplate.Trigger - wpf

I have a WPF application that has a light and a switch. When I press the switch the switch and light should change to its "ON" image and when I press again they should change to their "OFF" images. I have a single restriction: I can only do this strictly in XAML and therfore no code-behind files.
The way I do this is to redefine the control template for ToggleButton. Only the light switch is in this control template (the light itself shouldn't be clickable), and that is apparently my problem. I can't access the light switch from inside the control templates triggers. I get the following error "Cannot find the Trigger target 'lightImage'. (The target must appear before any Setters, Triggers, or Conditions that use it.)"
Heres my code:
<Image Name="lightImage" Source="Resources/LOFF.bmp" Stretch="None" Canvas.Left="82" Canvas.Top="12"/>
<ToggleButton Canvas.Left="169" Canvas.Top="123">
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<Image Name="switchImage" Source="Resources/SUp.bmp"/>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="switchImage" Property="Source" Value="Resources/SDown.bmp" />
<Setter TargetName="lightImage" Property="Source" Value="Resources/LON.bmp"/>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter TargetName="switchImage" Property="Source" Value="Resources/SUp.bmp"/>
<Setter TargetName="lightImage" Property="Source" Value="Resources/LOFF.bmp"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
Is there another way to do this?
Cheers

You seem to have "onImage", but trying to reference "lightImage"?
Edit: since those triggers are inside your control template I think it looks for "lightImage" only inside that template. You should create a property for 'source' in the code behind and bind to that both in your image and button.
Edit2: if no code behind maybe you could try some relative binding along the lines of:
{Binding RelativeSource={RelativeSource
FindAncestor, AncestorType={x:Type Canvas}},
Path=lightImage.Source}
Sorry if this is completely stupid, I use Silverlight and this is only available in WPF, so only a wild guess!
Anyway, idea comes from this cheatsheet, seems you can have quite complex bindings in WPF, so worth trying a few different ones: http://www.nbdtech.com/Free/WpfBinding.pdf

Finally, I fixed it. I didn't consider that you could use the "IsHitTestVisible" property on the image I didn't want to be clickable. With that property I could just put the lightImage inside the controltemplate and voila.
Heres the code:
<ToggleButton Canvas.Left="81" Canvas.Top="20">
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<Canvas>
<Image x:Name="lightImage" Source="Resources/LOFF.bmp" IsHitTestVisible="False" />
<Image x:Name="switchImage" Source="Resources/SUp.bmp" Canvas.Left="88" Canvas.Top="100"/>
</Canvas>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="lightImage" Property="Source" Value="Resources/LON.bmp"/>
<Setter TargetName="switchImage" Property="Source" Value="Resources/SDown.bmp"/>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter TargetName="lightImage" Property="Source" Value="Resources/LOFF.bmp"/>
<Setter TargetName="switchImage" Property="Source" Value="Resources/SUp.bmp"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>

Related

WPF override declared style in vb.net

I've declared the below style. How can I override the style foreground color dynamically in my vb.net?
<Style x:Key="LabelWinner" TargetType="{x:Type Label}">
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect Color="#FF000000" ShadowDepth="6" />
</Setter.Value>
</Setter>
<Setter Property="Foreground" Value="#FFFF0000"/>
</Style>
As mentioned in the comment #nit, In WPF have a powerful system behavior properties in the form of Style.Triggers.
Earlier, in WinForms to change a specific property, we had to do it through the code that was not quite comfortable and practical. The developers of WPF decided to separate the visual logic related to the appearance of the program, and business logic, which contains the desired behavior of the program. Actually, it was a Style.
To set the Style trigger, you need to select the appropriate properties. The trigger is as follows:
<Trigger Property="SomeProperty" Value="SomeValue">
... Some actions by way of setters...
</Trigger>
For example, we want to see, when you hover the mouse cursor changes Foreground color and FontSize. Then we choose the property IsMouseOver, and then write a Trigger:
<Style x:Key="LabelWinner" TargetType="{x:Type Label}">
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect Color="#FF000000" ShadowDepth="6" />
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="Green" />
<Setter Property="FontSize" Value="14" />
</Trigger>
</Style.Triggers>
</Style>
It should be remembered, that in WPF have a list of value precedence (MSDN), that the local value of a higher priority than the trigger style. Therefore, if you value for property of Label will be set locally, the trigger will not be able to change it, for example:
<Label Foreground="Red" ... /> <!-- Trigger don't change foreground -->
If the standard property are missing, or the need to implement your scenario, then it have the attached dependency property (MSDN). Inside it, you can set any condition, for example to start the animation and the trigger in the style it will work.
Example of trigger with attached dependency property:
<Trigger Property="local:YourClass.MyProperty" Value="True">
<Setter TargetName="SaveButton" Property="Background" Value="AliceBlue" />
</Trigger>

Use trigger to change colour of a path declared in ResourceDictionary

I'm creating a radiobutton style. The RadioButton has a Border which hosts a ContentControl. The ContentControl has its Content property set to a Path (FemaleVector) declared in a separate ResourceDictionary. How can I change the Fill property of the path when the radiobutton IsChecked? Below is what I have so far. I am able to change the background property of the border but setting the Foreground property of the ContentControl does not change the colour of the path. (Didn't think that would work.)
<Style x:Key="Female" TargetType="{x:Type RadioButton}">
<Setter Property="Margin" Value="0,0,5,0"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RadioButton}">
<Border x:Name="border" Padding="7,3,7,3" Width="35" Height="35" BorderBrush="#8CD567DC" Background="#00D567DC" CornerRadius="5" BorderThickness="0.8">
<ContentControl x:Name="content" Content="{DynamicResource FemaleVector}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Background" TargetName="border" Value="#8CD567DC"/>
<Setter Property="Foreground" TargetName="content" Value="Blue"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I don't like having the long Data property of paths in my Styles, so I have moved them into a separate ResourceDictionary. Should I just put the Path back into my Style instead of keeping it in a separate ResourceDictionary?
Edit: similar questions are here and here.
If the style is not reused somewhere, I would personally keep it in local style resources section. That way you see bigger picture. Otherwise it would be wise to keep it in ResourceDictionary :)
Either way you should be able to change Fill property with:
<Setter Property="Content.Fill" TargetName="content" Value="Blue"/>
If this is not working, I advise few ways more:
You can use Trigger.EnterActions<> in Xaml. Perhaps setting property through animation will have better effect? ControlTemplate triggers with setters are sometimes way limiting.
There's also relative binding. But you gotta be careful with that. (If you pla to make it reusable)
In your FemaleVector style, you can bind Fill against ContentControl Foreground. Look for RelativeBinding in Google.
And then there's property inheritance. If you set Fill color in FemaleVector, you need to do it with style. Such as:
<Style>
<Setter Property="Fill" Value="BLACK" />
</Style>
you can later set ContentControls Style and add trigger there, like:
<ContentControl.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding={Binding IsChecked, RelativeSource={RelativeSource AncestorType=RadioButto}} Value=TRUE>
<Setter Property="Path.Fill" Value="BLACK" />
</DataTrigger>

How to use custom properties in a ControlTemplate trigger

I have a class derived from slider which uses a custom control template and has a few added dependency properties. I would like to fire triggers within the template based on the new properties.
For example, I have a new dependency property called ThumbIsVisible which when set to false I want just the thumb portion of my slider to be hiddin. My control template looks like:
<Slider.Template>
<ControlTemplate TargetType="{x:Type Slider}">
...
<Track.Thumb>
<Thumb x:Name="m_Thumb" Style="{StaticResource SliderThumbStyle}" />
...
I would like to add in a trigger that looks like:
<ControlTempate.Trigger>
<Trigger Property="ThumbIsVisible" Value="False">
<Setter TargetName="m_Thumb" Property="Visibility" Value="Collapsed" />
Right off the bat I can see this won't work as I have the control tempate's target type set to Slider. However, if I change that to say:
<ControlTemplate TargetType="{x:Type local:myCustomSlider}">
then I run into problems with the template type differing from the controls. The only way around this is to create the xaml using the local:myCustomSlider as the type instead of Slider. However, doing this causes lots of problems with VisualStudio's designer and code behind.
Does anyone know if there is a standard way to get around all of this? As a workaround I tried adding to the template's triggers via code-behind but have not been able to get that to work.
Of course it only takes me 30 minutes after posting my question to find the answer when I spent two days looking for it first. Oh well, the solution is to use DataTriggers.
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=ThumbIsVisible}" Value="False">
<Setter TargetName="m_Thumb" Property="Visibility" Value="Hidden" />
</DataTrigger>
</ControlTemplate.Triggers>
The key is to use the RelativeSource={RelativeSource Self} to find the custom property. After that it works exactly as expected.
It looks like there is an even simpler way to solve this problem.
<ControlTemplate.Triggers>
<Trigger Property=local:CustomSlider.ThumbIsVisible" Value="False">
<Setter TargetName="m_Thumb" Property="Visibility" Value="Hidden" />
</Trigger>
</ControlTemplate.Triggers>
where local is the namespace of the CustomSlider class.

using control templates that vary only slightly

i have the following control template:
<ControlTemplate x:Key="GrayButton" TargetType="{x:Type Button}">
<Grid>
<Image x:Name="GrayButtonImage" Source="/Server;component/Images/bg.bmp"/>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White" Text="{x:Static props:Resources.IDS_ABORT}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter TargetName="GrayButtonImage" Property="Source" Value="/Server;component/Images/GrayButtonOn.bmp"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="GrayButtonImage" Property="Source" Value="/Server;component/Images/GrayButton.bmp"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
and here's one button using the control template:
<Button Height="40" HorizontalAlignment="Left" Margin="250,334,0,0" Name="ejf" VerticalAlignment="Top" Width="106" Template="{StaticResource GrayButton}" Click="execJournalPrgm" IsEnabled="False"/>
I need roughly 4-8 more buttons that vary only by text name/color... what is the best way to do this without repeating the control template definition 4-8 times?
any help would be greatly appreciated.
One way to do this is by making a custom control (by inheriting from Button) and setting up all the necessary properties which should be bound in the default template. Then you only need to create instances of that control and set those properties instead of changing anything in the template.

In WPF, how do I bind a textbox style to two different styles that toggle on a button click?

I'm working in a MVVM paradigm, and I'm trying to get a textbox to change styles when a button is triggered.
I have a file named TextBoxStyles and think that I might be creating a placeholder style that has a trigger that changes based on a back end boolean. Correct me if I'm wrong but the backend boolean is really straight forward:
public void ChangeStyleButtonBoolean()
{
_changeStyleButtonBoolean = true;
}
But how do I:
1) bind the style to this boolean, and
2) then select two different styles based on that boolean?
Could you use a toggle button and have a trigger? We're currently doing something similar by changing the image inside of the template for a toggle button. Something like this:
<ControlTemplate x:Key="LightBulbToggleButtonTemplate" TargetType="{x:Type ToggleButton}">
<Image Name="LightBulbButton" Source="{StaticResource LightBulbOn}" Width="24" Height="24" Cursor="Hand" />
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Source" Value="{StaticResource LightBulbOn}" TargetName="LightBulbButton"/>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Source" Value="{StaticResource LightBulbOff}" TargetName="LightBulbButton"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

Resources