GaryX
GaryX

Reputation: 737

how to style Grid ColumnDefinitions in WPF

I want to know how can I style a Grid so that I don't need to specify the

<Grid.ColumnDefinitions>
   <ColumnDefinition Width="auto" SharedSizeGroup="SG1"/>
   <ColumnDefinition Width="auto" SharedSizeGroup="SG2"/>
</Grid.ColumnDefinitions>

every time?

Thank you very much!

ps: I did try to search on Google first. But I couldn't find any answer. Anyone who find the answer from google could you please tell me what keyword do you use to search? Sometimes I find it hard to determine what keyword use to search.

ps2: I am too lazy, every I just open chrome and type something and search. If nothing found, I conclude nothing found and come here. Is there anyone will search on Google, then find nothing, then open bing.com and search? And find nothing and go to yahoo and search and search?.....

Upvotes: 20

Views: 26608

Answers (6)

Sir Rufo
Sir Rufo

Reputation: 19096

I wrote a GridHelper that simplifies the Row and Column definitions and supports styling.

The given example would now look like this

<Grid local:GridHelper.ColumnDefinitions="auto:SG1,auto:SG2">
  ...
</Grid>

or as a style

<Style TargetType="Grid" x:Key="MyGridStyle">
  <Setter Property="local:GridHelper.ColumnDefinitions" Value="auto:SG1,auto:SG2" />
</Style/>

<Grid Style="{StaticResource MyGridStyle}">
  ...
</Grid>

The definition is comma-separated and supports any value you can use for the Width/Height property of ColumnDefinition/RowDefinition as the conversion is done by the built-in GridLengthConverter.

In addition to that you can set the SharedSizeGroup too for each Column/Row separated by a colon.

public static class GridHelper
{
    private static readonly GridLengthConverter _converter = new();

    private static IEnumerable<(string Size, string? SharedSizeGroup)> ParseDefinitions( string definitions )
    {
        string[] parts = definitions.Split( ",", StringSplitOptions.TrimEntries ) ?? Array.Empty<string>();
        return parts
            .Select( p => p.Split( ":", StringSplitOptions.TrimEntries ) )
            .Select( p => (p[0], p.Length > 1 ? p[1] : null) );
    }

    public static string? GetRowDefinitions( Grid obj )
    {
        return (string?)obj.GetValue( RowDefinitionsProperty );
    }

    public static void SetRowDefinitions( Grid obj, string? value )
    {
        obj.SetValue( RowDefinitionsProperty, value );
    }

    // Using a DependencyProperty as the backing store for RowDefinitions.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty RowDefinitionsProperty =
        DependencyProperty.RegisterAttached( "RowDefinitions", typeof( string ), typeof( GridHelper ), new PropertyMetadata( RowDefinitionsChanged ) );

    private static void RowDefinitionsChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
        var grid = (Grid)d;
        var newDefinitions = e.NewValue as string;

        grid.RowDefinitions.Clear();

        if ( newDefinitions is null ) return;

        foreach ( var rowDefinition in ParseDefinitions( newDefinitions ) )
        {
            var height = _converter.ConvertFromInvariantString( rowDefinition.Size ) as GridLength? ?? GridLength.Auto;
            grid.RowDefinitions.Add( new RowDefinition { Height = height, SharedSizeGroup = rowDefinition.SharedSizeGroup, } );
        }
    }

    public static string? GetColumDefinitions( Grid obj )
    {
        return (string?)obj.GetValue( ColumDefinitionsProperty );
    }

    public static void SetColumDefinitions( Grid obj, string? value )
    {
        obj.SetValue( ColumDefinitionsProperty, value );
    }

    // Using a DependencyProperty as the backing store for ColumDefinitions.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ColumDefinitionsProperty =
        DependencyProperty.RegisterAttached( "ColumDefinitions", typeof( string ), typeof( GridHelper ), new PropertyMetadata( ColumnDefinitionsChanged ) );

    private static void ColumnDefinitionsChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
        var grid = (Grid)d;
        var newDefinitions = e.NewValue as string;

        grid.ColumnDefinitions.Clear();

        if ( newDefinitions is null ) return;

        foreach ( var columnDefinition in ParseDefinitions( newDefinitions ) )
        {
            var width = _converter.ConvertFromInvariantString( columnDefinition.Size ) as GridLength? ?? GridLength.Auto;
            grid.ColumnDefinitions.Add( new ColumnDefinition { Width = width, SharedSizeGroup = columnDefinition.SharedSizeGroup, } );
        }
    }
}

