UWP CustomControl Styles don't inherit from default styles - c#

I'm creating a custom control (not UserControl) and I put my default style in generic.xaml. Since I'm basing my control on CheckBox, and I'm not really doing anything interesting other than adding some extra logic on the backend and maybe overriding a single property I would like to base my styles on the default styles.
Previously, I would be able to do something like
<Style TargetType="local:MyCheckBox" BasedOn="CheckBox">
<Setter Property="..." Value="..." />
</Style>
And this would use the default style and override my single property. Is this possible in UWP apps or do I need to copy the entire built-in style and change my single property?
My class is defined like this:
public class MyCheckBox : CheckBox
{
public MyCheckBox()
{
this.DefaultStyleKey = typeof(MyCheckBox);
}
// etc.
}

<Style TargetType="local:MyCheckBox" BasedOn="CheckBox">
<Setter Property="..." Value="..." />
</Style>
And this would use the default style and override my single property. Is this possible in UWP apps or do I need to copy the entire built-in style and change my single property?
By my side, if I use BasedOn="CheckBox" and place a MyCheckBox in the MainPage, this error will occur:
The name "MyCheckBox" does not exist in the namespace "xxxx".
Invalid value for property 'BasedOn':...
Cannot assign text value 'CheckBox' into property 'BasedOn' of type 'Style'.
If you want to use BasedOn style here, you can refer to Use based-on styles. According to this doc, you will need to use StaticResource here.
But I don't think here only using BasedOn style can solve the problem. Yes you are right, CustomControl style doesn't inherit from default styles, as you can see, the generic.xaml includes a default style, in this style there are only a ControlTemplate and a Border, it's empty.
Since I'm basing my control on CheckBox, and I'm not really doing anything interesting other than adding some extra logic on the backend and maybe overriding a single property I would like to base my styles on the default styles.
If I understand your request correctly, since the customcontrol's template is fast empty, so you are also right, you will need to copy the entire built-in style. For example, you can modify the template the same as the CheckBox's ControlTemplate:
<Style x:Key="CheckBoxStyle" TargetType="CheckBox">
<Setter Property="Background" Value="Blue" />
<Setter Property="Foreground" Value="Red" />
<Setter Property="Padding" Value="8,5,0,0" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Top" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="MinWidth" Value="120" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="UseSystemFocusVisuals" Value="True" />
</Style>
<Style TargetType="local:MyCheckBox" BasedOn="{StaticResource CheckBoxStyle}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyCheckBox">
<Grid BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Height="32" VerticalAlignment="Top">
<Rectangle x:Name="NormalRectangle" Fill="Transparent" Height="20" Stroke="{ThemeResource SystemControlForegroundBaseMediumHighBrush}" StrokeThickness="{ThemeResource CheckBoxBorderThemeThickness}" UseLayoutRounding="False" Width="20" />
<FontIcon x:Name="CheckGlyph" Foreground="{ThemeResource SystemControlHighlightAltChromeWhiteBrush}" FontSize="20" FontFamily="{ThemeResource SymbolThemeFontFamily}" Glyph="" Opacity="0" />
</Grid>
<ContentPresenter x:Name="ContentPresenter" AutomationProperties.AccessibilityView="Raw" ContentTemplate="{TemplateBinding ContentTemplate}" ContentTransitions="{TemplateBinding ContentTransitions}" Content="{TemplateBinding Content}" Grid.Column="1" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" TextWrapping="Wrap" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
But I think you can also modify the template of your customcontrol like this:
<Style x:Key="CheckBoxStyle1" TargetType="CheckBox">
<Setter Property="Foreground" Value="Red" />
</Style>
<Style TargetType="local:MyCheckBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyCheckBox">
<CheckBox Content="222" Style="{StaticResource CheckBoxStyle1}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This will make your CustomControl getting the default style of CheckBox, so which single property do you want to change?

Related

How to make a button in a Textbox's ControlTemplate update the text of the TextBox?

