Today I had to solve a problem that appeared quite difficult, but turned out to not be so hard if I let Expression Blend do most the work and finish it up in the XAML. I ended up having to completely recreate the default template style and then modify it.
Note: This article could also be titled: How to change the default template style of a standard control?
I had a RadioButton with text that wraps and it wasn’t displaying exactly how my team wanted. Here is the XAML.
<Window x:Class="RadioButtonTopAligned.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">
<Grid>
<RadioButton GroupName="RadioButtonList">
<Label>
<AccessText TextWrapping="Wrap" Text="_This is a very long radio button control line of text that should wrap." MaxWidth="300"/>
</Label>
</RadioButton>
</Grid>
</Window>
The problem is that the circle bullet is center aligned like this:
Notice how the circle bullet is aligned in between the two lines of text. I need it to be top aligned like this:
Notice how the circle bullet is aligned with the top line of text. I need to get WPF to do this.
From a Visual Studio 2010 point of view, there is no easy way to do this. At first I thought it would be a simple dependency property, but it isn’t. An quick internet search led me to realize that I have to pretty much re-style the whole RadioButton. This sounds really hard and in fact, in Visual Studio, without help, it would be really hard. You would have to have the code for the default template style for the RadioButton control memorized.
Here is an forum post I found from MSDN: How do I make a RadioButton’s Bullet align top
While the post is exactly what I was looking for and has an answer, I didn’t at first grasp the answer. I wasn’t sure what was going on until one of my co-workers, Shawn, who is more skilled in Expression Blend, showed me. Now that I understand, I want to make sure the next person who finds the same forum post on MSDN can understand even easier by writing this article and adding it to the forum post.
This is where Expression Blend comes in. If you don’t have Expression Blend, don’t worry, you can still get through this article as I will include the the default style code that Expression Blend created for me right here in my post.
In Expression Blend, this is what to do.
- Create a blank WPF project in Expression Blend.
- Add a RadioButton.
- Right-click on the RadioButton and choose Edit Template | Edit a Copy…
- Click OK on the Create Style Resource window.
Here is what happens to your XAML and you can do this to the XAML in you project manually if you don’t have Expression Blend.
- The following reference is added to the project: PresentationFramework.Aero
- The same is referenced in the XAML (See line 4 of the XAML below)
- The default RadioButton style is copied to your XAML under the Window.Resources element. (See lines 10-48 in the XAML below)
- The RadioButton is assigned the style created. (See line 51 in the XAML below)
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
x:Class="RadioButtonTopAligned_EB.MainWindow"
x:Name="Window"
Title="MainWindow"
Width="640" Height="480">
<Window.Resources>
<SolidColorBrush x:Key="CheckBoxStroke" Color="#8E8F8F"/>
<Style x:Key="CheckRadioFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle Margin="14,0,0,0" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="RadioButtonStyle1" TargetType="{x:Type RadioButton}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="#F4F4F4"/>
<Setter Property="BorderBrush" Value="{StaticResource CheckBoxStroke}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RadioButton}">
<BulletDecorator Background="Transparent">
<BulletDecorator.Bullet>
<Microsoft_Windows_Themes:BulletChrome BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" IsChecked="{TemplateBinding IsChecked}" IsRound="true" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderPressed="{TemplateBinding IsPressed}"/>
</BulletDecorator.Bullet>
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</BulletDecorator>
<ControlTemplate.Triggers>
<Trigger Property="HasContent" Value="true">
<Setter Property="FocusVisualStyle" Value="{StaticResource CheckRadioFocusVisual}"/>
<Setter Property="Padding" Value="4,0,0,0"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<RadioButton GroupName="RadioButtonList" Style="{DynamicResource RadioButtonStyle1}">
<Label>
<AccessText TextWrapping="Wrap" Text="_This is a very long radio button control line of text that should wrap." MaxWidth="300"/>
</Label>
</RadioButton>
</Grid>
</Window>
Now we can edit the XAML. Below is the same XAML as above with the following edits:
- Inside the BulletDecorator.Bullet element on line 30, create a DockPanel around the BulletChrome element.
- The ControlPresenter is moved to be inside the DockPanel.
- Add the following XAML atrributes to the BulletChrome element:
VerticalAlignment=”Top” Margin=”0,8,0,0″ Height=”{TemplateBinding FontSize}” Width=”{TemplateBinding FontSize}”
Note: If you change the font of the text content in the RadioButton, you should change the Margin in the style as well. I haven’t figured out how to make it always match the top line without manually tweaking it when you change the font. Also, if you don’t want the BulletChrome element to be the same size as the font, you will have to tweak Width and Height too.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
x:Class="RadioButtonTopAligned_EB.MainWindow"
x:Name="Window"
Title="MainWindow"
Width="640" Height="480">
<Window.Resources>
<SolidColorBrush x:Key="CheckBoxStroke" Color="#8E8F8F"/>
<Style x:Key="CheckRadioFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle Margin="14,0,0,0" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="RadioButtonStyle1" TargetType="{x:Type RadioButton}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="#F4F4F4"/>
<Setter Property="BorderBrush" Value="{StaticResource CheckBoxStroke}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RadioButton}">
<BulletDecorator Background="Transparent">
<BulletDecorator.Bullet>
<DockPanel>
<Microsoft_Windows_Themes:BulletChrome VerticalAlignment="Top" Margin="0,8,0,0" Height="{TemplateBinding FontSize}" Width="{TemplateBinding FontSize}" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" IsChecked="{TemplateBinding IsChecked}" IsRound="true" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderPressed="{TemplateBinding IsPressed}" />
<ContentPresenter RecognizesAccessKey="True" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</DockPanel>
</BulletDecorator.Bullet>
</BulletDecorator>
<ControlTemplate.Triggers>
<Trigger Property="HasContent" Value="true">
<Setter Property="FocusVisualStyle" Value="{StaticResource CheckRadioFocusVisual}"/>
<Setter Property="Padding" Value="4,0,0,0"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<RadioButton GroupName="RadioButtonList" Style="{DynamicResource RadioButtonStyle1}">
<Label>
<AccessText TextWrapping="Wrap" Text="_This is a very long radio button control line of text that should wrap." MaxWidth="300"/>
</Label>
</RadioButton>
</Grid>
</Window>
I hope this posts clarifies how to completely recreate a template style for a default control to modify something that at first doesn’t appear modifiable.