2

I have edited the standard GroupBox template as I wanted to customize it. Apart from other customizations, I wanted the GroupBox header to be Horizantally aligned in the Center instead of Left or Right. The alignment of the Header is not a problem however, the real problem is the OpacityMask defined for the Border controls. The opacity mask sets the transparent space behind the groupbox header where the borders are not drawn. I haven't able to figure it out how to place the transparent space / gap behind the groupbox header when I set the header to the center.

Here is how my XAML looks like: (Please navigate to the section beginning with "Border.OpacityMask" which sets the transparent gap in the border around the header)

<ControlTemplate x:Key="GroupBoxControlTemplate1" 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 Background="{TemplateBinding Background}" BorderBrush="Transparent" 
     BorderThickness="{TemplateBinding BorderThickness}" 
     CornerRadius="4" Grid.Column="1    " Grid.ColumnSpan="4" 
     Grid.Row="1" Grid.RowSpan="3" HorizontalAlignment="Stretch"/>

    <Border x:Name="Header" Grid.Column="2" Grid.RowSpan="2" HorizontalAlignment="Left" 
        Padding="3,1,3,0" VerticalAlignment="Stretch">
        <Border.Effect>
            <DropShadowEffect BlurRadius="10" Direction="334"/>
        </Border.Effect>
        <ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
         Content="{TemplateBinding Header}" 
         ContentSource="Header" 
         ContentStringFormat="{TemplateBinding HeaderStringFormat}" 
         ContentTemplate="{TemplateBinding HeaderTemplate}" 
         RecognizesAccessKey="True" Height="Auto" 
         VerticalAlignment="Center"
         HorizontalAlignment="Center"
         OpacityMask="#FF3844BD" Margin="0,1,0,0">
        </ContentPresenter>
    </Border>

    <ContentPresenter Margin="{TemplateBinding Padding}" 
        SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
        Content="{TemplateBinding Content}" 
        ContentStringFormat="{TemplateBinding ContentStringFormat}" 
        ContentTemplate="{TemplateBinding ContentTemplate}" 
        Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="2"/>
    <Border BorderBrush="White" BorderThickness="{TemplateBinding BorderThickness}" 
        CornerRadius="4" Grid.ColumnSpan="3" Grid.Row="1" Grid.RowSpan="3" RenderTransformOrigin="0.5,0.5" Margin="0">
        <Border.OpacityMask>
            <MultiBinding ConverterParameter="7" UpdateSourceTrigger="Default">
                <MultiBinding.Converter>
                    <BorderGapMaskConverter/>
                </MultiBinding.Converter>
                <Binding Path="ActualWidth" ElementName="Header"/>
                <Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}"/>
                <Binding Path="ActualHeight" RelativeSource="{RelativeSource Self}"/>
            </MultiBinding>
        </Border.OpacityMask>
        <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="4">
            <Border BorderBrush="White" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="4"/>
        </Border>
    </Border>
</Grid>

Many thanks for your help in advance.

-Wajahat

