In the illustration below you can see a custom control that I'm currently creating with a view to learning as much as I can about the whole process of control creation.
Currently I'm trying to both simplify the xaml by effectively refactoring repetitive style elements out into a separate style and achieve a disabled look for images when the buttons in which they are situated are disabled.
In the xaml below you can see both the button style I've created and the data trigger for the image. In theory both are being applied to the second button on the left, but quite clearly they aren't being.
I think that the basic style definitions are correct but it may be that they are in the incorrect place. Can anyone advise as to what it is that I'm currently doing wrong.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ViewToLearn.WpfControls">
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type UIElement}, AncestorLevel=1}, Path=IsEnabled}"
Value="False">
<Setter Property="Opacity"
Value="0.2"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type Button}">
<Setter Property="BorderBrush"
Value="Transparent" />
<Setter Property="Background"
Value="Transparent" />
<Setter Property="BorderThickness"
Value="0" />
<Setter Property="Margin"
Value="4" />
</Style>
<Style TargetType="{x:Type local:VtlDataNavigator}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:VtlDataNavigator}">
<StackPanel Orientation="Horizontal">
<Button BorderBrush="Transparent"
Background="Transparent"
BorderThickness="0">
<StackPanel Margin="4">
<Image Source="/ViewToLearn.WpfControls;component/Resources/Images/button_rounded_blue_first24.png" />
</StackPanel>
</Button> <!-- This is the button that should be influenced by the style and trigger defined above, but clearly it isn't -->
<Button IsEnabled="False">
<StackPanel Margin="4">
<Image Source="/ViewToLearn.WpfControls;component/Resources/Images/button_rounded_blue_previous24.png" />
</StackPanel>
</Button>
<TextBox Text="{Binding Path=RecordIndex,RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center"
IsReadOnly="True"
BorderThickness="0"
Margin="4" />
<TextBlock Text="of"
VerticalAlignment="Center"
Margin="4" />
<TextBlock Text="{Binding Path=RecordCount,RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center"
Margin="4" />
<Button BorderBrush="Transparent"
Background="Transparent"
BorderThickness="0">
<StackPanel Margin="4">
<Image Source="/ViewToLearn.WpfControls;component/Resources/Images/button_rounded_blue_next24.png" />
</StackPanel>
</Button>
Your second button has IsEnabled="false"
That makes the difference.
But if you want the background to be the same for all buttons, enabled or not, you have to got in the triggers.
The best way to generate the default style and template with triggers is to put a dummy/test button in a window, right click on it, Edit Template/Edit a copy.
VS will generate much code. You 'll see inside the "full" template" of the button + the triggers.
Inside there is a trigger for different appearance when disabled :
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="{x:Type ButtonBase}">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<!-- ... -->
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" TargetName="border" Value="#FFF4F4F4"/>
<Setter Property="BorderBrush" TargetName="border" Value="#FFADB2B5"/>
<Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="#FF838383"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
It's that template you can take inspiration from to update your style (without trigger when disabled):
<Style TargetType="{x:Type Button}">
<Setter Property="BorderBrush"
Value="Transparent" />
<Setter Property="Background"
Value="Transparent" />
<Setter Property="BorderThickness"
Value="0" />
<Setter Property="Margin"
Value="4" />
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="{x:Type ButtonBase}">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<!-- no trigger int he template -->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Notes regarding your code because it seems you are eager to learn :
Don't have to set Background, BorderThickness, BorderBrush for every button, it is already done in your Button style
The StackPanel around every Image is the same and can be put inside the template around the ContentPresenter tag
If VtlDataNavigator is a Usercontrol, you can put all the content of <ControlTemplate TargetType="{x:Type local:VtlDataNavigator}"> inside the <UserControl> tag.
It makes the control more reusable (don't have to provide the template in every Window for the control)
Regards
Related
I have 2 ComboBox's in a app settings flyout. One of which has an ItemTemplate defined and the other does not:
<StackPanel Orientation="Horizontal"
Margin="5">
<Label Content="Accent"
VerticalAlignment="Center"/>
<ComboBox ItemsSource="{Binding MetroAccents}"
SelectedItem="{Binding SelectedAccent}"
Margin="5">
<ComboBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Name}"
Margin="0"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<StackPanel Orientation="Horizontal"
Margin="5">
<Label Content="Grid Lines"
VerticalAlignment="Center"/>
<ComboBox ItemsSource="{Binding GridLinesVisibilityList}"
SelectedItem="{Binding SelectedGridLinesVisibility}"
Margin="5"/>
However, the ComboBox with the ItemTemplate has a larger height than the other ComboBox. Analysing the control with the 'Enable Selection' debugging tool, the ComboBox with the ItemTemplate seems to have a TextBlock with the Label (defined in the template). Whereas the other ComboBox only has a Label. Does anyone know why this is?
Label will take more space due to it's default style which looks like this:
<Style x:Key="LabelStyle1" TargetType="{x:Type Label}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Padding" Value="5"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Top"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Label}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Which as you can see has padding on it. While the TextBlock does not. This is why it takes more space.
Bear in mind that Label supports key bindings with _ underscore and also other UI elements as it's content. I personally don't use it unless I need key bindings, but even then I use AccessText MSDN.
I have simple ToggleButton and when IsChecked i want to change only the text.
I want all other properties like border and background will be Transparent but it seems that i still have this background style:
<ToggleButton x:Name="changeButBorderedBlinky" Content="EDIT" Width="40" Height="40" Background="Transparent">
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Content" Value="Done"/>
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
One thing that is often annoying is some visual aspects of the default WPF controls are coded in a way so as they are not configurable. The MouseOver Background is an example (scrollbar sizes is another... grrr!!!). You can solve this though by defining your own Template for the ToggleButton and eliminate that MouseOver trigger. Here is a simple example:
<ToggleButton x:Name="changeButBorderedBlinky" Width="40" Height="40" Background="Transparent">
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Content" Value="EDIT"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Border x:Name="border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="True">
<ContentPresenter x:Name="contentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
ContentStringFormat="{TemplateBinding ContentStringFormat}"
Focusable="False"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Content" Value="Done"/>
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
Also, in order to change a property in a trigger it has to be set in a style, not directly. That is why your Content doesn't change in your MouseOver Trigger. If you remove the property setting for it and
add it into the Style with a Setter, it will allow the trigger to change it.
One downside to all this is it overrides all the default template triggers so you won't see when the ToggleButton is checked anymore unless you add a trigger for "IsChecked" too. (and if you need to alter the Background in the trigger, move the Background Property to a Setter like I did for Content)
Hope that helps...
Sorry to revive an old thread, but (for someone struggling) the following worked for me. This can be in App.xaml file under Application.Resources tag or directly in the Window.Resources. I originally needed it for a UWP application, then had to adapt for WPF for a different project:
<Style TargetType="ToggleButton" x:Key="TransparentToggleButtonStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid x:Name="RootGrid" Background="Transparent">
<ContentPresenter x:Name="ContentPresenter" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Then just style your ToggleButton:
<ToggleButton Style="{StaticResource TransparentToggleButtonStyle}">
(whatever content you want to present)
</ToggleButton>
Hope this helps someone,
I have the below code set for a custom button. But I want to change the background for a single button.
<Style x:Key="myButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Rectangle x:Name="rectangle" Fill="#FF2F2FEA" Stroke="Black">
<Rectangle.Effect>
<DropShadowEffect ShadowDepth="3"/>
</Rectangle.Effect>
</Rectangle>
<ContentPresenter x:Name="contentPresenter" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Bottom" Margin="2.833,0,2.5,1.162" RenderTransformOrigin="0.5,0.5" Width="69.667"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
The code below is meant to override the background color but doesn't. What am I missing? Thanks
<Style x:Key="SpecialButton" TargetType="Button" BasedOn="{StaticResource myButtonStyle}">
<Setter Property="Background" Value="PaleGreen" />
<Setter Property="Height" Value="19.96" />
</Style>
Add <Setter Property="Background" Value="#FF2F2FEA" /> to the myButtonStyle. And change the Fill on the Rectangle to Fill="{TemplateBinding Background}"
<Style x:Key="myButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="#FF2F2FEA" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Rectangle x:Name="rectangle" Fill="{TemplateBinding Background}" Stroke="Black">
<Rectangle.Effect>
<DropShadowEffect ShadowDepth="3"/>
</Rectangle.Effect>
</Rectangle>
<ContentPresenter x:Name="contentPresenter" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="Bottom"
Margin="2.833,0,2.5,1.162"
RenderTransformOrigin="0.5,0.5" Width="69.667"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
After this, the two Buttons should show up properly.
<StackPanel>
<Button Content="BASE" Style="{StaticResource myButtonStyle}"></Button>
<Button Content="DERIVED" Style="{StaticResource SpecialButton}"></Button>
</StackPanel>
Result:
Explanation: if you change the Template of a Control you have to "wire-up" its properties (like Background, Foreground, HorizontalContentAlignment etc) to properties of elements in the ControlTemplate. After all you want to give the ability to end users of the control to modify the color, alignment etc, and not just have a Control with a static visual tree.
I have a simple xaml example and I want to change CornerRadius property, which is set through ControlTemplate of Button. I need a way to change it through Button. One use case is: I have 2 buttons, one sets to "BigRadius" and another one sets to "SmallRadius". If I change the default one on Border, then both buttons will have the same CornerRadius. Is there a way to do this in XAML only?
<Window x:Class="StyleDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border CornerRadius="5" BorderBrush="Blue"
BorderThickness="5"
Width="80"
Height="40"
x:Name="BaseBorder">
<ContentPresenter Content="{Binding Path=Content, RelativeSource={RelativeSource TemplatedParent}}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="Grid" x:Name="GridWithMarginStyle">
<Setter Property="Margin" Value="12"></Setter>
</Style>
</Window.Resources>
<StackPanel>
<!--This button will have a big corner radius-->
<Button Name="Ok" Content="Ok"></Button>
<!--This button will have a small corner radius-->
<Button Name="CancelBtn" Click="CancelBtn_Click">Cancel</Button>
</StackPanel>
</Window>
You will need two styles for the Button to achieve what you are doing or create a Custom Button to implement CornerRadius as DependencyProperty, and bind it with CornerRadius of Border in ControlTemplate.
<Style TargetType="Button" x:Key="LargeRadiusStyle">
<Setter Property="Background" Value="White" />
<Setter Property="TextBlock.TextAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="10" Background="White" BorderBrush="Black" BorderThickness="1" >
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="Button" x:Key="SmallRadiusStyle">
<Setter Property="Background" Value="White" />
<Setter Property="TextBlock.TextAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="3" Background="White" BorderBrush="Black" BorderThickness="1" >
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Not sure what you have against DPs. You need SOMETHING to tell the button whether you want the big radius or the small radius. WPF can't read your mind (yet) :). If you don't want a custom button class, then you can use an attached property.
I have a custom template for an expander that is close to the code below. I had to change some of the code to take out custom classes, brushes, etc..
<Style TargetType="{x:Type Expander}">
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
<Setter Property="VerticalContentAlignment"
Value="Top" />
<Setter Property="BorderBrush"
Value="Transparent" />
<Setter Property="FontFamily"
Value="Tahoma" />
<Setter Property="FontSize"
Value="12" />
<Setter Property="Foreground"
Value="Black" />
<Setter Property="BorderThickness"
Value="1" />
<Setter Property="Margin"
Value="2,0,0,0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<Border x:Name="Border"
SnapsToDevicePixels="true"
Background="White"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Margin="0,0,0,10"
Padding="0"
CornerRadius="8">
<DockPanel>
<Border x:Name="HeaderSite"
Background="Blue"
CornerRadius="8"
Height="32"
DockPanel.Dock="Top">
<DockPanel>
<ToggleButton Foreground="White"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="0"
MinHeight="0"
MinWidth="0"
Padding="6,2,6,2"
IsChecked="{Binding Path=IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
DockPanel.Dock="Left">
</ToggleButton>
<ContentPresenter SnapsToDevicePixels="True"
HorizontalAlignment="Left"
Margin="4,0,0,0"
ContentSource="Header"
VerticalAlignment="Center"
RecognizesAccessKey="True" />
</DockPanel>
</Border>
<Border x:Name="InnerBorder"
Margin="0" >
<ContentPresenter Focusable="false"
Visibility="Collapsed"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
x:Name="ExpandSite"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
DockPanel.Dock="Bottom" />
</Border>
</DockPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded"
Value="true">
<Setter Property="Margin"
TargetName="InnerBorder"
Value="5" />
<Setter Property="Visibility"
TargetName="ExpandSite"
Value="Visible" />
</Trigger>
<Trigger Property="IsEnabled"
Value="false">
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
As you can see there are two ContentPresenters. I would like the first one to use Tahoma Bold as the font instead of the default Tahoma. How can I do this?
You need to use the FontWeight property to specify a bold font. However, you've probably noticed that ContentPresenter doesn't have that property. So you'll need to use the TextBlock.FontWeight attached property to tell the ContentPresenter that any text inside it should be bold.
Try this:
<ContentPresenter TextBlock.FontFamily="Tahoma"
TextBlock.FontWeight="Bold"
SnapsToDevicePixels="True"
HorizontalAlignment="Left"
Margin="4,0,0,0"
ContentSource="Header"
VerticalAlignment="Center"
RecognizesAccessKey="True" />
I can't help about Silverlight, but in the new WPF 4 it is TextElement rather than TextBlock