Upvotes: 0

Egenskaper
Egenskaper

Reputation: 334

This is a solution which doesn't require any helper class.

It's possible to set ColumnDefinitions by using an ItemsControl with a Grid as its ItemsPanelTemplate. This is shown in the example below.

<ItemsControl>
    <ItemsControl.Resources>
        <Style TargetType="ItemsControl">
            <Setter Property="ItemsPanel">
                <Setter.Value>
                    <ItemsPanelTemplate>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition />
                                <ColumnDefinition Width="40" />
                            </Grid.ColumnDefinitions>
                        </Grid>
                    </ItemsPanelTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ItemsControl.Resources>
    <TextBox Text="First column" />
    <TextBox Text="second column" Grid.Column="1" />
</ItemsControl>

Upvotes: 17

Johan Larsson
Johan Larsson

Reputation: 17580

Here is a way:

1) Create a collection with an attached property like this:

public class ColumnDefinitions : Collection<ColumnDefinition>
{
    public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached(
        "Source",
        typeof(ColumnDefinitions),
        typeof(ColumnDefinitions),
        new PropertyMetadata(
            default(ColumnDefinitions), 
            OnColumnDefinitionsChanged));

    public static void SetSource(Grid element, ColumnDefinitions value)
    {
        element.SetValue(SourceProperty, value);
    }

    [AttachedPropertyBrowsableForChildren(IncludeDescendants = false)]
    [AttachedPropertyBrowsableForType(typeof(Grid))]
    public static ColumnDefinitions GetSource(Grid element)
    {
        return (ColumnDefinitions)element.GetValue(SourceProperty);
    }

    private static void OnColumnDefinitionsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var grid = (Grid)d;
        grid.ColumnDefinitions.Clear();
        var columnDefinitions = (ColumnDefinitions)e.NewValue;
        if (columnDefinitions == null)
        {
            return;
        }

        foreach (var columnDefinition in columnDefinitions)
        {
            grid.ColumnDefinitions.Add(columnDefinition);
        }
    }
}

2) Then you can use it as a resource and in a style for grid like this:

Note that x:Shared="False" must be used. If not the same definition will be added to many grids causing WPF to throw.

<UserControl.Resources>
    <demo:ColumnDefinitions x:Key="SomeColumnDefinitions" x:Shared="False">
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="*" />
    </demo:ColumnDefinitions>

    <Style x:Key="SomeGridStyle" TargetType="{x:Type Grid}">
        <Setter Property="demo:ColumnDefinitions.Source" Value="{StaticResource SomeColumnDefinitions}"></Setter>
    </Style>
</UserControl.Resources>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition  Height="5"/>
        <RowDefinition />
    </Grid.RowDefinitions>
    <Grid Style="{StaticResource SomeGridStyle}">
        <Rectangle Grid.Row="0"
                   Grid.Column="0"
                   Width="120"
                   Fill="Blue" />
        <Rectangle Grid.Row="0"
                   Grid.Column="1"
                   Fill="Yellow" />
    </Grid>

    <Grid Grid.Row="2" Style="{StaticResource SomeGridStyle}">
        <Rectangle Grid.Row="0"
                   Grid.Column="0"
                   Width="120"
                   Fill="Blue" />
        <Rectangle Grid.Row="0"
                   Grid.Column="1"
                   Fill="Yellow" />
    </Grid>
</Grid>

Upvotes: 1

Rachel
Rachel

Reputation: 132548

