Custom Control Styling/Triggers - wpf

I'm trying to make a custom control that consists of several buttons and a couple labels, but the buttons are supposed to be invisible, showing only the content within them. I can get rid of the border, background, etc by setting them to Transparent. But whenever I MouseOver them the default windows hover effect shows the whole button again. I've tried numerous guides on custom controls, but ultimately cannot figure out how to override this. Basically my questions boil down to, how much of this can be placed in the generic.xaml file, what organization do I have to use within that file, and are there any other places that these styling should go instead? I do realize this is a very basic question, but it's just driving me nuts not being able to figure out the specific answer. Thanks!
Current xaml:
<Style TargetType="{x:Type local:TimePicker}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TimePicker}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}">
<StackPanel Orientation="Horizontal">
<StackPanel x:Name="PART_Root"
Orientation="Horizontal"
HorizontalAlignment="Center">
<ToggleButton x:Name="PART_HourButton"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0"
Height="{Binding ElementName=PART_IncDecPanel, Path=ActualHeight}"
Width="{Binding ElementName=PART_HourButton, Path=ActualHeight}"
Content="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Hour}">
<ToggleButton.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Transparent" />
</Trigger>
</ToggleButton.Triggers>
</ToggleButton>
<Label x:Name="PART_HourMinSeparator"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0"
Content=":" />
<ToggleButton x:Name="PART_MinButton"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0"
Height="{Binding ElementName=PART_HourButton, Path=ActualHeight}"
Width="{Binding ElementName=PART_HourButton, Path=ActualWidth}"
Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Minute}" />
<StackPanel x:Name="PART_IncDecPanel" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button x:Name="PART_IncreaseTime"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0"
HorizontalAlignment="Center"
HorizontalContentAlignment="Center"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Margin="0"
Padding="0"
Width="22"
Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IncreaseImage.ActualHeight}">
<Image Source="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IncreaseImage}" />
</Button>
<Button x:Name="PART_DecreaseTime"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0"
HorizontalContentAlignment="Center"
HorizontalAlignment="Center"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Margin="0"
Padding="0"
Height="{Binding ElementName=PART_IncreaseTime, Path=ActualHeight}"
Width="{Binding ElementName=PART_IncreaseTime, Path=ActualWidth}">
<Image Source="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DecreaseImage}" />
</Button>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

WPF uses "lookless controls," which basically means that you can change the entire visual part (at design- or run-time) of a control without changing its code or the behavior of that code. You're already making use of this concept to create a default Style for your TimePicker control. If you were to remove the ControlTemplate from that Style, you would see nothing at runtime because the control itself is just the behavior defined in the C# or VB code.
Since it sounds like you want to keep the behavior of your buttons, but completely change the look, this is an ideal scenario for re-templating. Here's a very simple example that will show only the Content (in the ContentPresenter):
<Button Content="Hello Template">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</ControlTemplate>
</Button.Template>
</Button>
You will probably want to add some more to the template, like a Transparent Border to catch mouse input and maybe some Triggers. The way you're attempting to use a Trigger on the ToggleButton in your example is incorrect (FrameworkElement Triggers collection only works with EventTriggers), but inside a ControlTemplate or Style, that pattern will work.
If you want to apply the same Style to every Button inside your TimePicker ControlTemplate you can add a default Button Style to the Resources collection of your ControlTemplate:
<ControlTemplate TargetType="{x:Type MyControl}">
<ControlTemplate.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ControlTemplate.Resources>
...
</ControlTemplate>

Related

Why cant I write and Display text in my TextBox what is made with an Style and an ControlTemplate?

so I got an TextBox and i made the Style in my Ressource Dictionary like that:
<Style x:Key="TextBoxTemplateBrowser" TargetType="{x:Type TextBox}">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border Background="White" CornerRadius="0 0 5 5" BorderBrush="Black" BorderThickness="1">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And my TextBox himself like that:
<TextBox TextWrapping="Wrap" x:Name="tb" Style="{DynamicResource TextBoxTemplateBrowser}"
Text="{Binding Inhalt, RelativeSource={RelativeSource AncestorType=local:ArtikelBezPanel}, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
TextChanged="TextBox_TextChanged"
Height="{Binding Height, RelativeSource={RelativeSource AncestorType=local:ArtikelBezPanel}, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" AcceptsReturn="True" BorderBrush="Black" AcceptsTab="True" VerticalAlignment="Top" BorderThickness="1">
</TextBox>
The Problem what I got now is that, when i try to write in that TextBox I can write anything and there cant be Text displayed what I declare before.
When templating TextBox you need the PART_ContentHost:
eg:
<ScrollViewer Margin="0" x:Name="PART_ContentHost" />
See DOCS for more info!
Replace
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
with
<ScrollViewer Margin="0" x:Name="PART_ContentHost" HorizontalAlignment="Center" VerticalAlignment="Center"/>
and it should work!

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>

