Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GroupBox] Missing #823

Closed
SuperJMN opened this issue Dec 13, 2016 · 10 comments
Closed

[GroupBox] Missing #823

SuperJMN opened this issue Dec 13, 2016 · 10 comments

Comments

@SuperJMN
Copy link
Contributor

There is no GroupBox like In WPF. Although it's not essential, this control is useful to label groups of controls.

TEMPORARY TRICK:
Although it doesn't exist, it can be emulated with a HeaderedContentControl with a custom Style:

        <Style Selector="HeaderedContentControl">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Border>
                            <StackPanel>
                                <ContentPresenter TextBlock.FontWeight="Bold" Content="{TemplateBinding Header}" />
                                <Border
                                    BorderBrush="{TemplateBinding Background}"
                                    BorderThickness="2"
                                    CornerRadius="5">
                                    <ContentPresenter Content="{TemplateBinding Content}" />
                                </Border>
                            </StackPanel>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
@grokys grokys modified the milestones: Post-1.0, 1.0 Jan 7, 2017
@derekantrican
Copy link
Contributor

Is this still being considered or is it old?

@FoggyFinder
Copy link
Contributor

Probably the issue should be closed: #3913

@derekantrican
Copy link
Contributor

Yeah, I think agreed. I also saw that PR and it seems the conclusion was it's not going to be added because "It is just a styled HeaderedContentControl"?

@Gillibald
Copy link
Contributor

#3913

@derekantrican
Copy link
Contributor

derekantrican commented Sep 14, 2020

For people who look at this in the future (which I'm sure will be the case unless one day Avalonia officially implements GroupBox), the workaround by @SuperJMN is great - you just have to assign the Border property, which is different from how normal WPF works (I think it's just black by default):

image

        <Style Selector="HeaderedContentControl">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Border>
                            <StackPanel>
                                <ContentPresenter TextBlock.FontWeight="Bold" Content="{TemplateBinding Header}" />
                                <Border
                                    BorderBrush="{TemplateBinding Background}"
                                    BorderThickness="2"
                                    CornerRadius="5">
                                    <ContentPresenter Content="{TemplateBinding Content}" />
                                </Border>
                            </StackPanel>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <HeaderedContentControl Header="Header" Background="Black">
          <TextBlock Text="text" Margin="5"/>
        </HeaderedContentControl>

But if you want something a little more classic-style, I created this:

image

    <Style Selector="HeaderedContentControl">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate>
            <Grid>
              <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
              </Grid.RowDefinitions>
              <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
              </Grid.ColumnDefinitions>
              <Border ZIndex="1" Padding="5,0,5,0" Background="White" Margin="5,0,0,0">
                <TextBlock TextBlock.FontWeight="Bold" Text="{TemplateBinding Header}" />
              </Border>
              <Border Grid.RowSpan="2" Grid.ColumnSpan="2" Margin="0,10,0,0"
                  BorderBrush="{TemplateBinding Background}"
                  BorderThickness="1">
                <ContentPresenter Content="{TemplateBinding Content}" />
              </Border>
            </Grid>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>

@BinToss
Copy link

BinToss commented Jun 26, 2023

I'll edit this issue to hide the WIP portion when its main issues are resolved.

Discussion found here.

2024-03-18 - Release v1.0.0

https://github.com/BinToss/GroupBox.Avalonia/releases/v1.0.0


2023-07-11

image

Notes:

  • Now targeting Avalonia 11.0.0
  • Requires a Converter class to convert Bounds properties (Rect) to a Clip property (Geometry).
    • When the Bounds are passed as Data Bindings with the Converter to a CombinedGeometry in AXAML, the CombinedGeomtry's Geometry1 and Geometry2 properties will observe the source Bounds properties for changes and will call the converter every time the Bounds change.
  • If copying to your project, ensure you include the AXAML file in the project file e.g. <AvaloniaResource Include="GUI\Styles\*.axaml" />. Do not include AXAML files which have code-behind.
  • This style's sample are made with Avalonia.Themes.Simple. When using the Simple theme, ensure it's added and untrimmed e.g. <PackageReference Include="Avalonia.Themes.Simple" Version="11.0.0" /> and <TrimmerRootAssembly Include="Avalonia.Themes.Simple" />

Fixed:

  • Header erroneously clipping Content has been resolved via workaround. The Border of GBContent is now invisible and not clipped. The visible, clipped border is now a separate element containing an invisible rectangle to ensure it has the same size as GBContent.