I currently have a custom control set up as shown below. (I am trying to make a custom numericupdown)
Basically, what this style does is it overlays two buttons on top of a styled text box. The two buttons should increase/decrease the value in the TextBox by one every time. The styling is fine, and the control displays correctly, but I do not know how to make it function.
WHAT I NEED
I need it so the buttons will decrease/increase the textbox's integer by one every time.
WHAT I HAVE TRIED
Just for your information, my current XAML ResourceDictionary is called Generic.xaml.
Try 1
I have tried creating a new C# class called Generic.xaml.cs and adding x:class="MyProject.Themes.Generic" and creating click events for the button. I would then add the appropriate event handlers into the Generic.xaml.cs file. I did manage to get the click events working, but I couldn't find a way to decrease the value of the TextBox.
For example:
<Button Click="increaseValue"></Button>
public partial class Generic
{
private void increaseValue(object sender, RoutedEventArgs e)
{
// code to change the textbox's value would be here, but I didn't know how to do it
}
}
Try 2
I have tried the exact same method as above, but with some differences. I nested all of my styles (in the code at the very bottom) into this:
<Style>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBox>
<!-- Styles go here -->
</TextBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
None of the above worked. Does anyone have any ideas?
Below is my full XAML code (it works fine). I mainly need assistance with the code-behind.
<Style TargetType="{x:Type local1:roundNumericUpDown}">
<Setter Property="FontFamily" Value="{StaticResource SourceSansRegular}" />
<Setter Property="Padding" Value="10, 0, 5, 1" />
<Setter Property="FontSize" Value="15" />
<Setter Property="Foreground" Value="White" />
<Setter Property="Background" Value="{StaticResource initialColor}" />
<Setter Property="Height" Value="28px" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Text" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="{TemplateBinding Background}" x:Name="Bd" BorderThickness="0" CornerRadius="15" BorderBrush="#FF383838">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="32px" />
</Grid.ColumnDefinitions>
<ScrollViewer x:Name="PART_ContentHost" />
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="13" />
<RowDefinition Height="2" />
<RowDefinition Height="13" />
</Grid.RowDefinitions>
<Button Grid.Row="0" Height="13" VerticalAlignment="Top" x:Name="IncreaseButton">
<Polygon Points="0,5 16,5 8,0" Stroke="{StaticResource decalColor}" Fill="{StaticResource decalColor}" />
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="{StaticResource hoverColor}" />
<Setter Property="WindowChrome.IsHitTestVisibleInChrome" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border CornerRadius="0, 13, 0 0" x:Name="Bd" Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource clickColor}" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="{StaticResource clickColor1}" />
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button Grid.Row="2" Height="13" VerticalAlignment="Bottom" x:Name="DecreaseButton">
<Polygon Points="0,0, 16,0 8,5" Stroke="{StaticResource decalColor}" Fill="{StaticResource decalColor}" />
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="{StaticResource hoverColor}" />
<Setter Property="WindowChrome.IsHitTestVisibleInChrome" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border CornerRadius="0, 0, 13 0" x:Name="Bd" Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource clickColor1}" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="{StaticResource clickColor}" />
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Try 1 is the right track
Of course I recommend to add try catch for the Convert method in-case user change the text to non-numeric value or switch TextBox to TextBlock making the value change only when the buttons are clicked and in that case you can guarantee it to be always number and you will not need the try and catch.
public partial class Generic
{
private void increaseValue(object sender, RoutedEventArgs e)
{
TextBox textBox = (((((sender as Button).Parent as Grid).Parent as Grid)).Parent as Border).TemplatedParent as TextBox;
textBox.Text = Convert.ToString(Convert.ToInt32(textBox.Text) + 1);
}
}
Not recommended
I honestly do not recommend the way you are creating your control. It would have been much easier and simpler to create an actual UserControl for your textBox and buttons and and add style to each element (textBox, UpButton, DownButton). Then you will have the freedom to access all 3 element freely. usually Style is for styling :)
Hopefully this answers your question

