WPF CustomControl switch content of ContentControl - c#

<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomControlLibrary1">
<ContentControl x:Key="BackSide" Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Back}" RenderTransformOrigin="0.5,0.5">
<ContentControl.RenderTransform>
<ScaleTransform ScaleX="-1" />
</ContentControl.RenderTransform>
</ContentControl>
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<ContentControl Grid.Row="1">
<ContentControl.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="tf" ScaleX="1" />
</TransformGroup>
</ContentControl.RenderTransform>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Content" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Front}" />
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<Binding ElementName="tf" Path="ScaleX">
<Binding.Converter>
<loc:LessThanXToTrueConverter X="0" />
</Binding.Converter>
</Binding>
</DataTrigger.Binding>
<DataTrigger.Setters>
<Setter Property="Content" Value="{StaticResource BackSide}"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This is some XAML code from a customcontrol. Where there are two dependency properties (Front and Back).
Via my DataTrigger I want to change the Content of the ContentControl from using "Front" to using "Back".
At first it shows the depencency property "Front" and then it should use the depencency property "Back" as the Content.
This is done via this code:
<DataTrigger.Setters>
<Setter Property="Content" Value="{StaticResource BackSide}"/>
</DataTrigger.Setters>
But this doesn't work...
I can bind and display the content of the Front dependency property in my control via:
<Setter Property="Content" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Front}" />
but I can't figure out how to bind the DataTrigger setter so that it uses the ContentControl's Content with the x:Key="BackSide" ContentControl.
Thanks in advance.

You said that this XAML works:
<Setter Property="Content" Value="{Binding RelativeSource={RelativeSource
TemplatedParent}, Path=Front}" />
So why don't you just try putting this XAML into your DataTrigger?:
<Setter Property="Content" Value="{Binding RelativeSource={RelativeSource
TemplatedParent}, Path=Back}" />
UPDATE >>>
I'm not really sure what you're trying to do with your UIElement DependencyPropertys, but I am guessing that you are taking the wrong approach. Typically in WPF, our properties are data types, not UI types and then we generate UI types using DataTemplates... perhaps you should re-think your approach.
Please see the Data Templating Overview page on MSDN for further help.
You might also find my answer to the WPF MVVM navigate views question helpful to read.

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>

TemplatedParent is null when used inside a ControlTemplate's DataTrigger

Consider this (edited-down) Style, designed for a Button whose Content is a String:
<Style x:Key="Test" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<StackPanel>
<TextBlock x:Name="text" Text="{TemplateBinding Content}" />
<TextBlock x:Name="demo" Text="{Binding RelativeSource={RelativeSource TemplatedParent}}" />
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}">
<DataTrigger.Value>
<system:String>Test</system:String>
</DataTrigger.Value>
<Setter TargetName="test" Property="Foreground" Value="Red" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The intention in this example is to turn the button text red if it equals the word "Test"1. But it doesn't work, because the trigger's TemplatedParent binding resolves to null instead of to the Button the Style is applied to. However, the TextBlock named "demo" will have its Text set to "System.Windows.Controls.Button: [ButtonText]" as expected, which means TemplatedParent works correctly at that level. Why doesn't it work inside the DataTrigger?
1 I know there are other ways to achieve that, but I'm trying to understand why the binding doesn't work the way I expect it to.
TemplatedParent in your ControlTemplate.Triggers is not what you expect. Inside trigger it actually references Button.TemplatedParent. As such, it will only be non-null if your create that button inside template. You don't create button inside template, so it is null in your case. Now consider this xaml:
<Window.Resources>
<Style x:Key="Test"
TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<StackPanel>
<TextBlock x:Name="text"
Text="dummy" />
<TextBlock x:Name="demo"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}}" />
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}">
<DataTrigger.Value>
<system:String>Test</system:String>
</DataTrigger.Value>
<Setter TargetName="text"
Property="Foreground"
Value="Red" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="Test2" TargetType="ContentControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<Button Style="{StaticResource Test}"></Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<!--<Button Content="Test" Style="{StaticResource Test}"/>-->
<ContentControl Style="{StaticResource Test2}" Content="Test" />
</Grid>
Here I retemplate ContentControl and inside template I use button with your template. If you run this code, you will see "dummy" text in red, because Button.TemplatedParent is now ContentControl, and it has it's Content equals "Test", which confirms what I said above.
Now back to your problem: just change RelativeSource TemplatedParent to RelativeSource Self (no need to change DataTrigger to Trigger) - this one would reference your Button.
I think it might be a similar issue in .NET Core WPF.
My DataTrigger was not firing with {Binding MyProp, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource Convert}}, but instead when I changed the RelativeSource to Self the binding started to work. I'm not sure whether it's a hack or a solution, but it worked.
Maybe it's worth mentioning that my template was based on MyView (see below) and I was binding to a DependencyProperty on MyView.
So my final code looked like this:
<ControlTemplate x:Key="Template" TargetType="{x:Type ns:MyView}">
<!-- Some template -->
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding MyProp, RelativeSource={RelativeSource Self}, Converter={StaticResource Convert}}" Value="True">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
I'm not quite sure, but I think the trigger equals by referenc, because Content returns an Object. So it will never be true with your string defined within the trigger.

