How to reference the parent control from a style ControlTemplate in WPF? - wpf

I have a simple style for label controls. I'd like to define a control template inside the style with a button, which could be clicked and would set the visibility property of the label to 'Hidden'. Something like this:
<Style x:Key="MessageLabel_WithCloseButton" TargetType="{x:Type Label}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Label}">
<Border BorderThickness="1" Padding="4" CornerRadius="3"
BorderBrush="Gray" Background="#FFA11616">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0"/>
<Button Grid.Column="1" Width="16" Height="16" Padding="2" FontSize="9" Content="X">
<!-- THIS IS WRONG! HOW TO CREATE A TRIGGER FOR THIS BUTTON
HERE AND HOW TO REFER TO THE LABEL? -->
<Button.Triggers>
<Trigger Property="Button.IsPressed" Value="True">
<Setter Property="Visibility" Value="Hidden" />
</Trigger>
</Button.Triggers>
</Button>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The problem is I'm not sure how to handle the click with a trigger and also how to set a property of the label that contains the button.
Thanks.

I authored this with Blend 4. Essentially you want to handle the "PreviewMouseButtonUp" event on your button with an EventTrigger. The EventTrigger will start a Storyboard which animates the UIElement.Visibility property to "Hidden" at the top of the Visual Tree for your label's style.
To get control over the content in the button, you can use the Tag Property on the label control. Otherwise, you will have to create another dependency property, and that means subclassing Label.
Inside the style, then, the <Button/> looks like this:
<Button x:Name="button" Grid.Column="1" Padding="2"
FontSize="9"
Content="{Binding Path=Tag, RelativeSource={RelativeSource TemplatedParent}}"/>
...and since you can put anything into a Tag property you can do this:
<Label x:Name="label" Content="Label"
Style="{DynamicResource MessageLabel_WithCloseButton}">
<Label.Tag>
<StackPanel>
<TextBlock>WOOT</TextBlock>
<TextBlock>WOOT</TextBlock>
</StackPanel>
</Label.Tag>
</Label>
Here is a modified complete style (I also modified some things for better automatic sizing:
<Style x:Key="MessageLabel_WithCloseButton" TargetType="{x:Type Label}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Label}">
<ControlTemplate.Resources>
<Storyboard x:Key="OnClick1">
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="border">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Hidden</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</ControlTemplate.Resources>
<Border x:Name="border" BorderThickness="1" Padding="4" CornerRadius="3"
BorderBrush="Gray" Background="#FFA11616">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0" VerticalAlignment="Center" Margin="0,0,3,0"/>
<Button x:Name="button" Grid.Column="1" Padding="2" FontSize="9" Content="{Binding Path=Tag, RelativeSource={RelativeSource TemplatedParent}}"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="UIElement.PreviewMouseLeftButtonUp" SourceName="button">
<BeginStoryboard x:Name="OnClick1_BeginStoryboard" Storyboard="{StaticResource OnClick1}"/>
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Note, too, that the EventTrigger is on your ControlTemplate and not on the Button in your tree. But that might be just the way Blend generates the code.

You can use a ToggleButton instead of the normal Button and then just use the IsChecked property for the Trigger:
<Style x:Key="MessageLabel_WithCloseButton" TargetType="{x:Type Label}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Label}">
<Border BorderThickness="1" Padding="4" CornerRadius="3"
BorderBrush="Gray" Background="#FFA11616">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0"/>
<ToggleButton x:Name="CloseButton" Grid.Column="1" Width="16" Height="16" Padding="2" FontSize="9" Content="X"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger SourceName="CloseButton" Property="IsChecked" Value="True">
<Setter Property="Visibility" Value="Hidden" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Related

How to change the borderbrush on a Mouse Over event in WPF

I am trying to change the color of the BorderBrush when the mouse is hovering over the button. I have created a new control template for the button however when setting up the triggers visual studio tells me i have got to use an EventTrigger but when i use this there is no MouseOver event, only a MouseEnter event. When applying this and running the solution the border brush does not change. Any Suggestions?
EDIT:
Code I am having the issue with:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.Resources>
<ControlTemplate x:Key="LogInButton">
<Grid Width="AUTO" Height="AUTO">
<Border x:Name="ButtonBorder"
BorderBrush="#B7B7B7"
BorderThickness="2"
Background="Transparent"
Cursor="Hand">
<Image Source="{Binding Tag, RelativeSource={RelativeSource TemplatedParent}}"/>
<Border.Triggers>
<EventTrigger RoutedEvent="MouseMove">
<EventTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="ButtonBorder"
Storyboard.TargetProperty="BorderBrush"
To="White"
Duration="0"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.EnterActions>
</EventTrigger>
</Border.Triggers>
</Border>
</Grid>
</ControlTemplate>
</Grid.Resources>
<Button Template="{StaticResource LogInButton}" Tag="Images/Login.png" Height="50" Width="50" Background="Transparent">
</Button>
</Grid>
You could just add a Trigger to <ControlTemplate.Triggers> instead of adding an EventTrigger to <Border.Triggers>:
<ControlTemplate x:Key="LogInButton">
<Grid Width="AUTO" Height="AUTO">
<Border x:Name="ButtonBorder"
BorderBrush="#B7B7B7"
BorderThickness="2"
Background="Transparent"
Cursor="Hand">
<Image Source="{Binding Tag, RelativeSource={RelativeSource TemplatedParent}}"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="ButtonBorder" Property="BorderBrush" Value="White" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

MouseOver style trigger won't change background

New to WPF, coming from a web background.
My style trigger won't change button background. Style XAML:
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Background" Value="GhostWhite" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#F48230"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
Button XAML (nothing relevant in Window or Grid attributes):
<Window...>
<Grid...>
<StackPanel Orientation="Horizontal" Grid.Row="7" Grid.Column="0" Grid.ColumnSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center">
<Button Name="btnEdit" Cursor="Hand" Content="Edit Settings..." HorizontalAlignment="Left" VerticalAlignment="Top" Width="100" Margin="0,0,5,0" Click="btnEdit_Click"/>
<Button Name="btnExit" Cursor="Hand" Content="Exit" HorizontalAlignment="Left" VerticalAlignment="Top" Width="100" Margin="5,0,0,0" Click="btnExit_Click" />
</StackPanel>
</Grid>
</Window>
The buttons do pick up the background style in the resources section, but not the trigger - mouseover results in default behaviour.
Supplementary question: Is there a way I can debug this? I looked in Live Visual Tree but couldn't figure out how to get the info I need.
WPF Controls have a Template property of type ControlTemplate. This property tells WPF how to draw the control on the screen. A WPF Button uses Windows Chrome in it's ControlTemplate which uses user selected system colors to allow for consistency between different applications. Leveraging the magic of WPF and XAML, you can create your own ControlTemplate to make the button look any way you see fit.
Create a style with a key so you can choose which buttons use the template:
<Window.Resources>
<Style x:Key="MyButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Background" Value="GhostWhite" />
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<!-- a simple square button -->
<Border Name="wrapper"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Margin="{TemplateBinding Margin}"
Background="#01000000">
<!-- notice the wrapper has a background that is NEAR transparent. This is important. It'll ensure the button raises the click event -->
<Border Name=inner
Background="{TemplateBinding Background}">
<ContentPresenter Margin="{TemplateBinding Padding}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#FFF48230"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Background" Value="#FF99501B"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
And then to use the template on a button:
<Window...>
<Grid...>
<StackPanel Orientation="Horizontal" Grid.Row="7" Grid.Column="0" Grid.ColumnSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center">
<Button Style="{StaticResource MyButtonStyle}" Name="btnEdit" Cursor="Hand" Content="Edit Settings..." HorizontalAlignment="Left" VerticalAlignment="Top" Width="100" Margin="0,0,5,0" Click="btnEdit_Click"/>
<Button Style="{StaticResource MyButtonStyle}" Name="btnExit" Cursor="Hand" Content="Exit" HorizontalAlignment="Left" VerticalAlignment="Top" Width="100" Margin="5,0,0,0" Click="btnExit_Click" />
</StackPanel>
</Grid>
</Window>

Need some suggestions about how to reference outside control

I insert a Tabcontrol into a BorderControl. The Tabcontrol used seperate styles. Right now the Close Button inside Tabcontrol can refer to itself. So when click it the TabControl is closed I guess so. Is it possible to refer the button to the Border control outside so that when close the Border control is collpased?
<DockPanel LastChildFill="True">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160" MinWidth="100"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Margin="5,5,0,5" BorderBrush="Aqua" BorderThickness="1">
<TabControl Margin="0,15,0,0" Style="{StaticResource StandardTabControl}" >
<TabItem Header="Start">
</TabItem>
</TabControl>
</Border>
<GridSplitter Grid.Column="1" Background="Transparent" Width="6" HorizontalAlignment="Center"></GridSplitter>
<Border Grid.Column="2" BorderBrush="Aqua" BorderThickness="1" Margin="0,5,5,5"></Border>
</Grid>
</DockPanel>
After click the close button
And part of the styles of TabControl.
<Button Grid.Column="1" Height="15" Width="15" HorizontalAlignment="Center" VerticalAlignment="Center">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<Ac:CloseTabItemAction TabItem="{Binding RelativeSource={RelativeSource AncestorType=TabItem}}"
TabControl="{Binding RelativeSource={RelativeSource AncestorType=TabControl}}">
</Ac:CloseTabItemAction>
</i:EventTrigger>
</i:Interaction.Triggers>
<!--A lot more about styles-->
</Button>
here is a solution using control template triggers in pure xaml
<DockPanel LastChildFill="True">
<Control>
<Control.Template>
<ControlTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"
MinWidth="100"
x:Name="column" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Margin="5,5,0,5"
BorderBrush="Aqua"
BorderThickness="1">
<TabControl Margin="0,15,0,0"
Style="{StaticResource StandardTabControl}"
x:Name="tabControl">
<TabItem Header="Start">
</TabItem>
</TabControl>
</Border>
<GridSplitter Grid.Column="1"
Background="Transparent"
Width="6"
HorizontalAlignment="Center"></GridSplitter>
<Border Grid.Column="2"
BorderBrush="Aqua"
BorderThickness="1"
Margin="0,5,5,5"></Border>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding HasItems,ElementName=tabControl}"
Value="false">
<Setter TargetName="column"
Property="MinWidth"
Value="0" />
<Setter TargetName="column"
Property="Width"
Value="0" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Control.Template>
</Control>
</DockPanel>
based on the assumption that when CloseTabItemAction will be called it will remove the TabItem from the TabControl, the trigger will detect the same based on HasItems property and will collapse the column if there are no items
EDIT
based on the comments here are the finding. while resizing GridSplitter sets a Local value to the ColumnDefinition's Width property which have higher precedence than the Trigger values so the Trigger effectively fails to modify the value.
the solution proposed is to use Animation to set the desired value as Animations has higher precedence then the Local value, and the desired value is applied to the property.
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding HasItems,ElementName=T1}"
Value="false">
<Setter TargetName="Co0"
Property="MinWidth"
Value="0" />
<Setter TargetName="G1"
Property="Width"
Value="0" />
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Co0"
Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<GridLength>0</GridLength>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</ControlTemplate.Triggers>
in the above code I also make the splitter width to 0 so that there is no empty space on the left