Button Style performance hit

I have a usecase to show nearly 10000 items in a WPF usercontrol. I am using ItemsControl and each item is represented by a button (items a simple clickable text). I have defined a style for button in usercontrol resources.
Things work fine till I have more than 5000 items in my list then UI paint starts to slow down- 10000 items takes nearly 3+ minutes to be displayed.
If I move the style from resources to Button.Style then also it take 2.5 mins to displays the items.
If I remove the style completely, I see no noticeable delay.The only reason to use Button style is to give its ContentPresenter's Border (named as Chrome in below code) the same background as button, which is otherwise Gray.
Please let me know how can I use styles efficiently without incurring a Performance hit or how can I paint the ContentPresenter Border's Background as same color as Button (transparent would work somehow).
Here is the code sample:
<UserControl.Resources>
<Style x:Key="ButtonStyle" TargetType="{x:Type Button}" BasedOn="{x:Null}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="Chrome" Background="{TemplateBinding Property=Background}">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<ContentPresenter.Resources>
<Style TargetType="{x:Type TextBlock}" BasedOn="{x:Null}">
<Setter Property="FontSize" Value="{Binding FontSize, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid Name="Grid1" Margin="5,5,5,5">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Margin="5,0,0,0">
<Border Name="Border1" Margin="2,2,2,2" BorderBrush="Gray" BorderThickness="2">
<ItemsControl Name="ItemsControl1" ItemsSource="{Binding LargeItems}" FocusVisualStyle="{x:Null}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding Columns}" Rows="{Binding Rows}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Name="Border1" Background="{Binding BorderBkg}"
BorderThickness="1" Padding="{Binding PaddingVal}">
<Button Name="MyButton" Content="{Binding Label}"
Background="{Binding Background}"
Foreground="{Binding Foreground}"
BorderThickness="0"
BorderBrush="Transparent"
Margin="0"
Style="{StaticResource ButtonStyle}"
IsEnabled="{Binding IsButtonEnabled}"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}, Path=DataContext.ButtonAction}"
CommandParameter="{Binding}">
</Button>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</ScrollViewer>
</Grid>
Thanks,
RDV
It seems that there is no data virtualization implemented in your ItemControl. You can implement the virtualization by adding
VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling"
in your ItemsControl and see the performance difference.

MenuItem shortcut in ControlTemplate

I've got next makrup:
<Style TargetType="{x:Type MenuItem}">
<Setter Property="MinWidth" Value="65" />
<Setter Property="MinHeight" Value="30" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MenuItem}">
<Border x:Name="MainBorder" BorderThickness="1" Background="Black">
<TextBlock Margin="5" Text="{TemplateBinding Header}" Foreground="{TemplateBinding Foreground}" />
<Popup x:Name="SubMenuPopup" IsOpen="{Binding Path=IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}" Placement="Right"
AllowsTransparency="True" Focusable="False">
<Border Background="Gray">
<Grid x:Name="SubMenu" Grid.IsSharedSizeScope="True" Background="Transparent">
<StackPanel Margin="0" IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Cycle" Background="Gray" />
</Grid>
</Border>
</Popup>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
When I create MenuItem somewhere and set it's Header property with "_" symbol - it doesn't create shortcut for this menu item.
Example - letter 'F' is not underlined and shortcut doesn't work.
How to support shortcuts in ControlTemplates in MenuItems?
Thanks.
It is not the complete template but instead of the TextBlock put a ContentPresenter that can recognize access keys:
<ContentPresenter Margin="5" Content="{TemplateBinding Header}" TextBlock.Foreground="{TemplateBinding Foreground}" RecognizesAccessKey="True" />
I suppose the xaml you have pasted here is only part of your implementation, so my solution is onl y a continuation of yours with the access key working...
You can find entire templates just like this one: http://msdn.microsoft.com/en-us/library/ms747082%28v=vs.85%29.aspx
If you want you menu item letter to underline itself of keypress then you have to set the InputGesture on menuitem like this:
<MenuItem Header="_File"
InputGestureText="Ctrl+F"
Commmand={Binding NewFileCommand}/>
But if you want to create shortcut for the menuitem command then you will have to create the commandbindings like below on your window:
<Window.CommandBindings>
<CommandBinding Command="local:MyCommands.NewFile" Executed="NewFile_Executed" />
</Window.CommandBindings>
<Window.InputBindings>
<KeyBinding Key="F" Modifiers="Control" Command="local:MyCommands.NewFile"/>
</Window.InputBindings>

