Home
Options

Creating a styled WPF GroupBox in pure code

edited March 1

Starting here because this is a question specific to LINQPad, but I think I might need to post this on StackOverflow for a wider audience. That said, I know there are people far more knowledgeable than I who just might have a good solution for what I'm after. My apologies for the long post.

My goal is a WPF GroupBox styled like so:

This particular image is from a GroupBox created using VS2022 with the following XAML:

    <GroupBox Header="Arguments" Style="{x:Null}" Foreground="Orange">
      <GroupBox.Template>
        <ControlTemplate TargetType="{x:Type GroupBox}">
          <Grid SnapsToDevicePixels="true">
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width="6"/>
              <ColumnDefinition Width="Auto"/>
              <ColumnDefinition Width="*"/>
              <ColumnDefinition Width="6"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="*"/>
              <RowDefinition Height="6"/>
            </Grid.RowDefinitions>
            <Border BorderBrush="Transparent" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.ColumnSpan="4" Grid.Column="0" CornerRadius="4" Grid.Row="1" Grid.RowSpan="3"/>
            <Border BorderBrush="Transparent" BorderThickness="{TemplateBinding BorderThickness}" Grid.ColumnSpan="4" CornerRadius="4" Grid.Row="1" Grid.RowSpan="3">
              <Border.OpacityMask>
                <MultiBinding ConverterParameter="7">
                  <MultiBinding.Converter>
                    <BorderGapMaskConverter />
                  </MultiBinding.Converter>
                  <Binding ElementName="Header" Path="ActualWidth"/>
                  <Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}"/>
                  <Binding Path="ActualHeight" RelativeSource="{RelativeSource Self}"/>
                </MultiBinding>
              </Border.OpacityMask>
              <Border BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="3">
                <Border.BorderBrush>
                  <DrawingBrush Viewport="0,0,4,4" ViewportUnits="Absolute" TileMode="Tile">
                    <DrawingBrush.Drawing>
                      <DrawingGroup>
                        <GeometryDrawing Brush="Orange">
                          <GeometryDrawing.Geometry>
                            <GeometryGroup>
                              <RectangleGeometry Rect="0,0,50,50" />
                              <RectangleGeometry Rect="50,50,50,50" />
                            </GeometryGroup>
                          </GeometryDrawing.Geometry>
                        </GeometryDrawing>
                      </DrawingGroup>
                    </DrawingBrush.Drawing>
                  </DrawingBrush>
                </Border.BorderBrush>
                <Border BorderBrush="Transparent" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2"/>
              </Border>
            </Border>
            <Border x:Name="Header" Grid.Column="1" Padding="3,1,3,0" Grid.Row="0" Grid.RowSpan="2" Background="Transparent" BorderBrush="Transparent" CornerRadius="2" BorderThickness="1">
              <ContentPresenter ContentSource="Header" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
            </Border>
            <ContentPresenter Grid.ColumnSpan="2" Grid.Column="1" Margin="{TemplateBinding Padding}" Grid.Row="2" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
          </Grid>
        </ControlTemplate>
      </GroupBox.Template>
      <Label Foreground="White">Test</Label>
    </GroupBox>

My basic attempt starts as so:

var gb = new GroupBox();
gb.Header = "Arguments";
gb.Foreground = new SolidColorBrush(Colors.Orange);
var brush = new DrawingBrush
{
  Drawing = new DrawingGroup
  {
    Children = new DrawingCollection
    {
      new GeometryDrawing
      {
        Brush = new SolidColorBrush(Colors.Orange)
      }
    }
  },
  TileMode = TileMode.Tile,
  Viewport = new Rect(0, 0, 8, 8),
  ViewportUnits = BrushMappingMode.Absolute,
};
gb.BorderBrush = brush;
gb.Content = new Label { Content = "Test", Foreground = new SolidColorBrush(Color.FromRgb(220, 220, 220)), Padding = new Thickness(1, 0, 1, 1), FontSize = 13 };
var op = await PanelManager.StackWpfElementAsync(gb, "Test 123");
var wrapper = op.GetWpfElement() as ScrollViewer;
var dispatcher = wrapper.Dispatcher;
dispatcher.Invoke(() => wrapper.Background = new SolidColorBrush(Color.FromRgb(28, 28, 28)));