wpf popup doesn't close when focus is lost in xaml

I've written following xaml code to show a popup with the content in a expander control. all the things work ok up to the position where the popup opens when the button is clicked. but the popup wont close when I click away from it. plus as soon as I click away to close the popup my whole application seems to be freezed for a little amount of time. Im trying to figure out what might be the issue. could anyone assist me to find this please ?. Thanks you.
<Style TargetType="{x:Type Expander}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<StackPanel>
<Button x:Name="btnTask" Content="{TemplateBinding Header}" >
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Label Content="{TemplateBinding Content}" Margin="2,0,2,0" />
</ControlTemplate>
</Button.Template>
<Button.ToolTip>
<Border CornerRadius="0,2,0,2" BorderBrush="White" BorderThickness="1" Background="SeaShell">
<StackPanel Width="150" Height="Auto">
<TextBlock TextWrapping="Wrap" Text="{TemplateBinding Tag}" Padding="2"/>
</StackPanel>
</Border>
</Button.ToolTip>
</Button>
<Popup x:Name="popupTask" Height="200" Width="200" StaysOpen="False">
<ContentControl Content="{TemplateBinding Content}" />
</Popup>
</StackPanel>
<ControlTemplate.Triggers>
<EventTrigger SourceName="btnTask" RoutedEvent="ButtonBase.Click">
<BeginStoryboard>
<Storyboard TargetName="popupTask">
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsOpen" BeginTime="0:0:0" Duration="0:0:3">
<DiscreteBooleanKeyFrame KeyTime="0:0:0.2" Value="True" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You can use a ToggleButton instead of a Button and use a different trigger, based on the ToggleButton's property IsChecked. Here is how you can change your style:
<Style TargetType="{x:Type Expander}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<StackPanel>
<ToggleButton x:Name="btnTask" Content="{TemplateBinding Header}">
<ToggleButton.Template>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Label Content="{TemplateBinding Content}" Margin="2,0,2,0" />
</ControlTemplate>
</ToggleButton.Template>
<ToggleButton.ToolTip>
<Border CornerRadius="0,2,0,2" BorderBrush="White" BorderThickness="1" Background="SeaShell">
<StackPanel Width="150" Height="Auto">
<TextBlock TextWrapping="Wrap" Text="{TemplateBinding Tag}" Padding="2"/>
</StackPanel>
</Border>
</ToggleButton.ToolTip>
</ToggleButton>
<Popup x:Name="popupTask" Height="200" Width="200" StaysOpen="False">
<ContentControl Content="{TemplateBinding Content}" />
</Popup>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger SourceName="btnTask" Property="IsChecked" Value="True">
<Setter TargetName="popupTask" Property="IsOpen" Value="True" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