It was always a pet peeve of mine to have to write out the RowDefinitions and ColumnDefinitions, so one day I got tired of it and wrote some attached properties that can be used for this kind of thing.

Now instead of writing my Grid definition like this:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
</Grid>

I can use

<Grid local:GridHelpers.RowCount="6"
      local:GridHelpers.StarRows="5"
      local:GridHelpers.ColumnCount="4"
      local:GridHelpers.StarColumns="1,3">

</Grid>

It only allows for Auto and * sizes, but most of the time that's all I'm using.

It also supports bindings for dynamically sized Grids

<Grid local:GridHelpers.RowCount="{Binding RowCount}"
      local:GridHelpers.ColumnCount="{Binding ColumnCount}" />

Here's a copy of the code in case that site ever goes down :

public class GridHelpers
{
    #region RowCount Property

    /// <summary>
    /// Adds the specified number of Rows to RowDefinitions. 
    /// Default Height is Auto
    /// </summary>
    public static readonly DependencyProperty RowCountProperty =
        DependencyProperty.RegisterAttached(
            "RowCount", typeof(int), typeof(GridHelpers),
            new PropertyMetadata(-1, RowCountChanged));

    // Get
    public static int GetRowCount(DependencyObject obj)
    {
        return (int)obj.GetValue(RowCountProperty);
    }

    // Set
    public static void SetRowCount(DependencyObject obj, int value)
    {
        obj.SetValue(RowCountProperty, value);
    }

    // Change Event - Adds the Rows
    public static void RowCountChanged(
        DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        if (!(obj is Grid) || (int)e.NewValue < 0)
            return;

        Grid grid = (Grid)obj;
        grid.RowDefinitions.Clear();

        for (int i = 0; i < (int)e.NewValue; i++)
            grid.RowDefinitions.Add(
                new RowDefinition() { Height = GridLength.Auto });

        SetStarRows(grid);
    }

    #endregion

    #region ColumnCount Property

    /// <summary>
    /// Adds the specified number of Columns to ColumnDefinitions. 
    /// Default Width is Auto
    /// </summary>
    public static readonly DependencyProperty ColumnCountProperty =
        DependencyProperty.RegisterAttached(
            "ColumnCount", typeof(int), typeof(GridHelpers),
            new PropertyMetadata(-1, ColumnCountChanged));

    // Get
    public static int GetColumnCount(DependencyObject obj)
    {
        return (int)obj.GetValue(ColumnCountProperty);
    }

    // Set
    public static void SetColumnCount(DependencyObject obj, int value)
    {
        obj.SetValue(ColumnCountProperty, value);
    }

    // Change Event - Add the Columns
    public static void ColumnCountChanged(
        DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        if (!(obj is Grid) || (int)e.NewValue < 0)
            return;

        Grid grid = (Grid)obj;
        grid.ColumnDefinitions.Clear();

        for (int i = 0; i < (int)e.NewValue; i++)
            grid.ColumnDefinitions.Add(
                new ColumnDefinition() { Width = GridLength.Auto });

        SetStarColumns(grid);
    }

    #endregion

    #region StarRows Property

    /// <summary>
    /// Makes the specified Row's Height equal to Star. 
    /// Can set on multiple Rows
    /// </summary>
    public static readonly DependencyProperty StarRowsProperty =
        DependencyProperty.RegisterAttached(
            "StarRows", typeof(string), typeof(GridHelpers),
            new PropertyMetadata(string.Empty, StarRowsChanged));

    // Get
    public static string GetStarRows(DependencyObject obj)
    {
        return (string)obj.GetValue(StarRowsProperty);
    }

    // Set
    public static void SetStarRows(DependencyObject obj, string value)
    {
        obj.SetValue(StarRowsProperty, value);
    }

    // Change Event - Makes specified Row's Height equal to Star
    public static void StarRowsChanged(
        DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        if (!(obj is Grid) || string.IsNullOrEmpty(e.NewValue.ToString()))
            return;

        SetStarRows((Grid)obj);
    }

    #endregion

    #region StarColumns Property

