Pergunta

I'm creating a groupbox in WPF to contain my controls, and was wondering whether it's possible to change the header to run vertically instead of horizontally? Here's my code;

<GroupBox Grid.Column="0">
    <GroupBox.Header>
        Navigation
    </GroupBox.Header>
    <StackPanel Orientation="Vertical" >
        <Button>Up</Button>
        <Button>Down</Button>
    </StackPanel>
</GroupBox>

I've explored the properties, but can't seem to find anything other than altering a TextBlock contained within the GroupBox.Header element.

<GroupBox Grid.Column="0">
    <GroupBox.Header>
        <Orientation>
           <!-- Invalid -->
        </Orientation>
    </GroupBox.Header>
    <StackPanel Orientation="Vertical" >
        <Button>Up</Button>
        <Button>Down</Button>
    </StackPanel>
</GroupBox>
Foi útil?

Solução

You can but it isn't pretty! Badly rotated header

<GroupBox>
    <GroupBox.Header>
        <TextBlock Text="Hello">
            <TextBlock.RenderTransform>
                 <RotateTransform Angle="90" CenterX="0" CenterY="0"  /> 
            </TextBlock.RenderTransform>
        </TextBlock>
    </GroupBox.Header>
  <TextBlock Text="World!"/>
</GroupBox>

You are also going to need to modify the style of the GroupBox to support this.

This looks like its just a grid with a couple of rows, a couple of content presenters and a couple of borders, so it shouldbe possible to turn that into columns and go from there. I'd actually put the Rotate on the ContentPresenter that does the header, so then you can just apply your style wherever you want it.

Final Update

By extracting the template out of the standard control we can modify it to move the header round, however we also find that there is a converter used to mask the border around. By using a decompiler (DotPeek) we can also switched round the rows and columns to move the gap to the side.

Rotated GroupBox

So the template looks like this

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <BorderGapMaskConverter x:Key="BorderGapMaskConverter"/>
    <Style x:Key="GroupBoxStyle1" TargetType="{x:Type GroupBox}">
        <Setter Property="BorderBrush" Value="#D5DFE5"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type GroupBox}">
                    <Grid SnapsToDevicePixels="true">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="6"/>
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="6"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="6"/>
                        </Grid.RowDefinitions>
                        <Border BorderBrush="Transparent" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.ColumnSpan="3" Grid.Column="1" CornerRadius="4" Grid.Row="0" Grid.RowSpan="4"/>
                        <Border x:Name="Header" Grid.Column="0" Padding="3,1,3,0" Grid.Row="1" Grid.ColumnSpan="2">
                            <Border.LayoutTransform>
                                <RotateTransform Angle="-90"/>
                            </Border.LayoutTransform>
                            <ContentPresenter ContentSource="Header" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                        </Border>
                        <ContentPresenter Grid.RowSpan="2" Grid.Row="1" Margin="{TemplateBinding Padding}" Grid.Column="2" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                        <Border BorderBrush="White" BorderThickness="{TemplateBinding BorderThickness}" Grid.RowSpan="4" CornerRadius="4" Grid.Column="1" Grid.ColumnSpan="3">
                            <Border.OpacityMask>
                                <MultiBinding ConverterParameter="7" Converter="{StaticResource BorderGapMaskConverter}">
                                    <Binding ElementName="Header" Path="ActualWidth"/>
                                    <Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}"/>
                                    <Binding Path="ActualHeight" RelativeSource="{RelativeSource Self}"/>
                                </MultiBinding>
                            </Border.OpacityMask>
                            <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="3">
                                <Border BorderBrush="White" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2"/>
                            </Border>
                        </Border>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>


<GroupBox Header="Hello world!" Margin="12" Style="{DynamicResource GroupBoxStyle1}"/>
</Window>

And the modified converter looks like this

// Type: System.Windows.Controls.BorderGapMaskConverter
// Assembly: PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
// Assembly location: C:\Windows\Microsoft.NET\assembly\GAC_MSIL\PresentationFramework\v4.0_4.0.0.0__31bf3856ad364e35\PresentationFramework.dll

using System;
using System.Globalization;
using System.Runtime;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Shapes;