Known Issues:

  • Resized Border does not render past its initial Bounds.
  • Header text seems slightly offset on the Y axis.

TODO:

  • Refactor to ThemeResource
  • Get sample image for "classic" style with Fluent theme
  • Get sample images for Simple- and Fluent- themed "modern" style.
  • Consider making WPF-/MahApps.Metro-inspired theme
GroupBoxSimpleClassic.axaml
<Styles
    xmlns="https://github.com/avaloniaui"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:CompileBindings="True"
    xmlns:conv="clr-namespace:HXE.GUI.Converters;assembly=HXE">

    <Style Selector="HeaderedContentControl">

        <SimpleTheme />

        <Style.Resources>
            <conv:RectGeometryConverter x:Key="rectConv" />
            <Thickness x:Key="BorderMargin">0,10,0,0</Thickness>
            <Thickness x:Key="BorderPadding">5,10,5,5</Thickness>

        </Style.Resources>

        <Setter
            Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <Grid Name="GBGrid">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="*" />
                        </Grid.ColumnDefinitions>

                        <TextBlock Name="GBHeaderText"
                            Margin="5,0,0,0"
                            Padding="5,0,5,0"
                            ZIndex="1"
                            TextBlock.FontWeight="Bold" Text="{TemplateBinding Header}">
                        </TextBlock>

                        <Border Name="GBBorder"
                            Grid.RowSpan="2" Grid.ColumnSpan="2"
                            Margin="{DynamicResource BorderMargin}"
                            Padding="{DynamicResource BorderPadding}"
                            BorderBrush="{TemplateBinding Foreground}"
                            BorderThickness="1">
                            <Rectangle
                                Height="{Binding #GBContent.Height}"
                                Width="{Binding #GBContent.Width}" />
                            <Border.Clip>
                                <CombinedGeometry
                                    GeometryCombineMode="Exclude"
                                    Geometry1="{Binding #GBGrid.Bounds, Mode=OneWay, Converter={StaticResource rectConv}}"
                                    Geometry2="{Binding #GBHeaderText.Bounds, Mode=OneWay, Converter={StaticResource rectConv}}" />
                            </Border.Clip>
                        </Border>

                        <Border Name="GBContent"
                            Grid.RowSpan="2" Grid.ColumnSpan="2"
                            Margin="{DynamicResource BorderMargin}"
                            Padding="{DynamicResource BorderPadding}"
                            BorderThickness="0">

                            <ContentPresenter Content="{TemplateBinding Content}" />
                        </Border>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Styles>
RectGeometryConverter.cs
using System;
using System.Globalization;
using Avalonia;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Media;

namespace HXE.GUI.Converters;

public class RectGeometryConverter : IValueConverter
{
    public static readonly RectGeometryConverter Instance = new();

    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
    {
        if (value is Rect rect)
            return new RectangleGeometry(rect);

        // converter used for the wrong type
        return new BindingNotification(new InvalidCastException(), BindingErrorType.Error);
    }

    public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
    {
        if (value is RectangleGeometry rectangle)
            return rectangle.Rect;

        // converter used for the wrong type
        return new BindingNotification(new InvalidCastException(), BindingErrorType.Error);
    }

}

2023-06-26

@derekantrican's examples are great, but the Classic style in that example will not adapt to Theme or Style changes i.e. Header background will remain solid white when the "real" background changes.
I've considered two solutions to this. One utlizes the Border's Clip property. The other utilizes the Border's OpacityMask. I'm attempting the former, but I'm struggling to make the Clip geometry recalculate when the source Bounds change. Both will likely require classes for conversions and/or observers.

Border.Clip = BoundsA - BoundsB


(Click to see images) At one point, I was excluding GBHeader.Bounds (GBHeader being the Border containing the text block in the original example) from GBContent.Bounds, but an unexpectedly large part of the latter's Border is clipped despite the resulting geometry not affecting that region according the Dev Tools.
(Click to see images) However, I now exclude GBHeaderText.Bounds from GBGrid.Bounds to avoid that particular issue.

This implementation currently has two issues.

  1. The CombinedGeometry assigned to GBContent.Clip is not recalculated when the Rect-value Bounds it was created from have changed.
  2. The CombinedGeomtry assigned to GBContent.Clip partially obscures the presented contents. I believe this could be remedied by getting the TransformBounds, but the lack of this property in my Avalonia 11.0.0-rc1.1 application's Dev Tools window indicates it no longer exists or may be inaccessible under certain conditions. The suspected issue is that two Bounds have different origin points relative to the window, so excluding one Bounds from the other will result in one being offset from its expected position in the CombinedGeometry