    /// <summary>
    /// Makes the specified Column's Width equal to Star. 
    /// Can set on multiple Columns
    /// </summary>
    public static readonly DependencyProperty StarColumnsProperty =
        DependencyProperty.RegisterAttached(
            "StarColumns", typeof(string), typeof(GridHelpers),
            new PropertyMetadata(string.Empty, StarColumnsChanged));

    // Get
    public static string GetStarColumns(DependencyObject obj)
    {
        return (string)obj.GetValue(StarColumnsProperty);
    }

    // Set
    public static void SetStarColumns(DependencyObject obj, string value)
    {
        obj.SetValue(StarColumnsProperty, value);
    }

    // Change Event - Makes specified Column's Width equal to Star
    public static void StarColumnsChanged(
        DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        if (!(obj is Grid) || string.IsNullOrEmpty(e.NewValue.ToString()))
            return;

        SetStarColumns((Grid)obj);
    }

    #endregion

    private static void SetStarColumns(Grid grid)
    {
        string[] starColumns = 
            GetStarColumns(grid).Split(',');

        for (int i = 0; i < grid.ColumnDefinitions.Count; i++)
        {
            if (starColumns.Contains(i.ToString()))
                grid.ColumnDefinitions[i].Width = 
                    new GridLength(1, GridUnitType.Star);
        }
    }

    private static void SetStarRows(Grid grid)
    {
        string[] starRows = 
            GetStarRows(grid).Split(',');

        for (int i = 0; i < grid.RowDefinitions.Count; i++)
        {
            if (starRows.Contains(i.ToString()))
                grid.RowDefinitions[i].Height = 
                    new GridLength(1, GridUnitType.Star);
        }
    }
}

Upvotes: 28

Marcin Wisnicki
Marcin Wisnicki

Reputation: 4701

Create attached dependency property with change callback to synchronize collection elements:

<Grid>
  <Grid.Style>
    <Style TargetType="Grid">
      <Setter Property="my:GridUtils.ColumnDefinitions">
        <Setter.Value>
          <my:ColumnDefinitionCollection>
            <ColumnDefinition Width="1*" />
            <ColumnDefinition Width="1*" />
          </my:ColumnDefinitionCollection>
        </Setter.Value>
      </Setter>
    </Style>
  </Grid.Style>

  <Button Content="Button" />
  <Button Content="Button" Grid.Column="1" />
</Grid>

Implementation (RowDefinition support omitted as it's basically identical):

public class GridUtils
{
    public static readonly DependencyProperty ColumnDefinitionsProperty =
        DependencyProperty.RegisterAttached("ColumnDefinitions", typeof (ColumnDefinitionCollection),
                                            typeof (GridUtils),
                                            new PropertyMetadata(default(ColumnDefinitionCollection),
                                                                    OnColumnDefinitionsChanged));

    private static void OnColumnDefinitionsChanged(DependencyObject d, DependencyPropertyChangedEventArgs ev)
    {
        var grid = (Grid) d;
        var oldValue = (ColumnDefinitionCollection) ev.OldValue;
        var newValue = (ColumnDefinitionCollection) ev.NewValue;
        grid.ColumnDefinitions.Clear();
        if (newValue != null)
            foreach (var cd in newValue)
                grid.ColumnDefinitions.Add(cd);
    }

    public static void SetColumnDefinitions(Grid element, ColumnDefinitionCollection value)
    {
        element.SetValue(ColumnDefinitionsProperty, value);
    }

    public static ColumnDefinitionCollection GetColumnDefinitions(Grid element)
    {
        return (ColumnDefinitionCollection) element.GetValue(ColumnDefinitionsProperty);
    }
}

public class ColumnDefinitionCollection : List<ColumnDefinition> {}

Upvotes: 8

keyle
keyle

Reputation: 2837

I believe it's not possible because you can't set a style that affects all ColumnDefinition(s).

Grid does not support ControlTemplate, so you can't do it with composition.

The only hack I can think of would be to create a user control with those 2 columns and extend the grid. But that's nasty.

Upvotes: 1

Related Questions