XAML, move two contentcontrol inside a button with animation

Is it possible to move two contentcontrol inside a button and also resize this said button?
<Button Height="100" Width="100">
<Grid>
<Grid.RowDefinitions>
<RowDefinition height="30"/>
<RowDefinition height="30"/>
</Grid.RowDefinitions>
<Image Grid.Row="0" Source="img.jpg"/>
<TextBlock Grid.Row="1" Text="Some content"/>
</Grid>
</Button>
I would like to align them horizontally instead of vertically on MouseOver and resize the button from 100 to 50, is this possible ?
This can be achieved with a style:
<Style x:Key="ExampleButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Background" Value="LightGray"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="Height" Value="100"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
<UniformGrid x:Name="uGrid">
<Image Source="img.jpg" />
<ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</UniformGrid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="false">
<Setter TargetName="uGrid" Property="Rows" Value="2" />
<Setter TargetName="uGrid" Property="Columns" Value="1" />
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="uGrid" Property="Columns" Value="2" />
<Setter TargetName="uGrid" Property="Rows" Value="1" />
<Setter Property="Height" Value="50" />
<Setter Property="Width" Value="50" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This style defines a modified template for your button, and can leverage triggers to respond to mouse events.
To add the style to your button, do the following:
<Grid>
<Grid.Resources>
<!-- Put Style Here -->
</Grid.Resources>
<Button Style="{DynamicResource ExampleButtonStyle}">
<TextBlock Grid.Row="1" Text="Some content"/>
</Button>
</Grid>
How you distribute your code between the style and the control instance is up to you. You will likely want to keep things modular and reusable.
Anatomy of a Style
If you haven't explored styles before, they can be a bit daunting and verbose. I have explained the components of the provided style below.
You would start a new style by declaring the Style. Here you must nominate a target type (what you want to apply the style to) and a Key (the name of the style resource:
<Style x:Key="ExampleButtonStyle" TargetType="{x:Type Button}">
<!-- Style content goes here. -->
</Style>
Setters are used to make the style apply values to properties of the target control. For example:
<Setter Property="Background" Value="LightGray"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Height" Value="100"/>
<Setter Property="Width" Value="100"/>
Because you want to change the layout of the button, you will want to nominate a control template. To do this, use a Setter to set the Template property of the button. The template here can include any layout you want. The ContentPresenter is used to display the body of the Button tag from your implementation:
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
<UniformGrid x:Name="uGrid">
<Image Source="img.jpg" />
<ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</UniformGrid>
</Border>
<ControlTemplate.Triggers>
<!-- Triggers here -->
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
Finally, you need to add triggers to make the control template update with interaction. The trigger essentially observes a property of the control, and applies when its value matches the value nominated by the trigger. This updates the control using the setters within.
<Trigger Property="IsMouseOver" Value="false">
<Setter TargetName="uGrid" Property="Rows" Value="2" />
<Setter TargetName="uGrid" Property="Columns" Value="1" />
</Trigger>
Layout
To achieve your layout goal I used a UniformGrid. The UniformGrid divides itself into equal cells to match the number of content items within. Note that this is available in WPF, but not UWP. On the UniformGrid, you can set a Columns or Rows count, and the grid will compensate as necessary. In the style above, I am changing the nominated Row and Column counts and letting the grid adjust itself layout accordingly. This visually swaps content from rows to columns.
Concerns
There are other methods that could achieve this more elegantly, but Styles offer a large amount of flexibility, and have a smaller learning curve.
You stated that you wanted to change the button size from 100 to 50 (I am assuming on both axis?) and I must advise you against this. You will see what happens when you apply the provided style; the button starts as a 100x100 square. A user moves the mouse over the button to just inside the button's edge. The button rapidly changes from 50x50 to 100x100 until the mouse is moved to the centre or off the button entirely. This offers a poor and confusing user experience, and deserves some more thought.
this might be late, but according to what have been said in the comments, this could do the trick, i have worked on something similar recently
<Style x:Key="btnSubMenu" TargetType="{x:Type Button}">
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontWeight" Value="ExtraBold"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
<Grid x:Name="uGrid2">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Image Grid.Row="0" Source="/Images/1.png" />
<ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
<Grid Grid.Row="2" Name="Gbexample" Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="40"/>
<RowDefinition Height="40"/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Style="{DynamicResource btnSubSubMenu}" Content="{DynamicResource strCity}" Name="btnCity"/>
<Button Grid.Row="1" Style="{DynamicResource btnSubSubMenu}" Content="{DynamicResource strCountry}" Name="btnCountry"/>
<Button Grid.Row="2" Style="{DynamicResource btnSubSubMenu}" Content="{DynamicResource strStore}" Name="btnStoreIn"/>
<Button Grid.Row="3" Style="{DynamicResource btnSubSubMenu}" Content="{DynamicResource strLocation}" Name="btnLocation"/>
</Grid>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="false">
<Setter TargetName="Gbproduct" Property="Visibility" Value="Collapsed" />
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="Gbproduct" Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

WPF custom control does not use default style of ToggleButton

I'm creating a custom control which is based on a ToggleButton.
With this simple style in Generic.xaml I can't get the default togglebutton styles working on my custom control.
I'm setting the foreground, background and borderbrush to the systemcolors, but nothing happens.
<Style TargetType="{x:Type local:PopupButton}" BasedOn="{StaticResource {x:Type ToggleButton}}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" />
<Setter Property="BorderBrush" Value="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="MinHeight" Value="22" />
<Setter Property="MinWidth" Value="75" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:PopupButton}">
<Grid SnapsToDevicePixels="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ToggleButton Grid.Column="0">
<ToggleButton.Template>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
RecognizesAccessKey="True" />
</ControlTemplate>
</ToggleButton.Template>
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Margin}"
RecognizesAccessKey="true" />
</ToggleButton>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I've overridden the defaultstylekey:
public class PopupButton : ToggleButton
{
static PopupButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(PopupButton), new FrameworkPropertyMetadata(typeof(PopupButton)));
}
// ...
Been testing, playing around with other (xaml) code but after a few hours I still have not figured out why the default style is not applied.
In your ControlTemplate for local:PopupButton you have a toggle button but you are overring it's template as well. Try removing:
<ToggleButton.Template>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
RecognizesAccessKey="True" />
</ControlTemplate>
</ToggleButton.Template>

