From 3f00647223dcb54281b08ae034136d427b9ce0d2 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 19 Mar 2026 14:09:22 +0000 Subject: [PATCH] Change visual brush for group box to grid (#20947) * change visual brush for group box to grid * remove unused usings * Addressed review * cleanup --- src/Avalonia.Base/Rect.cs | 1 - .../Converters/BorderGapMaskConverter.cs | 126 ++++++++++++++++++ .../Controls/GroupBox.xaml | 108 +++++++-------- .../Controls/GroupBox.xaml | 106 +++++++-------- 4 files changed, 233 insertions(+), 108 deletions(-) create mode 100644 src/Avalonia.Controls/Converters/BorderGapMaskConverter.cs diff --git a/src/Avalonia.Base/Rect.cs b/src/Avalonia.Base/Rect.cs index 58a8c56c8b..9c901254a6 100644 --- a/src/Avalonia.Base/Rect.cs +++ b/src/Avalonia.Base/Rect.cs @@ -1,7 +1,6 @@ using System; using System.Globalization; using System.Numerics; -using Avalonia.Animation.Animators; using Avalonia.Utilities; namespace Avalonia diff --git a/src/Avalonia.Controls/Converters/BorderGapMaskConverter.cs b/src/Avalonia.Controls/Converters/BorderGapMaskConverter.cs new file mode 100644 index 0000000000..913b2f1534 --- /dev/null +++ b/src/Avalonia.Controls/Converters/BorderGapMaskConverter.cs @@ -0,0 +1,126 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Globalization; +using Avalonia.Controls.Shapes; +using Avalonia.Data.Converters; +using Avalonia.Media; + +namespace Avalonia.Controls.Converters +{ + // Ported from https://github.com/dotnet/wpf/blob/main/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/BorderGapMaskConverter.cs + + /// + /// Converter that generates the visual brush for + /// + public class BorderGapMaskConverter : IMultiValueConverter + { + /// + /// Convert a value. + /// + /// values as produced by source binding + /// target type + /// converter parameter + /// culture information + /// + /// Converted value. + /// Visual Brush that is used as the opacity mask for the Border + /// in the style for GroupBox. + /// + public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) + { + // + // Parameter Validation + // + if (parameter == null || + values == null || + values.Count != 3 || + values[0] is not double || + values[1] is not double || + values[2] is not double) + { + return AvaloniaProperty.UnsetValue; + } + + if (parameter is not double && parameter is not string) + { + return AvaloniaProperty.UnsetValue; + } + + // + // Conversion + // + double? headerWidth = (double?)values[0]; + double? borderWidth = (double?)values[1]; + double? borderHeight = (double?)values[2]; + + // Doesn't make sense to have a Grid + // with 0 as width or height + if (borderWidth == 0 + || borderHeight == 0) + { + return null; + } + + // Width of the line to the left of the header + // to be used to set the width of the first column of the Grid + double lineWidth; + if (parameter is string) + { + lineWidth = Double.Parse(((string)parameter), NumberFormatInfo.InvariantInfo); + } + else + { + lineWidth = (double)parameter; + } + + Grid grid = new Grid + { + Width = borderWidth ?? 0, + Height = borderHeight ?? 0 + }; + + ColumnDefinition colDef1 = new ColumnDefinition(); + ColumnDefinition colDef2 = new ColumnDefinition(); + ColumnDefinition colDef3 = new ColumnDefinition(); + colDef1.Width = new GridLength(lineWidth); + colDef2.Width = new GridLength(headerWidth ?? 0); + colDef3.Width = new GridLength(1, GridUnitType.Star); + grid.ColumnDefinitions.Add(colDef1); + grid.ColumnDefinitions.Add(colDef2); + grid.ColumnDefinitions.Add(colDef3); + RowDefinition rowDef1 = new RowDefinition(); + RowDefinition rowDef2 = new RowDefinition(); + rowDef1.Height = new GridLength((borderHeight ?? 0) / 2); + rowDef2.Height = new GridLength(1, GridUnitType.Star); + grid.RowDefinitions.Add(rowDef1); + grid.RowDefinitions.Add(rowDef2); + + Rectangle rectColumn1 = new Rectangle(); + Rectangle rectColumn2 = new Rectangle(); + Rectangle rectColumn3 = new Rectangle(); + rectColumn1.Fill = Brushes.Black; + rectColumn2.Fill = Brushes.Black; + rectColumn3.Fill = Brushes.Black; + + Grid.SetRowSpan(rectColumn1, 2); + Grid.SetRow(rectColumn1, 0); + Grid.SetColumn(rectColumn1, 0); + + Grid.SetRow(rectColumn2, 1); + Grid.SetColumn(rectColumn2, 1); + + Grid.SetRowSpan(rectColumn3, 2); + Grid.SetRow(rectColumn3, 0); + Grid.SetColumn(rectColumn3, 2); + + grid.Children.Add(rectColumn1); + grid.Children.Add(rectColumn2); + grid.Children.Add(rectColumn3); + + return (new VisualBrush(grid)); + } + } +} diff --git a/src/Avalonia.Themes.Fluent/Controls/GroupBox.xaml b/src/Avalonia.Themes.Fluent/Controls/GroupBox.xaml index 4278b91e36..5b31fdb55c 100644 --- a/src/Avalonia.Themes.Fluent/Controls/GroupBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/GroupBox.xaml @@ -1,11 +1,13 @@ + @@ -18,62 +20,60 @@ - + - - - - - - - - - - - + + + + + + + + + - - - - - - + + + VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" + UseLayoutRounding="{TemplateBinding UseLayoutRounding}" /> + + + @@ -84,7 +84,7 @@ + diff --git a/src/Avalonia.Themes.Simple/Controls/GroupBox.xaml b/src/Avalonia.Themes.Simple/Controls/GroupBox.xaml index 526ba7cc77..826c3ec2fe 100644 --- a/src/Avalonia.Themes.Simple/Controls/GroupBox.xaml +++ b/src/Avalonia.Themes.Simple/Controls/GroupBox.xaml @@ -1,11 +1,13 @@ + @@ -16,60 +18,58 @@ - + - - - - - - - - - - - + + + + + + + + + - - - - - - + + + VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" + UseLayoutRounding="{TemplateBinding UseLayoutRounding}" /> + + + @@ -80,6 +80,6 @@ - + +