WPF toggle buttons with custom template not accepting click in correct area

I'm a relative beginner in the WPF space so I'm sure this will be the first of many questions!
I have a series of toggle buttons all with a custom template designed to show an image with a transparent background that then becomes highlighted when the user toggles the button.
I wanted to add padding around the content so that the highlighted area would extent around the content.
This is working, but the user still has to click in the inner area to activate the button, which is not what I want.
I'm assuming it's because I'm using the Margin property of the ContentPresenter to bind to the Padding of the button and this is classed as outside of the content, but not sure of the best way to fix this.
It does work when de-selecting the button though.
Below is some XAML showing the problem which should be able to be copied and pasted straight into XamlPad.
<Page.Resources>
<Style x:Key="ValidationToggleButton" TargetType="ToggleButton">
<Setter Property="Padding" Value="5" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate >
<Grid Name="MainGrid">
<Viewbox>
<ContentPresenter Margin="{TemplateBinding Padding}"
Content="{TemplateBinding Property=Button.Content}" />
</Viewbox>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="ToggleButton.IsChecked" Value="True">
<Setter TargetName="MainGrid" Property="Background" Value="#88FFFF55" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
<Grid>
<GroupBox Grid.Column="0" Header="Validation" BorderBrush="#55BBE6" Margin="2" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ToggleButton Grid.Column="0" Style="{StaticResource ValidationToggleButton}">
CLICK
</ToggleButton>
</Grid>
</GroupBox>
</Grid>
Anyone have any idea how I might correct this?
Welcome to WPF world :). That happens because of the... background brush. If you don't set it it's null. And that means it's not visible for hit testing mechanism. Quick fix to this set Background="Transparent" to the MainGrid. But more appropriate way to set it via styles:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<Style x:Key="ValidationToggleButton" TargetType="ToggleButton">
<Setter Property="Padding" Value="5" />
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate >
<Grid Name="MainGrid" Background="{TemplateBinding Background}">
<Viewbox>
<ContentPresenter Margin="{TemplateBinding Padding}"
Content="{TemplateBinding Property=Button.Content}" />
</Viewbox>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="ToggleButton.IsChecked" Value="True">
<Setter TargetName="MainGrid" Property="Background" Value="#88FFFF55" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
<Grid>
<GroupBox Grid.Column="0" Header="Validation" BorderBrush="#55BBE6" Margin="2" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ToggleButton Grid.Column="0" Style="{StaticResource ValidationToggleButton}">
CLICK
</ToggleButton>
</Grid>
</GroupBox>
</Grid>
</Page>

Resources