Use an image for a menu option in WPF

I use the following XAML to achieve a menu using radio buttons:
<Grid.Resources>
<Style TargetType="{x:Type RadioButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RadioButton}">
<TextBlock Text="{TemplateBinding Content}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="White" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, RelativeSource={RelativeSource AncestorType={x:Type RadioButton}}}" Value="True">
<Setter Property="Foreground" Value="Gold" />
</DataTrigger>
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType={x:Type RadioButton}}}" Value="True">
<Setter Property="Foreground" Value="Gold" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<StackPanel Margin="25,74,644,78" Background="{x:Null}">
<RadioButton Content="1. Do something." Click="RadioButton1_Click" FontSize="16" Margin="5"/>
<RadioButton Content="2. Do something else." Click="RadioButton2_Click" FontSize="16" Margin="5"/>
</StackPanel>
This works fine but I want to include an image as a menu option instead of text. (The image is for 'Home' and it will have a normal image and a hover over image)
Here is what I want to achieve:
How do I do this?
You can define another style for displaying Radio Button as an Image based on the existing style posted in question, just need slight changes in ControlTemplate's content part. Following is an example of such a Style and the usage :
<StackPanel Orientation="Horizontal" Background="Black">
<StackPanel.Resources>
<Style x:Key="PictSTyle" TargetType="{x:Type RadioButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RadioButton}">
<Image Width="50" Height="50">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="home_default.png" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType={x:Type RadioButton}}}" Value="True">
<Setter Property="Source" Value="home_hovered.png" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Resources>
<RadioButton Margin="5" Style="{StaticResource ResourceKey=PictSTyle}"/>
</StackPanel>
To give a menu item an image:
<MenuItem>
<MenuItem.Icon>
<Image Source=""/>
</MenuItem.Icon>
</MenuItem>
for your trigger just change the MenuItem's icon to the "highlighted one", when the user hovers over the menu item (IsMouseOver), by changing the Icon's image source.
Put an image element as the "Content" property for your radio button
<RadioButton Click=HomRadioButtonClick>
<RadioButton.Content>
<Image Source=""/>
</RadioButton.Content>
</RadioButton>
You'll need some extra logic for the hover behavior, but that should at least get the image in your menu.

Wpf listbox itemstlye triggers not working

