how to style Grid ColumnDefinitions in WPF
Asked Answered
V

6

20

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?.....

Vacuity answered 3/3, 2011 at 1:30 Comment(0)
A
27

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);
        }
    }
}
Anaxagoras answered 28/6, 2012 at 13:4 Comment(1)
Try combining Rachel's solution with this one: Use Attached Property in Style. This results in something like this <Style TargetType="{x:Type Grid}" x:Key="MyGridStyle"> <Style.Setters> <Setter Property="helper:GridHelpers.ColumnCount" Value="2"></Setter> </Style.Setters> </Style> and you have what you're asking forAraucaria
T
17

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>
Ti answered 12/2, 2015 at 9:44 Comment(0)
D
7

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> {}
Dorkus answered 28/6, 2012 at 12:59 Comment(4)
This will kill performance since it won't execute until the initial layout pass is complete.Fang
Can you expand on that ? I've done some tracing and OnColumnDefinitionChanged is executed before any Measure/Arrange/LayoutUpdated methods/events of Grid element. It also does not affect number of times they are executed.Dorkus
I get the error message: 'value' already belongs to another 'ColumnDefinitionCollection'. So I probably need to create a copy of the ColumnDefinition.Sukkoth
Using: grid.ColumnDefinitions.Add(new ColumnDefinition { Width = cd.Width }); within the foreach will solve it.Sukkoth
L
1

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.

Lalita answered 3/3, 2011 at 4:34 Comment(0)
P
1

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>
Penang answered 3/7, 2016 at 17:56 Comment(0)
P
0

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, } );
        }
    }
}
Pigskin answered 14/3, 2024 at 12:52 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.