WPF - Why is my buttons content moving separately from the button itself?

I have created a TabControl in a WPF application I'm writing. I re-templated TabItem so that I could have a button on each tab header to close it. So far, all is well and good.
I decided that I now wanted shiny round buttons instead of the default square ugly things. Also, I wanted to use an image as my buttons content instead of simply setting the content to "X".
My XAML styles/templates:
<Style TargetType="{x:Type Button}" x:Key="EllipseButtonStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Ellipse Fill="{TemplateBinding Background}"
Stroke="{TemplateBinding BorderBrush}"
StrokeThickness="{TemplateBinding BorderThickness}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"/>
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding Button.Content}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="ClosableTabItemTemplate">
<DockPanel MinWidth="120" Margin="0,0,0,0">
<ContentPresenter
Content="{Binding Path=DisplayName}"
VerticalAlignment="Center"
HorizontalAlignment="Left"/>
<Button
Command="{Binding Path=UnSubscribeApplicationCommand}"
CommandParameter="{Binding Path=DisplayName}"
BorderBrush="Black"
BorderThickness="2"
VerticalContentAlignment="Center"
VerticalAlignment="Center"
HorizontalAlignment="Right"
DockPanel.Dock="Right"
Width="16" Height="16">
<Image Source="closeicon.bmp" Height="8" Width="8"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Button.Style>
<Style TargetType="{x:Type Button}"
BasedOn="{StaticResource EllipseButtonStyle}">
<Setter Property="Background"
Value="{StaticResource CloseOffButtonBrush}"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource CloseOnButtonBrush}"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</DockPanel>
</DataTemplate>
With the above code in place, however, a selected tabs content (and the background as well) seems to shift upwards because of what I assume is the TabItems content moving upwards due to it being selected. Why, then, is the ellipse not shifting with the other content? Anyone have any idea what is going on here?
Sorry for the delayed response - I ended up solving the issue by modeling my TabItem template after the one posted in this blogpost. I believe the issue surfaced due to the fact that my TabItem template was being defined as a DataTemplate, not as a ControlTemplate as it should have been. Here is the new template:
<ControlTemplate x:Key="ClosableTabItemTemplate" TargetType="TabItem">
<Grid SnapsToDevicePixels="true">
<Border x:Name="Bd" Background="{StaticResource TabItemUnselectedBrush}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1,1,1,0" >
<DockPanel x:Name="ContentPanel">
<Button Command="{Binding Path=UnSubscribeApplicationCommand}" BorderBrush="Black" HorizontalAlignment="Center" Margin="3,0,3,0" VerticalAlignment="Center" Width="16" Height="16" DockPanel.Dock="Right" ToolTip="Close Tab">
<Button.Content>
<Image Source="pack://application:,,,/Resources/Close.png" Height="8" Width="8" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Button.Content>
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource EllipseButtonStyle}">
<Setter Property="Background" Value="{StaticResource CloseOffButtonBrush}"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource CloseOnButtonBrush}"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<ContentPresenter x:Name="Content" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" Content="{Binding Path=DisplayName}" RecognizesAccessKey="True" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="{TemplateBinding Padding}"/>
</DockPanel>
</Border>
</Grid>
<!-- bunch of ControlTemplate triggers to style the TabItem background color/position -->
</ControlTemplate>
Whenever I have controls inside a DockPanel, it always seems to play up if one of the controls I've explicitly attached DockPanel.Dock to comes AFTER the element I want to take up the fill portion. While I don't know if this will answer your question, try this instead in your ClosableTabItemTemplate DataTemplate:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0"/>
<Button Grid.Column="1"/>
</Grid>

Resources