But this only results in a GroupBox looking like (zoomed in to show the actual displayed border):

Obviously it isn't dashed. However, it's not even orange. The double line is due to changing the background to black (essentially) while the default styling places white borders around the border (which defaults to black despite my attempt to change the brush used). My intention is to iterate into the style I'm looking for, but if I can't even get the color right to start, I think I must be missing something fundamental to getting it right.

If I modify the BorderBrush to be just

gb.BorderBrush = new SolidColorBrush(Colors.Orange);

the result is orange, but significantly washed out due to the other borders.

Comments

  • Options

    In terms of answer the question 'How do I create this XAML programmatically', StackOverflow is probably a better bet. However, if you already have working XAML, it can sometimes be easier just to parse the XAML:

    var template = (ControlTemplate)XamlReader.Parse("""
        <ControlTemplate TargetType="{x:Type GroupBox}" xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
          <Grid SnapsToDevicePixels="true">
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width="6"/>
              <ColumnDefinition Width="Auto"/>
              <ColumnDefinition Width="*"/>
              <ColumnDefinition Width="6"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="*"/>
              <RowDefinition Height="6"/>
            </Grid.RowDefinitions>
            <Border BorderBrush="Transparent" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.ColumnSpan="4" Grid.Column="0" CornerRadius="4" Grid.Row="1" Grid.RowSpan="3"/>
            <Border BorderBrush="Transparent" BorderThickness="{TemplateBinding BorderThickness}" Grid.ColumnSpan="4" CornerRadius="4" Grid.Row="1" Grid.RowSpan="3">
              <Border.OpacityMask>
                <MultiBinding ConverterParameter="7">
                  <MultiBinding.Converter>
                    <BorderGapMaskConverter />
                  </MultiBinding.Converter>
                  <Binding ElementName="Header" Path="ActualWidth"/>
                  <Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}"/>
                  <Binding Path="ActualHeight" RelativeSource="{RelativeSource Self}"/>
                </MultiBinding>
              </Border.OpacityMask>
              <Border BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="3">
                <Border.BorderBrush>
                  <DrawingBrush Viewport="0,0,4,4" ViewportUnits="Absolute" TileMode="Tile">
                    <DrawingBrush.Drawing>
                      <DrawingGroup>
                        <GeometryDrawing Brush="Orange">
                          <GeometryDrawing.Geometry>
                            <GeometryGroup>
                              <RectangleGeometry Rect="0,0,50,50" />
                              <RectangleGeometry Rect="50,50,50,50" />
                            </GeometryGroup>
                          </GeometryDrawing.Geometry>
                        </GeometryDrawing>
                      </DrawingGroup>
                    </DrawingBrush.Drawing>
                  </DrawingBrush>
                </Border.BorderBrush>
                <Border BorderBrush="Transparent" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2"/>
              </Border>
            </Border>
            <Border x:Name="Header" Grid.Column="1" Padding="3,1,3,0" Grid.Row="0" Grid.RowSpan="2" Background="Transparent" BorderBrush="Transparent" CornerRadius="2" BorderThickness="1">
              <ContentPresenter ContentSource="Header" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
            </Border>
            <ContentPresenter Grid.ColumnSpan="2" Grid.Column="1" Margin="{TemplateBinding Padding}" Grid.Row="2" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
          </Grid>
        </ControlTemplate>
        """);
    
    var gb = new GroupBox { Template = template, Header = 'Arguments' };
    gb.Dump();
    
  • Options

    Thanks Joe. Just one more thing I I had no clue about. :/

Sign In or Register to comment.