GroupBoxClassic.axaml

<Styles
	xmlns="https://github.com/avaloniaui"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://github.com/avaloniaui ../../../AvaloniaSchema-11.0.0.xsd"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	x:CompileBindings="True"
	xmlns:conv="clr-namespace:HXE.GUI.Converters;assembly=HXE">

	<!-- AvaloniaSchema is generated by Avant Garde -->

	<Style
		Selector="HeaderedContentControl">
		<SimpleTheme />
		<Style.Resources>
			<conv:RectGeometryConverter x:Key="rectConv" />
		</Style.Resources>
		<Setter
			Property="Template">
			<Setter.Value>
				<ControlTemplate>
					<Grid Name="GBGrid">
						<Grid.RowDefinitions>
							<RowDefinition Height="Auto" />
							<RowDefinition Height="*" />
						</Grid.RowDefinitions>
						<Grid.ColumnDefinitions>
							<ColumnDefinition Width="Auto" />
							<ColumnDefinition Width="*" />
						</Grid.ColumnDefinitions>


						<TextBlock Name="GBHeaderText"
							Margin="5,0,0,0"
							Padding="5,0,5,0"
							TextBlock.FontWeight="Bold" Text="{TemplateBinding Header}">
							<TextBlock.Bounds />
						</TextBlock>

						<Border Name="GBContent"
							Grid.RowSpan="2" Grid.ColumnSpan="2"
							Margin="0,10,0,0"
							Padding="5,10,5,5"
							BorderBrush="{TemplateBinding Foreground}"
							BorderThickness="1"
							Clip="{Binding}">
							<Border.Clip>
								<CombinedGeometry x:DataType="Rect"
									GeometryCombineMode="Exclude"
									Geometry1="{Binding ElementName=GBGrid, Path=Bounds, Mode=OneWay, Converter={StaticResource rectConv}}"
									Geometry2="{Binding ElementName=GBHeaderText, Path=Bounds, Mode=OneWay, Converter={StaticResource rectConv}}">
								</CombinedGeometry>
							</Border.Clip>
							<ContentPresenter Content="{TemplateBinding Content}" />
						</Border>
					</Grid>
				</ControlTemplate>
			</Setter.Value>
		</Setter>
	</Style>
</Styles>

RectGeometryConverter.cs

using System;
using System.Globalization;
using Avalonia;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Media;

namespace HXE.GUI.Converters;

public class RectGeometryConverter : IValueConverter
{
    public static readonly RectGeometryConverter Instance = new();

    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
    {
        if (value is Rect rect)
            return new RectangleGeometry(rect);

        // converter used for the wrong type
        return new BindingNotification(new InvalidCastException(), BindingErrorType.Error);
    }

    public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
    {
        if (value is RectangleGeometry rectangle)
            return rectangle.Rect;

        // converter used for the wrong type
        return new BindingNotification(new InvalidCastException(), BindingErrorType.Error);
    }

}

@yll690
Copy link
Contributor

yll690 commented Sep 14, 2023

@BinToss Thank you for your styles. It's very useful. And I found replacing the CombinedGeometry with a MultiBinding solves the first known issue.

<Border.Clip>
  <MultiBinding Converter="{StaticResource rectConv}">
    <Binding ElementName="GBGrid" Path="Bounds"/>
    <Binding ElementName="GBHeaderText" Path="Bounds"/>
  </MultiBinding>
</Border.Clip>
    public class RectGeometryConverter : IMultiValueConverter
    {
        public static readonly RectGeometryConverter Instance = new();

        public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
        {
            if (values.Count == 2 && values[0] is Rect borderBounds && values[1] is Rect headerBounds)
            {
                return new CombinedGeometry(GeometryCombineMode.Exclude, new RectangleGeometry(borderBounds), new RectangleGeometry(headerBounds));
            }
            return new BindingNotification(new InvalidCastException(), BindingErrorType.Error);
        }
    }

@postrondev
Copy link

i am absulute newbee.
do have a small complete example how to use it in a control dll?
thx.

@BinToss
Copy link

BinToss commented Dec 29, 2023 via email

@BinToss
Copy link

BinToss commented Mar 22, 2024

https://github.com/BinToss/GroupBox.Avalonia/releases/v1.0.0
Thanks to @yll690 for the MultiBinding advice!
@postrondev BinToss.GroupBox.Avalonia 1.0.0 has been released for Avalonia 11.0.10.
See ./GroupBox.Avalonia.Sample/ for usage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants