AutoGrid – Part 1

Whilst developing Year View in Silverlight 4, I had a scenario where I wanted to use a Grid control, but needed to create the columns and rows dynamically rather than statically defined in XAML.  The reason I need them to be dynamic is that although my initial view was a Gregorian year planner that requires 12 rows for the months, and 37 columns for the day headings, I wanted to cater for alternate views as and when I have time to implement them…and these alternatives are unlikely to have the same layout requirements.

Secondly, I dislike the idea of having to enter 12 row definitions and 37 column definitions direct in the XAML…it’s just ‘unpleasant’.

The UniformGrid seemed like a possible solution, but was not sufficiently flexible for my scenario; even though I initially want my rows and columns to be uniform, I want to easily override this and set explicit dimensions and minimums, etc. for reasons that will become clear in Part 2.

And thus, the AutoGrid was born.  Here’s a simple example of its use:

XAML Example Use
<ItemsControl   DataContext="{Binding YearPlanner.View.CellsView}"                                
                ItemsSource="{Binding DayHeadings}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <planet:AutoGrid Rows="1" Columns="{Binding NumberOfDayHeadings}" />                        
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>
C# AutoGrid Code - Part 1
// Copyright 2010 (c) Planet Software Pty Ltd. This work is
// licenced under the Creative Commons Attribution 2.5 Australia License. To view
// a copy of this licence, visit http://creativecommons.org/licenses/by/2.5/au/

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using Planet.Windows.Controls.Helpers;

namespace Planet.Windows.Controls
{
    // This control allows us to define the number of rows and columns on the grid
    // rather than having to manually enter monthRow/col definitions. This is
    // clearly useful inside a control template definition where we need to
    // dynamically vary the number of rows/quantities. Further, we can define the
    // widths/heights, so we can get the benefit of a uniform size in either/both
    // directions.    
    public class AutoGrid : Grid
    {
        private int _cellCount;// = 0 - Keep count to avoid unneeded layout changes

        #region public int Rows

        private static readonly DependencyProperty<AutoGrid, int> RowsProperty =
            DependencyProperty<AutoGrid, int>.Register(o => o.Rows, 0);

        public int Rows
        {
            get { return RowsProperty.GetValue(this); }
            set
            {
                if (value < 0) { value = 0; }
                RowsProperty.SetValue(this, value);
                Refresh();
            }
        }
        #endregion

        #region public int Columns

        private static readonly DependencyProperty<AutoGrid, int> ColumnsProperty =
            DependencyProperty<AutoGrid, int>.Register(o => o.Columns, 0);

        public int Columns
        {
            get { return ColumnsProperty.GetValue(this); }
            set
            {
                if (value < 0) { value = 0; }
                ColumnsProperty.SetValue(this, value);
                Refresh();                
            }
        }
       
        #endregion

        #region public GridLength RowHeight

        private static readonly DependencyProperty<AutoGrid, GridLength>
            RowHeightProperty =
                DependencyProperty<AutoGrid, GridLength>.Register(o => o.RowHeight,
                    new GridLength(1, GridUnitType.Star));

        public GridLength RowHeight
        {
            get { return RowHeightProperty.GetValue(this); }
            set { RowHeightProperty.SetValue(this, value); }
        }

        #endregion

        #region public GridLength ColumnWidth

        private static readonly DependencyProperty<AutoGrid, GridLength>
            ColumnWidthProperty =
                DependencyProperty<AutoGrid, GridLength>.Register(o => o.ColumnWidth,
                    new GridLength(1, GridUnitType.Star));

        public GridLength ColumnWidth
        {
            get { return ColumnWidthProperty.GetValue(this); }
            set { ColumnWidthProperty.SetValue(this, value); }
        }
       
        #endregion

        #region public double MinRowHeight

        private static readonly DependencyProperty<AutoGrid, double>
            MinRowHeightProperty =
                DependencyProperty<AutoGrid, double>.Register(o => o.MinRowHeight,
                    0.0);

        public double MinRowHeight
        {
            get { return MinRowHeightProperty.GetValue(this); }
            set
            {
                if (value < 0) { value = 0; }
                MinRowHeightProperty.SetValue(this, value);
                Refresh();
            }
        }          

        #endregion

        #region public double MinColumnWidth

        private static readonly DependencyProperty<AutoGrid, double>
            MinColumnWidthProperty =
                DependencyProperty<AutoGrid, double>.Register(o => o.MinColumnWidth,
                    0.0);