Can't bind IsChecked on RadioButton with custom ControlTemplate

I have been using a custom RadioButton control with a ToggleButton as the control template. Here's what the xaml looks like:
<RadioButton.Template>
<ControlTemplate>
<ToggleButton x:Name="tb" IsChecked="{Binding IsChecked, Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}"
Content="{TemplateBinding RadioButton.Content}"
PreviewMouseDown="tb_PreviewMouseDown">
</ToggleButton>
</ControlTemplate>
</RadioButton.Template>
It's been working well, except when I try to either programatically set a button's IsChecked property, or make a binding with it. Then the button that should be checked is visually unresponsive - it doesn't appear to be pressed, and the Aero mouse over effect does not appear. The Clicked event handler still works, and the IsChecked property of both the RadioButton and the ControlTemplate's toggle button are true when I examine their values. Amy I doing something wrong with the binding? Any ideas?
Here's an example of how I use it in the application:
<local:RadioToggleButton Content="1Hr" GroupName="Interval" x:Name="oneHrBtn"
IsChecked="{BindingPath=oneHrBtnIsChecked, Mode=TwoWay}" Margin="2 5 3 5"
IsEnabled="{Binding Path=oneHrBtnIsEnabled, Mode=TwoWay}"/>
What you have is very strange. The RadioButton class derives from ToggleButton. So effectively you put a button in a button. Are you simply trying to make the RadioButton look like a ToggleButton? If so, why don't you use ToggleButton directly?
If you want to make the RadioButton look like a ToggleButton so you can use the GroupName feature, then you'd have to copy the ToggleButton control template and use that (not embed a ToggleButton in the control template).
You can get the default templates from here. Then search for the ToggleButton style and copy it's ControlTemplate.
EDIT:
The following example shows how this can be done. You just need to add a reference to PresentationFramework.Aero.
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:theme="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<LinearGradientBrush x:Key="ButtonNormalBackground" StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="#F3F3F3" Offset="0" />
<GradientStop Color="#EBEBEB" Offset="0.5" />
<GradientStop Color="#DDDDDD" Offset="0.5" />
<GradientStop Color="#CDCDCD" Offset="1" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
<SolidColorBrush x:Key="ButtonNormalBorder" Color="#FF707070" />
<Style x:Key="ButtonFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle Margin="2" StrokeThickness="1" Stroke="Black" StrokeDashArray="1 2" SnapsToDevicePixels="true" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Type RadioButton}" TargetType="{x:Type RadioButton}">
<Setter Property="FocusVisualStyle" Value="{StaticResource ButtonFocusVisual}" />
<Setter Property="Background" Value="{StaticResource ButtonNormalBackground}" />
<Setter Property="BorderBrush" Value="{StaticResource ButtonNormalBorder}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Padding" Value="1" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RadioButton}">
<theme:ButtonChrome Name="Chrome" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" RenderDefaulted="{TemplateBinding Button.IsDefaulted}"
RenderMouseOver="{TemplateBinding IsMouseOver}" RenderPressed="{TemplateBinding IsPressed}"
SnapsToDevicePixels="true">
<ContentPresenter Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</theme:ButtonChrome>
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter TargetName="Chrome" Property="RenderDefaulted" Value="true" />
</Trigger>
<Trigger Property="ToggleButton.IsChecked" Value="true">
<Setter TargetName="Chrome" Property="RenderPressed" Value="true" />
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="#ADADAD" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel>
<RadioButton GroupName="TestGroup">Option 1</RadioButton>
<RadioButton GroupName="TestGroup">Option 2</RadioButton>
</StackPanel>
</Window>
If all you want is a RadioButton which looks like a ToggleButton, you can actually implicitly refer to ToggleButton's style as a static resource by its type:
<RadioButton Style="{StaticResource {x:Type ToggleButton}}" />
This seems to work because RadioButton is descended from ToggleButton. So you can't, for example, use {StaticResource {x:Type ComboBox}}.
I'm not able to track down any documentation for using an x:Type as a resource for Style; I'd be interested to see it, if anyone out there knows where to look.
So the issue with my custom RadioToggleButton control was being caused by something very weird indeed. I will describe my solution below, not because I expect anyone else to run into this particular problem, but just as an example of a solution that seems unconnected to the problem.
There was a binding on IsEnabled property of the GroupBox containing the button group. This binding seemed to work fine, enabling and disabling all the internal controls when appropriate. But as soon as I removed this binding, the problem I described above disappeared. This is not ideal, but I decided that I had spent too much time on this issue, so I bound the IsEnabled properties of the individual controls to the same property that the GroupBox had been bound to, and now at least I have the behavior I wanted.