namespace WfpApplication1 //System.Windows.Controls
{
    public class LeftBorderGapMaskConverter : IMultiValueConverter
{
    [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    public LeftBorderGapMaskConverter()
    {
        //      base.ctor();
    }

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        Type type1 = typeof(double);
        if (parameter == null
            || values == null
            || (values.Length != 3 || values[0] == null)
            || (values[1] == null
                || values[2] == null
                || (!type1.IsAssignableFrom(values[0].GetType())
                    || !type1.IsAssignableFrom(values[1].GetType())))
            || !type1.IsAssignableFrom(values[2].GetType()))
            return DependencyProperty.UnsetValue;

        Type type2 = parameter.GetType();
        if (!type1.IsAssignableFrom(type2)
            && !typeof(string).IsAssignableFrom(type2))
            return DependencyProperty.UnsetValue;

        double pixels1 = (double)values[0];
        double num1 = (double)values[1];
        double num2 = (double)values[2];
        if (num1 == 0.0 || num2 == 0.0)
            return (object)null;

        double pixels2 = !(parameter is string)
            ? (double)parameter
            : double.Parse((string)parameter, (IFormatProvider)NumberFormatInfo.InvariantInfo);

        Grid grid = new Grid();
        grid.Width = num1;
        grid.Height = num2;
        RowDefinition RowDefinition1 = new RowDefinition();
        RowDefinition RowDefinition2 = new RowDefinition();
        RowDefinition RowDefinition3 = new RowDefinition();
        RowDefinition1.Height = new GridLength(pixels2);
        RowDefinition2.Height = new GridLength(pixels1);
        RowDefinition3.Height = new GridLength(1.0, GridUnitType.Star);
        grid.RowDefinitions.Add(RowDefinition1);
        grid.RowDefinitions.Add(RowDefinition2);
        grid.RowDefinitions.Add(RowDefinition3);
        ColumnDefinition ColumnDefinition1 = new ColumnDefinition();
        ColumnDefinition ColumnDefinition2 = new ColumnDefinition();
        ColumnDefinition1.Width = new GridLength(num2 / 2.0);
        ColumnDefinition2.Width = new GridLength(1.0, GridUnitType.Star);
        grid.ColumnDefinitions.Add(ColumnDefinition1);
        grid.ColumnDefinitions.Add(ColumnDefinition2);
        Rectangle rectangle1 = new Rectangle();
        Rectangle rectangle2 = new Rectangle();
        Rectangle rectangle3 = new Rectangle();
        rectangle1.Fill = (Brush)Brushes.Black;
        rectangle2.Fill = (Brush)Brushes.Black;
        rectangle3.Fill = (Brush)Brushes.Black;

        Grid.SetColumnSpan((UIElement)rectangle1, 2);
        Grid.SetColumn((UIElement)rectangle1, 0);
        Grid.SetRow((UIElement)rectangle1, 0);
        Grid.SetColumn((UIElement)rectangle2, 1);
        Grid.SetRow((UIElement)rectangle2, 1);
        Grid.SetColumnSpan((UIElement)rectangle3, 2);
        Grid.SetColumn((UIElement)rectangle3, 0);
        Grid.SetRow((UIElement)rectangle3, 2);
        grid.Children.Add((UIElement)rectangle1);
        grid.Children.Add((UIElement)rectangle2);
        grid.Children.Add((UIElement)rectangle3);
        return (object)new VisualBrush((Visual)grid);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        return new object[1]
          {
            Binding.DoNothing
          };
    }
}
}

Outras dicas

I don't think you can get the group box header to run vertically by simply changing properties or using a header template. Instead you can try the RotateTransform, but it will rotate all elements inside the group box as well. So you have to give counter transform for the container element inside the group box separately.

<GroupBox Header="Navigation" Grid.Column="0">
    <GroupBox.RenderTransform>
        <RotateTransform Angle="-90" CenterX="100" CenterY="100" />
    </GroupBox.RenderTransform>
    <StackPanel Orientation="Vertical">
        <StackPanel.RenderTransform>
            <RotateTransform Angle="90" CenterX="100" CenterY="100"  />
        </StackPanel.RenderTransform>
        <Button>Up</Button>
        <Button>Down</Button>
    </StackPanel>
</GroupBox>

Getting the elements to align would be messy and you wil have to play with CenterX and CenterY properties all day long. But if that's what you want I guess this is the way to get it :)

I can imagine two options of vertical header in group box:

1) Rotate the "flow" of the textblock but don't rotate letters

2) Rotate textblock itself

Here are these two options in XAML:

    <Grid>
        <Grid.Resources>
            <System:String x:Key="header">Vertical</System:String>
        </Grid.Resources>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <GroupBox Grid.Row="0">
            <GroupBox.HeaderTemplate>
                <DataTemplate>
                    <ItemsControl ItemsSource="{StaticResource header}" />
                </DataTemplate>
            </GroupBox.HeaderTemplate>
        </GroupBox>
        <GroupBox Grid.Row="1">
            <GroupBox.HeaderTemplate>
                <DataTemplate>
                    <TextBlock Text="{StaticResource header}">
                        <TextBlock.LayoutTransform>
                            <RotateTransform Angle="90"  />
                        </TextBlock.LayoutTransform>
                    </TextBlock>
                </DataTemplate>
            </GroupBox.HeaderTemplate>
        </GroupBox>
    </Grid>
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top