How to use custom properties in a ControlTemplate trigger - wpf

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.

Related

How can I create a button where caption changes based on boolean in viewmodel but button is based on a style resource?

Perhaps I'm missing something obvious, but I can't figure this one out... I have a button style defined in App.xml that presents a "flat" looking button:
<Style x:Key="FlatButton" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="ButtonBorder" BorderThickness="2" BorderBrush="{DynamicResource color_Logo}" Padding="5,3,5,3" HorizontalAlignment="Stretch" Background="White">
<TextBlock x:Name="ButtonText" FontSize="12" Foreground="{DynamicResource color_Logo}" HorizontalAlignment="Center" VerticalAlignment="Center"><ContentPresenter /></TextBlock>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="TextBlock.FontWeight" Value="Bold" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="BorderBrush" Value="Gray" TargetName="ButtonBorder" />
<Setter Property="Foreground" Value="Gray" TargetName="ButtonText" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="{DynamicResource color_LogoLight}" TargetName="ButtonBorder"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Works perfectly fine throughout my app. I now need to create a button with this style but where the content, or caption, of the button changes based on the state of a boolean value in the viewmodel. I've tried several iterations and I either end up with a complaint from the compiler that the style property is already set or I just see the object type name as the caption.
I suppose I could create a text property in the viewmodel that exposes the proper caption, but this seems to violate the separation of concerns in MVVM. While I know it's not an arrest-able offense if I do that, my viewmodel shouldn't care about the way the UI presents something, right? It just exposes the state of the object and the UI makes the decision on how to present it.
Another option is to create two buttons and hide the one that is not appropriate, based on the viewmodel boolean. This seems to conform to the MVVM pattern a bit better but I feel like I should be able to do this with a trigger on a single button.
Is it possible to override part of the style resource?
I'm going with the two-button solution, at the moment, but I'd just like to know what I'm missing.
Thanks.
J
Is it possible to override part of the style resource?
No. You can indeed base a Style on another Style and override specfic setters but you cannot "override" only a part of a ControlTemplate. Unfortunately you must then (re)define the entire template as a whole:
WPF: Is there a way to override part of a ControlTemplate without redefining the whole style?
I suppose I could create a text property in the viewmodel that exposes the proper caption, but this seems to violate the separation of concerns in MVVM.
Well, not at all. The very name View Model implies being a model to the View. Of course it depends on a handful of design choices, but this is not a violation of MVVM, in my opinion.
Besides that, I would go with a DataTrigger (defined in Style.Triggers) where you can set specific, view-only strings given the value of your boolean property.
And, please, don't go with the two-button solution, that is closer to an arrestable offense ;o)

How to modify legacy named style for having different setters based on targetTypes?

I have this named style
<Style x:Key="validationSupport" TargetType="{x:Type Control}">
<Setter Property="Margin" Value="5,2,14,2" />
...OMISSIS...
<Style.Triggers>
...OMISSIS...
<DataTrigger Binding="{Binding DataContext.ActiveWorkspace.Editable, RelativeSource={RelativeSource AncestorType=Window}}" Value="False">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
I use it extensively for TextBoxes, ComboBoxes, DatePickers etc, so I used as TargetType a super class for all these elements, Control.
Now I would like to differentiate the setter inside the dataTrigger using specific properties that 'Control' doesn't have. It seems I have to create different styles with different names,each for every targetType I want to differentiate, but that way I have to change the style name inside all elements which use it. Is there a smarter way to achieve that goal ? I don't want want to go and modify every xaml file I have.
Update after first answer
I have tried to put the following setters inside the datatrigger:
<Setter Property="Background" Value="#FFECECF8" />
<Setter Property="CheckBox.IsEnabled" Value="False" />
<Setter Property="DatePicker.IsEnabled" Value="False" />
<Setter Property="ComboBox.IsEnabled" Value="False" />
<Setter Property="TextBox.IsReadOnly" Value="True" />
Unfortunately the tests gave odd results. The IsEnabled property is set for TextBoxes too despite the prefix should limit its application to CheckBoxes, DatePickers and ComboBoxes.
My final need was to make some control contents unchangeable avoiding the difficult to read colors associated with disabled controls. From previous researches I understood that changing the colors for a 'disabled' control is not an easy task and involves the redefinition of the control template. So I thought to apply a combination of IsReadOnly and Background, but it is not applicable for the above problem. In fact CheckBoxes, DatePickers and ComboBoxes can only be made unchangeable using the IsEnabled property.
Am I missing something ?
There is a way, but I have to warn you - this is far from best-practice and should be avoided
WPF allows you to use desired type as a prefix for the property. That way, if you apply the style to a control that doesn't inherit from the prefixed type - the setter is ignored.
<Style x:Key="validationSupport" TargetType="{x:Type Control}">
<Setter Property="Margin" Value="5,2,14,2" />
...OMISSIS...
<Style.Triggers>
...OMISSIS...
<DataTrigger Binding="{Binding DataContext.ActiveWorkspace.Editable, RelativeSource={RelativeSource AncestorType=Window}}" Value="False">
<Setter Property="IsEnabled" Value="False" />
<Setter Property="Button.Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
[Test this extensively, since I suspect that it might create memory leaks.]

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

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>