Adding a custom dependency property to a Control Template in XAML

I have managed to get further with my read only check box after a bit of a break and now have the functionality I want in a reasonably elegant form. The problem is I have used a bit of a hack to make it work, although this is not a disaster it would be nice to do it better.
To recap: I want a regular looking checkbox that does not self check when it is clicked, instead the click event triggers a background worker that later on causes a variable to be updated. This variable is bound to checkbox.ischecked and it is then updated with the new value.
I would like to use a control template based on the idea here:
A read-only CheckBox in C# WPF
I have modified this and stripped out stuff I thought I didn't need (perhaps unwisely) and ended up with:
<ResourceDictionary 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">
<!-- -->
<Style x:Key="ReadOnlyCheckBoxStyle" TargetType="{x:Type CheckBox}" >
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<BulletDecorator SnapsToDevicePixels="true" Background="Transparent">
<BulletDecorator.Bullet>
<Microsoft_Windows_Themes:BulletChrome Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
RenderMouseOver="{TemplateBinding IsMouseOver}"
IsChecked="{TemplateBinding Tag}">
</Microsoft_Windows_Themes:BulletChrome>
</BulletDecorator.Bullet>
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
RecognizesAccessKey="True" />
</BulletDecorator>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{StaticResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This checkbox works as described above and I call it like this:
<CheckBox x:Name="uiComboBox" Content="Does not set the backing property, but responds to it."
Style="{StaticResource ReadOnlyCheckBoxStyle}" Tag="{Binding MyBoolean}" Click="uiComboBox_Click"/>
The hack I made was to use the 'Tag' DependencyProperty to carry the data binding into the control template. This bypasses whatever mechanism is normally causing the checkbox to self check. To revert to a normal acting checkbox just change binding to Tag to a binding to IsChecked and inside the BulletDecorator set the TemplateBinding to IsChecked instead of Tag.
So I guess my questions are:
Have I got the wrong end of the stick? Is there a place where I can override whatever mechanism causes the box to self check? Perhaps in ControlTemplate Triggers?
Is it a good idea to go around eliminating any spare XAML that I think is just being brought in from the default CheckBox or should I try and keep a complete replacement for all styles?
If what I am doing is not too crazy, can I add a dependency property in XAML so that I don't have to use the Tag property?
It also occurs to me that perhaps what I really want is a button control that looks like a checkbox, maybe an invisible button with the usual animated checkbox on top which I bind data to the graphic of. Any thoughts on that plan would also be very welcome.
Thanks very much
Ed
I managed to sort out this problem and my ReadOnlyCheckBox idea, in the end I created a custom control based around Button and then applied a style to make it look like a CheckBox. I added my own IsChecked property that does not get set when the user clicks but is bound to the data so the displayed check only appears when the data changes.
C#:
public class ReadOnlyCheckBoxControl : System.Windows.Controls.Button
{
public static DependencyProperty IsCheckedProperty;
public ReadOnlyCheckBoxControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ReadOnlyCheckBoxControl), new FrameworkPropertyMetadata(typeof(ReadOnlyCheckBoxControl)));
}
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
static ReadOnlyCheckBoxControl()
{
IsCheckedProperty = DependencyProperty.Register("IsChecked", typeof(bool), typeof(ReadOnlyCheckBoxControl));
}
}
XAML:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:y="clr-namespace:ReadOnlyCheckBoxControlNS;assembly="
xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero">
<SolidColorBrush x:Key="CheckBoxFillNormal" Color="#F4F4F4" />
<SolidColorBrush x:Key="CheckBoxStroke" Color="#8E8F8F" />
<Style x:Key="EmptyCheckBoxFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle SnapsToDevicePixels="true"
Margin="1"
Stroke="Black"
StrokeDashArray="1 2"
StrokeThickness="1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CheckRadioFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle SnapsToDevicePixels="true"
Margin="14,0,0,0"
Stroke="Black"
StrokeDashArray="1 2"
StrokeThickness="1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type y:ReadOnlyCheckBoxControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type y:ReadOnlyCheckBoxControl}">
<BulletDecorator SnapsToDevicePixels="true" Background="Transparent">
<BulletDecorator.Bullet>
<Microsoft_Windows_Themes:BulletChrome Background="{StaticResource CheckBoxFillNormal}"
BorderBrush="{StaticResource CheckBoxStroke}"
RenderMouseOver="{TemplateBinding IsMouseOver}"
IsChecked="{TemplateBinding IsChecked}">
</Microsoft_Windows_Themes:BulletChrome>
</BulletDecorator.Bullet>
<ContentPresenter SnapsToDevicePixels="True"
HorizontalAlignment="Left"
Margin="4,0,0,0"
VerticalAlignment="Center"
RecognizesAccessKey="True" />
</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="{StaticResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Categories