        public double MinColumnWidth
        {
            get { return MinColumnWidthProperty.GetValue(this); }
            set
            {
                if (value < 0) { value = 0; }
                MinColumnWidthProperty.SetValue(this, value);
                Refresh();
            }
        }        

        #endregion

        #region Context Changing Hack
        private static readonly DependencyProperty MyDataContextProperty =
            DependencyProperty.Register("MyDataContext",
                                    typeof(Object),
                                    typeof(AutoGrid),
                                    new PropertyMetadata(DataContextChanged));

        private static void DataContextChanged(object sender,
            DependencyPropertyChangedEventArgs e)
        {
            AutoGrid grid = (AutoGrid)sender;
            INotifyPropertyChanged data;

            data = e.OldValue as INotifyPropertyChanged;
            if (data != null) { data.PropertyChanged -= grid.DataContextChanged; }

            data = e.NewValue as INotifyPropertyChanged;
            if (data != null) { data.PropertyChanged += grid.DataContextChanged; }
        }

        private void DataContextChanged(object sender, PropertyChangedEventArgs e)
        {
            Refresh();
        }
        #endregion

        public AutoGrid()
            : base()
        {
            this.LayoutUpdated += new EventHandler(AutoGrid_LayoutUpdated);
            // Allow us to track the context changing
            SetBinding(MyDataContextProperty, new Binding());            
        }
        
        public void Refresh()
        {
            _cellCount = 0;
            InvalidateArrange();
        }

        private void AutoGrid_LayoutUpdated(object sender, EventArgs e)
        {
            int childCount = this.Children.Count;
            
            if (_cellCount != childCount)           
            {                
                _cellCount = childCount;

                this.ColumnDefinitions.Clear();
                this.RowDefinitions.Clear();
                
                for (int column = 0; column < Columns; ++column)
                {
                    ColumnDefinition cd = new ColumnDefinition();
                    cd.Width = ColumnWidth;
                    cd.MinWidth = MinColumnWidth;                   
                    this.ColumnDefinitions.Add(cd);
                }
                
                for (int row = 0; row < Rows; ++row)
                {
                    RowDefinition rd = new RowDefinition();
                    rd.Height = RowHeight;
                    rd.MinHeight = MinRowHeight;                  
                    this.RowDefinitions.Add(rd);
                }

                int columnPosition = 0;
                int rowPosition = -1;

                for (int i = 0; i < childCount; ++i)
                {
                    FrameworkElement item = this.Children[i] as FrameworkElement;

                    if (item != null)
                    {                        
                        columnPosition = i % Columns;
                        if (columnPosition == 0) { ++rowPosition; }

                        Grid.SetColumn(item, columnPosition);
                        Grid.SetRow(item, rowPosition);                                              
                    }
                }               
            }
        }       
    }
}

Note: The comment about Context Changing Hack - have a google and you will find a number of articles that talk about the lack of a DataContextChanged event in Silverlight.

The code is simple…we create the required number of columns and rows setting the default dimensions, and then loop through the child items adding one to each row/column until we’ve run out of items.

(You may want to extend this code depending on your view of boundary checking the loop and row/column assignment).

And that’s it.

Part 2 extends this class further as I want to make a distinction between ‘layout’ items and ‘other’ items that a grid may contain rather than assuming that we have 1 child item per cell.

Print | posted on Friday, 30 April 2010 9:30 AM

Feedback

# re: AutoGrid – Part 1

left by Matt at 16/10/2013 11:03 AM Gravatar
Awesomeness friend :) Works auto-magically with this too for simple forms...

<controls1:AutoArrangeGrid behaviours:GridBehaviour.Rows="auto#2" behaviours:GridBehaviour.Columns="auto;*">
<TextBlock Text="0,0"></TextBlock>
<TextBox Text="1,0"></TextBox>
<TextBlock Text="0,1"></TextBlock>
<TextBox Text="1,1"></TextBox>
...
</controls1:AutoArrangeGrid>

public class AutoArrangeGrid : Grid
{
private int _currentColumn = 0;
private int _currentRow = 0;

protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
{
base.OnVisualChildrenChanged(visualAdded, visualRemoved);

visualAdded.SetCurrentValue(Grid.RowProperty, _currentRow);
visualAdded.SetCurrentValue(Grid.ColumnProperty, _currentColumn);

_currentColumn++;
if (_currentColumn == ColumnDefinitions.Count)
{
_currentColumn = 0;
_currentRow++;
}
}
}
Title  
Name
Email (never displayed)
Url
Comments   
Please add 4 and 8 and type the answer here: