From 66becae09828079d0401876ee817abdec195b468 Mon Sep 17 00:00:00 2001 From: Corvin <43533385+corvinsz@users.noreply.github.com> Date: Tue, 10 Nov 2026 10:24:05 +0100 Subject: [PATCH] feat(CheckBox, RadioButton): Add ContentPlacement attached property --- src/MainDemo.Wpf/Toggles.xaml | 6 + src/MaterialDesign3.Demo.Wpf/Toggles.xaml | 27 ++-- .../ContentPlacementExample.xaml | 52 +++++++ .../ContentPlacementExample.xaml.cs | 12 ++ .../ContentPlacementExample.xaml | 52 +++++++ .../ContentPlacementExample.xaml.cs | 12 ++ .../CheckBoxAssist.cs | 13 ++ .../RadioButtonAssist.cs | 14 ++ .../Themes/MaterialDesignTheme.CheckBox.xaml | 142 ++++++++++------- .../MaterialDesignTheme.RadioButton.xaml | 144 ++++++++++-------- .../CheckBoxAssistTests.cs | 35 +++++ .../RadioButtonAssistTests.cs | 38 +++++ 12 files changed, 416 insertions(+), 131 deletions(-) create mode 100644 src/MaterialDesignDemo.Shared/Examples/CheckBoxExamples/ContentPlacementExample.xaml create mode 100644 src/MaterialDesignDemo.Shared/Examples/CheckBoxExamples/ContentPlacementExample.xaml.cs create mode 100644 src/MaterialDesignDemo.Shared/Examples/RadioButtonExamples/ContentPlacementExample.xaml create mode 100644 src/MaterialDesignDemo.Shared/Examples/RadioButtonExamples/ContentPlacementExample.xaml.cs create mode 100644 tests/MaterialDesignThemes.Wpf.Tests/RadioButtonAssistTests.cs diff --git a/src/MainDemo.Wpf/Toggles.xaml b/src/MainDemo.Wpf/Toggles.xaml index 2700d706d9..475abb25a5 100644 --- a/src/MainDemo.Wpf/Toggles.xaml +++ b/src/MainDemo.Wpf/Toggles.xaml @@ -4,6 +4,8 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:domain="clr-namespace:MaterialDesignDemo.Domain" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" + xmlns:sharedCbs="clr-namespace:MaterialDesignDemo.Shared.Examples.CheckBoxExamples;assembly=MaterialDesignDemo.Shared" + xmlns:sharedRbs="clr-namespace:MaterialDesignDemo.Shared.Examples.RadioButtonExamples;assembly=MaterialDesignDemo.Shared" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:smtx="clr-namespace:ShowMeTheXAML;assembly=ShowMeTheXAML" d:DesignHeight="4096" @@ -223,6 +225,8 @@ + + @@ -289,6 +293,8 @@ + + diff --git a/src/MaterialDesign3.Demo.Wpf/Toggles.xaml b/src/MaterialDesign3.Demo.Wpf/Toggles.xaml index 44da7c0e82..8fc2b1455b 100644 --- a/src/MaterialDesign3.Demo.Wpf/Toggles.xaml +++ b/src/MaterialDesign3.Demo.Wpf/Toggles.xaml @@ -4,6 +4,8 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:domain="clr-namespace:MaterialDesign3Demo.Domain" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" + xmlns:sharedCbs="clr-namespace:MaterialDesignDemo.Shared.Examples.CheckBoxExamples;assembly=MaterialDesignDemo.Shared" + xmlns:sharedRbs="clr-namespace:MaterialDesignDemo.Shared.Examples.RadioButtonExamples;assembly=MaterialDesignDemo.Shared" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:smtx="clr-namespace:ShowMeTheXAML;assembly=ShowMeTheXAML" d:DesignHeight="700" @@ -204,6 +206,8 @@ + + - - - + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/MaterialDesignDemo.Shared/Examples/CheckBoxExamples/ContentPlacementExample.xaml.cs b/src/MaterialDesignDemo.Shared/Examples/CheckBoxExamples/ContentPlacementExample.xaml.cs new file mode 100644 index 0000000000..58d3241884 --- /dev/null +++ b/src/MaterialDesignDemo.Shared/Examples/CheckBoxExamples/ContentPlacementExample.xaml.cs @@ -0,0 +1,12 @@ +namespace MaterialDesignDemo.Shared.Examples.CheckBoxExamples; + +/// +/// Interaction logic for ContentPlacementExample.xaml +/// +public partial class ContentPlacementExample : UserControl +{ + public ContentPlacementExample() + { + InitializeComponent(); + } +} diff --git a/src/MaterialDesignDemo.Shared/Examples/RadioButtonExamples/ContentPlacementExample.xaml b/src/MaterialDesignDemo.Shared/Examples/RadioButtonExamples/ContentPlacementExample.xaml new file mode 100644 index 0000000000..fb7dc7a7d9 --- /dev/null +++ b/src/MaterialDesignDemo.Shared/Examples/RadioButtonExamples/ContentPlacementExample.xaml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/MaterialDesignDemo.Shared/Examples/RadioButtonExamples/ContentPlacementExample.xaml.cs b/src/MaterialDesignDemo.Shared/Examples/RadioButtonExamples/ContentPlacementExample.xaml.cs new file mode 100644 index 0000000000..e9a386fd5b --- /dev/null +++ b/src/MaterialDesignDemo.Shared/Examples/RadioButtonExamples/ContentPlacementExample.xaml.cs @@ -0,0 +1,12 @@ +namespace MaterialDesignDemo.Shared.Examples.RadioButtonExamples; + +/// +/// Interaction logic for ContentPlacementExample.xaml +/// +public partial class ContentPlacementExample : UserControl +{ + public ContentPlacementExample() + { + InitializeComponent(); + } +} diff --git a/src/MaterialDesignThemes.Wpf/CheckBoxAssist.cs b/src/MaterialDesignThemes.Wpf/CheckBoxAssist.cs index 529a1a293c..c9e3a53d33 100644 --- a/src/MaterialDesignThemes.Wpf/CheckBoxAssist.cs +++ b/src/MaterialDesignThemes.Wpf/CheckBoxAssist.cs @@ -11,4 +11,17 @@ public static readonly DependencyProperty CheckBoxSizeProperty public static double GetCheckBoxSize(CheckBox element) => (double)element.GetValue(CheckBoxSizeProperty); public static void SetCheckBoxSize(CheckBox element, double checkBoxSize) => element.SetValue(CheckBoxSizeProperty, checkBoxSize); #endregion + + #region AttachedProperty : ContentPlacementProperty + /// + /// Where the content is placed relative to the check box glyph. + /// + public static readonly DependencyProperty ContentPlacementProperty + = DependencyProperty.RegisterAttached("ContentPlacement", typeof(Dock), typeof(CheckBoxAssist), new PropertyMetadata(Dock.Right)); + + public static Dock GetContentPlacement(CheckBox element) + => (Dock)element.GetValue(ContentPlacementProperty); + public static void SetContentPlacement(CheckBox element, Dock value) + => element.SetValue(ContentPlacementProperty, value); + #endregion } diff --git a/src/MaterialDesignThemes.Wpf/RadioButtonAssist.cs b/src/MaterialDesignThemes.Wpf/RadioButtonAssist.cs index c36cf7e677..9e426eba42 100644 --- a/src/MaterialDesignThemes.Wpf/RadioButtonAssist.cs +++ b/src/MaterialDesignThemes.Wpf/RadioButtonAssist.cs @@ -19,5 +19,19 @@ public static double GetRadioButtonSize(RadioButton element) => public static void SetRadioButtonSize(RadioButton element, double checkBoxSize) => element.SetValue(RadioButtonSizeProperty, checkBoxSize); #endregion + + #region AttachedProperty : ContentPlacementProperty + /// + /// Where the content is placed relative to the radio button glyph. + /// + public static readonly DependencyProperty ContentPlacementProperty + = DependencyProperty.RegisterAttached("ContentPlacement", typeof(Dock), typeof(RadioButtonAssist), new PropertyMetadata(Dock.Right)); + + public static Dock GetContentPlacement(RadioButton element) + => (Dock)element.GetValue(ContentPlacementProperty); + + public static void SetContentPlacement(RadioButton element, Dock value) + => element.SetValue(ContentPlacementProperty, value); + #endregion } } diff --git a/src/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.CheckBox.xaml b/src/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.CheckBox.xaml index 2c5d2fbdc4..be67db5b09 100644 --- a/src/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.CheckBox.xaml +++ b/src/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.CheckBox.xaml @@ -97,65 +97,78 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - + + + - + + + + + + + + + + + + + @@ -249,16 +262,16 @@ - - - - - - + - + + + + + + + + + + + + + diff --git a/src/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.RadioButton.xaml b/src/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.RadioButton.xaml index 9f7e8800ef..14e5e2220e 100644 --- a/src/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.RadioButton.xaml +++ b/src/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.RadioButton.xaml @@ -69,66 +69,77 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - + + + - + + + + + + + + + + + + + @@ -216,16 +227,16 @@ - - - - - - + + HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" + VerticalAlignment="{TemplateBinding VerticalContentAlignment}" + DockPanel.Dock="Left"> - + + + + + + + + + + + + + diff --git a/tests/MaterialDesignThemes.Wpf.Tests/CheckBoxAssistTests.cs b/tests/MaterialDesignThemes.Wpf.Tests/CheckBoxAssistTests.cs index cd0f875245..5f34f33ea6 100644 --- a/tests/MaterialDesignThemes.Wpf.Tests/CheckBoxAssistTests.cs +++ b/tests/MaterialDesignThemes.Wpf.Tests/CheckBoxAssistTests.cs @@ -16,4 +16,39 @@ public async Task TestCheckBoxSizeProperty() CheckBoxAssist.SetCheckBoxSize(testElement, 27.1); await Assert.That(CheckBoxAssist.GetCheckBoxSize(testElement)).IsEqualTo(27.1); } + + [Test, STAThreadExecutor] + public async Task TestContentPlacementProperty() + { + CheckBox testElement = new(); + + // Assert defaults + await Assert.That(CheckBoxAssist.ContentPlacementProperty.Name).IsEqualTo("ContentPlacement"); + await Assert.That(CheckBoxAssist.GetContentPlacement(testElement)).IsEqualTo(Dock.Right); + + // Assert setting works + CheckBoxAssist.SetContentPlacement(testElement, Dock.Top); + await Assert.That(CheckBoxAssist.GetContentPlacement(testElement)).IsEqualTo(Dock.Top); + } + + [Test, STAThreadExecutor] + // Dont parse the theme dictionaries in parallel, as it causes race conditions in WPFs XAML reader + [NotInParallel("ThemeDictionaryParsing")] + [Arguments("MaterialDesignCheckBox", Dock.Right, Dock.Left)] + [Arguments("MaterialDesignCheckBox", Dock.Left, Dock.Right)] + [Arguments("MaterialDesignCheckBox", Dock.Top, Dock.Bottom)] + [Arguments("MaterialDesignCheckBox", Dock.Bottom, Dock.Top)] + [Arguments("MaterialDesignUserForegroundCheckBox", Dock.Right, Dock.Left)] + [Arguments("MaterialDesignUserForegroundCheckBox", Dock.Left, Dock.Right)] + [Arguments("MaterialDesignUserForegroundCheckBox", Dock.Top, Dock.Bottom)] + [Arguments("MaterialDesignUserForegroundCheckBox", Dock.Bottom, Dock.Top)] + public async Task ContentPlacement_DocksGlyphOnOppositeSide(string styleKey, Dock placement, Dock expectedGlyphDock) + { + CheckBox checkBox = new() { Content = "Label" }; + CheckBoxAssist.SetContentPlacement(checkBox, placement); + checkBox.ApplyStyle(styleKey); + + var glyph = (FrameworkElement)checkBox.Template.FindName("IconWrapper", checkBox); + await Assert.That(DockPanel.GetDock(glyph)).IsEqualTo(expectedGlyphDock); + } } diff --git a/tests/MaterialDesignThemes.Wpf.Tests/RadioButtonAssistTests.cs b/tests/MaterialDesignThemes.Wpf.Tests/RadioButtonAssistTests.cs new file mode 100644 index 0000000000..88cd7fd775 --- /dev/null +++ b/tests/MaterialDesignThemes.Wpf.Tests/RadioButtonAssistTests.cs @@ -0,0 +1,38 @@ +namespace MaterialDesignThemes.Wpf.Tests; + +[TestExecutor] +public class RadioButtonAssistTests +{ + [Test, STAThreadExecutor] + public async Task TestContentPlacementProperty() + { + RadioButton testElement = new(); + + await Assert.That(RadioButtonAssist.ContentPlacementProperty.Name).IsEqualTo("ContentPlacement"); + await Assert.That(RadioButtonAssist.GetContentPlacement(testElement)).IsEqualTo(Dock.Right); + + RadioButtonAssist.SetContentPlacement(testElement, Dock.Left); + await Assert.That(RadioButtonAssist.GetContentPlacement(testElement)).IsEqualTo(Dock.Left); + } + + [Test, STAThreadExecutor] + // Dont parse the theme dictionaries in parallel, as it causes race conditions in WPFs XAML reader + [NotInParallel("ThemeDictionaryParsing")] + [Arguments("MaterialDesignRadioButton", Dock.Right, Dock.Left)] + [Arguments("MaterialDesignRadioButton", Dock.Left, Dock.Right)] + [Arguments("MaterialDesignRadioButton", Dock.Top, Dock.Bottom)] + [Arguments("MaterialDesignRadioButton", Dock.Bottom, Dock.Top)] + [Arguments("MaterialDesignUserForegroundRadioButton", Dock.Right, Dock.Left)] + [Arguments("MaterialDesignUserForegroundRadioButton", Dock.Left, Dock.Right)] + [Arguments("MaterialDesignUserForegroundRadioButton", Dock.Top, Dock.Bottom)] + [Arguments("MaterialDesignUserForegroundRadioButton", Dock.Bottom, Dock.Top)] + public async Task ContentPlacement_DocksGlyphOnOppositeSide(string styleKey, Dock contentPlacement, Dock expectedGlyphDock) + { + RadioButton radioButton = new() { Content = "Label" }; + RadioButtonAssist.SetContentPlacement(radioButton, contentPlacement); + radioButton.ApplyStyle(styleKey); + + var glyph = (FrameworkElement)radioButton.Template.FindName("IconWrapper", radioButton); + await Assert.That(DockPanel.GetDock(glyph)).IsEqualTo(expectedGlyphDock); + } +}