I am using a listbox itemstyle and triggers to change the mouseover color but the style does not seem to work:
<ListBox ItemsSource="{Binding LocationItems}" SelectionChanged="RadListBox_SelectionChanged" Name="RLBLocations">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="BorderBrush" Value="#FF709A70"/>
<Setter Property="Height" Value="50"/>
<Setter Property="Width" Value="200"/>
<Setter Property="Foreground" Value="#FF5C5C5C"/>
<Setter Property="FontFamily" Value="Franklin Gothic Book"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<StackPanel Orientation="Horizontal" Height="50">
<Image Source="{Binding Icon}" Margin="0" Stretch="UniformToFill" Width="32" Height="32"/>
<TextBlock Margin="10,10,0,0" Text="{Binding Text.Text}" Foreground="Black" TextAlignment="Left"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ToolTip" Value="{Binding ToolTip}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Background" Value="#FFF0F0F0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBoxItem}},
Path=IsMouseOver}" Value="True">
<Setter Property="Background" Value="#FF709A70"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
I get this error in the output window:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ListBoxItem', AncestorLevel='1''. BindingExpression:Path=IsMouseOver; DataItem=null; target element is 'ListBoxItem' (Name=''); target property is 'NoTarget' (type 'Object')
I understand that the error but need help trying figure out the style - I have tried doing it with a datatemplate and that did not work out either.
Your DataTrigger:
<DataTrigger Binding="{Binding RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBoxItem}},
Path=IsMouseOver}"
Value="True">
<!-- ... -->
</DataTrigger>
Is trying to find the Ancestor (of Type ListBoxItem) of the ListBoxItem. Of course that doesn't exist unless you nest ListBoxItems inside of each other.
Change it to this:
<Trigger Property="IsMouseOver" Value="True">
<!-- ... -->
</Trigger>
Also, you're overriding the ListBoxItem's Template, and in your custom template there's nothing pointing to the ListBoxItem.Background property, that brush will never be visible on screen.
I suggest you use it for the StackPanel's Background:
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<StackPanel Orientation="Horizontal" Height="50" Background="{TemplateBinding Background}">
<!-- ... -->
</StackPanel>
</ControlTemplate>
Also, I think you might be confusing the concept of ListBoxItem.Template with ListBox.ItemTemplate, which is a DataTemplate used to render your Data inside regular ListBoxItems. I suggest you take a look at this tutorial for more info.

Change fontweight on button in WPF treeview

I have a templated treeview that is working just as i want, but i have a context menu which allows for a tier 1 node to be marked as "default".
I have a datatrigger which reacts to a property in the viewmodel, which should change the fontweight to bold just to visually show that this is the default node. But the setter will not change the fontweight on the button no matter what!
However if i change another value, like the foreground color for example, it works just fine, font size also no problem.
Why is this, can anyone explain this? Here is some code if needed:
Trigger:
<HierarchicalDataTemplate ItemsSource="{Binding Children,Mode=TwoWay,NotifyOnSourceUpdated=True}">
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<Image x:Name="nodeImg" Source="{Binding Image}" MaxHeight="16" MaxWidth="16"/>
<Button x:Name="nodeButton" Content="{Binding Name}" Command="{Binding Command}" Style="{StaticResource TreeMenuButton}"/>
</StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem},Path=IsExpanded}" Value="True">
<Setter TargetName="nodeImg" Property="Source" Value="{Binding ImageExpanded}"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding IsDefaultConnection}" Value="True">
<Setter TargetName="nodeButton" Property="FontWeight" Value="Bold"></Setter>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
Default style on button:
<Style x:Key="TreeMenuButton" TargetType="{x:Type Button}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="MinHeight" Value="23" />
<Setter Property="MinWidth" Value="75" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="Border"
CornerRadius="0"
BorderThickness="0"
Background="Transparent"
BorderBrush="Transparent">
<ContentPresenter Margin="2"
HorizontalAlignment="Left"
VerticalAlignment="Center"
RecognizesAccessKey="True"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I'd be surprised if your code even built as you can't add a DataTrigger into a UIElement.Triggers collection. Try using a Style instead (this definitely works):
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsDefault}" Value="True">
<Setter Property="TextBlock.FontWeight" Value="Bold" />
</DataTrigger>
</Style.Triggers>
</Style>
If by some miracle your DataTemplate does work in a UIElement.Triggers collection, then try using the class name as well as the property name:
<Setter Property="TextBlock.FontWeight" Value="Bold" />

Categories