WPF ComboBox - showing something different when no items are bound

I have a ComboBox, and i want to change its look when the ItemsSource property is null. When it is in that state, i want to show a TextPanel with the text "Retrieving data" in it, and give it a look kind of similar to the watermarked textbox.
I figure to do this i need a ControlTemplate, and a trigger. I have the ControlTemplate here:
<ControlTemplate x:Key="LoadingComboTemplate" TargetType="{x:Type ComboBox}">
<Grid>
<TextBlock x:Name="textBlock" Opacity="0.345" Text="Retrieving data..." Visibility="Hidden" />
</Grid>
<!--
<ControlTemplate.Triggers>
<Trigger Property="ComboBox.ItemsSource" Value="0">
<Setter Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
-->
</ControlTemplate>
but my issue is how do i set up the trigger to show this when the ItemsSource property is null? I have tried a couple of different ways, and each way has given me the error message "Value 'ItemsSource' cannot be assigned to property 'Property'. Invalid PropertyDescriptor value.". My ComboBox xaml is this (including the attempted trigger):
<ComboBox Margin="112,35,80,0"
Name="MyComboBox"
Height="22.723"
VerticalAlignment="Top"
DisplayMemberPath="FriendlyName"
SelectedValuePath="Path"
TabIndex="160"
>
<Trigger>
<Condition Property="ItemsSource" Value="0" />
<Setter Property="Template" Value="{StaticResource LoadingComboTemplate}" />
</Trigger>
</ComboBox>
now should the trigger go on the ComboBox, or on the ControlTemplate? How do i access the ComboBox's ItemsSource property? Should i even be using a trigger?
Thanks!
Try putting {x:Null} for the value of the condition instead of 0.
Also I got it working by moving the Trigger to a style and modifing it slightly, see below.
<Style TargetType="ComboBox" x:Key="LoadingComboStyle">
<Style.Triggers>
<Trigger Property="ItemsSource" Value="{x:Null}">
<Setter Property="Template" Value="{StaticResource LoadingComboTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
<ComboBox Style="{StaticResource LoadingComboStyle}" .... >
The reason it only works in a style, is that only EventTriggers are allowed in the triggers collection directly on the Framework Element. For property triggers (like above) you need to use a style (I learn something every day).
See FrameworkElement.Triggers
Note that the collection of triggers established on an element only supports EventTrigger, not property triggers (Trigger). If you require property triggers, you must place these within a style or template and then assign that style or template to the element either directly through the Style property, or indirectly through an implicit style reference.

How do I trigger a style change if DataContext is null or not using WPF

I have a page with several controls. The controls are bound to display values which they get from the page's DataContext. What I would like to do is display another look of the page should the DataContext be null. In some cases the controls of the page should display differently if "their" property is set or not.
Is is possible to create a binding to see if the DataContext is set?
What I did as a workaround was to add a IsDataContextSet property to the page and the specify a binding like:
Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Page}}, Path=IsDataContextSet}" Value="false"
This works as I expect but I have a feeling that their is more elegant way to do this. Or at least or more WPFish way.
Given the scenario you describe, I would set the properties with a style and a data trigger. The data trigger would use the default binding which is the data context.
An example might look like this:
<Border>
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background"
Value="Orange" />
<Style.Triggers>
<DataTrigger Binding="{Binding}"
Value="{x:Null}">
<Setter Property="Background"
Value="Yellow" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
The border will be orange unless the data context is null, in which case the background is yellow.

Resources