Wajahat Khan
  • 23
  • 1
  • 3
  • I have come across a similar question here (http://stackoverflow.com/questions/2104013/wpf-groupbox-header-postion-alignment) where the suggested answer is not what I want. The suggested solution here was so simply flip the borders so the blank space in the border is moved over to the right. I want the header to be in the center and not on the right. Thanks. – Wajahat Khan Nov 20 '10 at 22:12

1 Answers1

6

I had to do something similar some time ago, I wanted to create a GroupBox with two headers (one on the left and one on the right). I just used Reflector to get the code for BorderGapMaskConverter, and modified it to create my own converter. You could probably do the same here.


EDIT: I modified my converter to make it work for a centered header.

Here's the ControlTemplate

<ControlTemplate TargetType="{x:Type GroupBox}">
    <Grid SnapsToDevicePixels="true">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="6"/>
            <ColumnDefinition Width="*"/>
            <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 CornerRadius="4"
            Grid.Row="1"
            Grid.RowSpan="3"
            Grid.Column="0"
            Grid.ColumnSpan="5"
            BorderThickness="{TemplateBinding BorderThickness}"
            BorderBrush="Transparent"
            Background="{TemplateBinding Background}"/>
        <Border x:Name="Header"
            Padding="3,1,3,0"
            Grid.Row="0"
            Grid.RowSpan="2"
            Grid.Column="2">
            <ContentPresenter ContentSource="Header" 
                          RecognizesAccessKey="True" 
                          SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
        </Border>
        <ContentPresenter Grid.Row="2"
                      Grid.Column="1"
                      Grid.ColumnSpan="3"
                      Margin="{TemplateBinding Padding}"
                      SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
        <Border CornerRadius="4"
            Grid.Row="1"
            Grid.RowSpan="3"
            Grid.ColumnSpan="5"
            BorderThickness="{TemplateBinding BorderThickness}"
            BorderBrush="White">
            <Border.OpacityMask>
                <MultiBinding Converter="{StaticResource CenterBorderGapMaskConverter}">
                    <Binding ElementName="Header"
                         Path="ActualWidth"/>
                    <Binding RelativeSource="{RelativeSource Self}"
                         Path="ActualWidth"/>
                    <Binding RelativeSource="{RelativeSource Self}"
                         Path="ActualHeight"/>
                </MultiBinding>
            </Border.OpacityMask>

            <Border BorderThickness="{TemplateBinding BorderThickness}"
                BorderBrush="{TemplateBinding BorderBrush}"
                CornerRadius="3">
                <Border BorderThickness="{TemplateBinding BorderThickness}"
                    BorderBrush="White"
                    CornerRadius="2"/>
            </Border>
        </Border>
    </Grid>
</ControlTemplate>

And here's the converter:

class CenterBorderGapMaskConverter : IMultiValueConverter
{
    // Methods
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        Type type = typeof(double);
        if (values == null
            || values.Length != 3
            || values[0] == null
            || values[1] == null
            || values[2] == null
            || !type.IsAssignableFrom(values[0].GetType())
            || !type.IsAssignableFrom(values[1].GetType())
            || !type.IsAssignableFrom(values[2].GetType()))
        {
            return DependencyProperty.UnsetValue;
        }

        double pixels = (double)values[0];
        double width = (double)values[1];
        double height = (double)values[2];
        if ((width == 0.0) || (height == 0.0))
        {
            return null;
        }
        Grid visual = new Grid();
        visual.Width = width;
        visual.Height = height;
        ColumnDefinition colDefinition1 = new ColumnDefinition();
        ColumnDefinition colDefinition2 = new ColumnDefinition();
        ColumnDefinition colDefinition3 = new ColumnDefinition();
        colDefinition1.Width = new GridLength(1.0, GridUnitType.Star);
        colDefinition2.Width = new GridLength(pixels);
        colDefinition3.Width = new GridLength(1.0, GridUnitType.Star);
        visual.ColumnDefinitions.Add(colDefinition1);
        visual.ColumnDefinitions.Add(colDefinition2);
        visual.ColumnDefinitions.Add(colDefinition3);
        RowDefinition rowDefinition1 = new RowDefinition();
        RowDefinition rowDefinition2 = new RowDefinition();
        rowDefinition1.Height = new GridLength(height / 2.0);
        rowDefinition2.Height = new GridLength(1.0, GridUnitType.Star);
        visual.RowDefinitions.Add(rowDefinition1);
        visual.RowDefinitions.Add(rowDefinition2);
        Rectangle rectangle1 = new Rectangle();
        Rectangle rectangle2 = new Rectangle();
        Rectangle rectangle3 = new Rectangle();
        rectangle1.Fill = Brushes.Black;
        rectangle2.Fill = Brushes.Black;
        rectangle3.Fill = Brushes.Black;
        Grid.SetRowSpan(rectangle1, 2);
        Grid.SetRow(rectangle1, 0);
        Grid.SetColumn(rectangle1, 0);
        Grid.SetRow(rectangle2, 1);
        Grid.SetColumn(rectangle2, 1);
        Grid.SetRowSpan(rectangle3, 2);
        Grid.SetRow(rectangle3, 0);
        Grid.SetColumn(rectangle3, 2);
        visual.Children.Add(rectangle1);
        visual.Children.Add(rectangle2);
        visual.Children.Add(rectangle3);
        return new VisualBrush(visual);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        return new object[] { Binding.DoNothing };
    }
}
Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • Many thanks Thomas, your suggestion works like a charm. I am disappointed with WPF however, that there is still a steep learning curve involved when doing these sort of customizations which aren't straightforward and still cumbersome. – Wajahat Khan Nov 21 '10 at 00:14
  • 1
    Yes, WPF hard is a bit difficult when you start... But when you start to understand it better, you can do things you could never have done in WinForms. Anyway, it's usually much easier to achieve what you want than in this specific case. You rarely need to create this kind of converter... – Thomas Levesque Nov 21 '10 at 00:39
  • +1, was just doing the same thing and when I reached the `BorderGapMaskConverter` I thought ouch... :) Great work! – Fredrik Hedblad Feb 05 '11 at 02:11
  • Hi, I was wondering if you could post the code for your dual-headered groupbox. It's exactly what I need and I can't find an easy way to accomplish this. If I could get the code for your groupbox or even the compiled DLL that contains it, I would really appreciate it. – Aphex May 31 '11 at 21:27
  • 4
    @Aphex, here it is: [DualHeaderGroupBox](http://pastebin.com/0qWKT5x0), [DualBorderGapMaskConverter](http://pastebin.com/ERffSdFs), [default style](http://pastebin.com/KutiRTwt) – Thomas Levesque May 31 '11 at 22:45