// --------------------------------------------------------------------------------------------------------------------
// <copyright file="StackGrid.cs" company="Catel development team">
// Copyright (c) 2008 - 2011 Catel development team. All rights reserved.
// </copyright>
// <summary>
// A grid-like control that allows a developer to specify the rows and columns, but gives the freedom
// not to define the actual grid and row numbers of the controls inside the <see cref="StackGrid" />.
// The <see cref="StackGrid" /> automatically builds up the internal grid.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;
namespace Catel.Windows.Controls
{
/// <summary>
/// A grid-like control that allows a developer to specify the rows and columns, but gives the freedom
/// not to define the actual grid and row numbers of the controls inside the <see cref="StackGrid"/>.
/// <para />
/// The <see cref="StackGrid"/> automatically builds up the internal grid.
/// </summary>
/// <example>
/// <code>
/// <StackGrid>
/// <StackGrid.RowDefinitions>
/// <RowDefinition Height="Auto" />
/// <RowDefinition Height="*" />
/// <RowDefinition Height="Auto" />
/// </StackGrid.RowDefinitions>
///
/// <StackGrid.ColumnDefinitions>
/// <ColumnDefinition Width="Auto" />
/// <ColumnDefinition Width="*" />
/// </StackGrid.ColumnDefinitions>
///
/// <!-- Name, will be set to row 0, column 1 and 2 -->
/// <Label Content="Name" />
/// <TextBox Text="{Bindng Name}" />
///
/// <!-- Empty row -->
/// <EmptyRow />
///
/// <!-- Wrappanel, will span 2 columns -->
/// <WrapPanel StackGrid.ColumnSpan="2">
/// <Button Command="{Binding OK}" />
/// </WrapPanel>
/// </StackGrid>
/// </code>
/// </example>
public class StackGrid : Panel
{
#region Fields
private readonly Grid _grid;
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="StackGrid"/> class.
/// </summary>
public StackGrid()
{
_grid = new Grid();
ColumnDefinitions = new ObservableCollection<ColumnDefinition>();
RowDefinitions = new ObservableCollection<RowDefinition>();
ColumnDefinitions.CollectionChanged += (sender, e) => OnDefinitionsCollectionChanged(e, _grid.ColumnDefinitions);
RowDefinitions.CollectionChanged += (sender, e) => OnDefinitionsCollectionChanged(e, _grid.RowDefinitions);
#if SILVERLIGHT
Loaded += (sender, e) => FinalInitialize();
#else
Initialized += (sender, e) => FinalInitialize();
#endif
}
#endregion
#region Attached Properties
/// <summary>
/// Gets the row span.
/// </summary>
/// <param name="dependencyObject">The <see cref="DependencyObject"/> to get the row span for.</param>
/// <returns>The row span value.</returns>
public static int GetRowSpan(DependencyObject dependencyObject)
{
return (int)dependencyObject.GetValue(RowSpanProperty);
}
/// <summary>
/// Sets the row span.
/// </summary>
/// <param name="dependencyObject">The <see cref="DependencyObject"/> to set the row span for.</param>
/// <param name="value">The new row span value.</param>
public static void SetRowSpan(DependencyObject dependencyObject, int value)
{
dependencyObject.SetValue(RowSpanProperty, value);
}
/// <summary>
/// RowSpanProperty
/// </summary>
public static readonly DependencyProperty RowSpanProperty =
DependencyProperty.RegisterAttached("RowSpan", typeof(int), typeof(StackGrid), new PropertyMetadata(OnRowSpanChanged));
/// <summary>
/// Called when the <see cref="RowSpanProperty"/> has changed.
/// </summary>
/// <param name="dependencyObject">The dependency object.</param>
/// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
private static void OnRowSpanChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var frameworkElement = dependencyObject as FrameworkElement;
if (frameworkElement == null)
{
return;
}
Grid.SetRowSpan(frameworkElement, (int)args.NewValue);
}
/// <summary>
/// Gets the column span.
/// </summary>
/// <param name="dependencyObject">The <see cref="DependencyObject"/> to get the column span for.</param>
/// <returns>The column span value.</returns>
public static int GetColumnSpan(DependencyObject dependencyObject)
{
return (int)dependencyObject.GetValue(ColumnSpanProperty);
}
/// <summary>
/// Sets the column span.
/// </summary>
/// <param name="dependencyObject">The <see cref="DependencyObject"/> to set the column span for.</param>
/// <param name="value">The new column span value.</param>
public static void SetColumnSpan(DependencyObject dependencyObject, int value)
{
dependencyObject.SetValue(ColumnSpanProperty, value);
}
/// <summary>
/// ColumnSpanProperty
/// </summary>
public static readonly DependencyProperty ColumnSpanProperty =
DependencyProperty.RegisterAttached("ColumnSpan", typeof(int), typeof(StackGrid), new PropertyMetadata(OnColSpanChanged));
/// <summary>
/// Called when the <see cref="ColumnSpanProperty"/> has changed.
/// </summary>
/// <param name="dependencyObject">The dependency object.</param>
/// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
private static void OnColSpanChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var frameworkElement = dependencyObject as FrameworkElement;
if (frameworkElement == null)
{
return;
}
Grid.SetColumnSpan(frameworkElement, (int)args.NewValue);
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the row definitions.
/// </summary>
/// <value>The row definitions.</value>
public ObservableCollection<RowDefinition> RowDefinitions
{
get { return (ObservableCollection<RowDefinition>)GetValue(RowDefinitionsProperty); }
set { SetValue(RowDefinitionsProperty, value); }
}
/// <summary>
/// RowDefinitions Property
/// </summary>
public static readonly DependencyProperty RowDefinitionsProperty = DependencyProperty.Register("RowDefinitions",
typeof(ObservableCollection<RowDefinition>), typeof(StackGrid), new PropertyMetadata(new ObservableCollection<RowDefinition>()));
/// <summary>
/// Gets or sets the column definitions.
/// </summary>
/// <value>The column definitions.</value>
public ObservableCollection<ColumnDefinition> ColumnDefinitions
{
get { return (ObservableCollection<ColumnDefinition>)GetValue(ColumnDefinitionsProperty); }
set { SetValue(ColumnDefinitionsProperty, value); }
}
/// <summary>
/// ColumnDefinitions property.
/// </summary>
public static readonly DependencyProperty ColumnDefinitionsProperty = DependencyProperty.Register("ColumnDefinitions",
typeof(ObservableCollection<ColumnDefinition>), typeof(StackGrid), new PropertyMetadata(new ObservableCollection<ColumnDefinition>()));
/// <summary>
/// Gets or sets a value indicating whether to show grid lines or not.
/// </summary>
/// <value><c>true</c> if grid lines should be visible; otherwise, <c>false</c>.</value>
public bool ShowGridLines
{
get { return (bool)GetValue(ShowGridLinesProperty); }
set { SetValue(ShowGridLinesProperty, value); }
}
/// <summary>
/// ShowGridLines property.
/// </summary>
public static readonly DependencyProperty ShowGridLinesProperty = DependencyProperty.Register("ShowGridLines", typeof(bool),
typeof(StackGrid), new PropertyMetadata(false, (sender, e) => ((StackGrid)sender).OnShowGridLinesChanged(e)));
/// <summary>
/// Handles the OnShowGridLinesChanged.
/// </summary>
/// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
private void OnShowGridLinesChanged(DependencyPropertyChangedEventArgs args)
{
_grid.ShowGridLines = (bool)args.NewValue;
}
#endregion
#region Method
/// <summary>
/// Raises the DefinitionsCollectionChanged event.
/// </summary>
/// <typeparam name="TCollection">The type of the collection.</typeparam>
/// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
/// <param name="collection">The collection.</param>
private static void OnDefinitionsCollectionChanged<TCollection>(NotifyCollectionChangedEventArgs e, TCollection collection)
where TCollection : IList
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (var item in e.NewItems)
{
collection.Add(item);
}
}
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (var item in e.NewItems)
{
collection.Remove(item);
}
}
if (e.Action == NotifyCollectionChangedAction.Replace)
{
foreach (var item in e.NewItems)
{
collection[e.NewStartingIndex] = item;
}
}
}
/// <summary>
/// Final initialize so the <see cref="StackGrid"/> is actually created.
/// </summary>
private void FinalInitialize()
{
MoveChildrenToGridChildren();
SetColumnsAndRows();
}
/// <summary>
/// Sets the columns and rows.
/// </summary>
private void SetColumnsAndRows()
{
var columnCount = ColumnDefinitions.Count;
var currentColumn = 0;
var currentRow = 0;
foreach (FrameworkElement child in _grid.Children)
{
var columnSpan = Grid.GetColumnSpan(child);
if (child is EmptyRow)
{
// If not yet reached the end of columns, force a new increment anyway
if (currentColumn != 0 && currentColumn <= columnCount)
{
currentRow++;
}
// The current column for an empty row is alway zero
currentColumn = 0;
Grid.SetColumn(child, currentColumn);
Grid.SetColumnSpan(child, columnCount);
Grid.SetRow(child, currentRow);
currentRow++;
continue;
}
Grid.SetColumn(child, currentColumn);
Grid.SetRow(child, currentRow);
if (currentColumn + columnSpan >= columnCount)
{
currentColumn = 0;
currentRow++;
}
else
{
// Increment the current column by the column span
currentColumn = currentColumn + columnSpan;
}
}
}
/// <summary>
/// Moves the children to grid children.
/// </summary>
private void MoveChildrenToGridChildren()
{
// Use internal collection, for the Children collection will be modified
var internalCollection = new FrameworkElement[Children.Count];
Children.CopyTo(internalCollection, 0);
foreach (var child in internalCollection)
{
Children.Remove(child);
_grid.Children.Add(child);
}
// Add the grid, as only child of the stackpanel
Children.Add(_grid);
}
/// <summary>
/// When overridden in a derived class, positions child elements and determines a size for a <see cref="T:System.Windows.FrameworkElement"/> derived class.
/// </summary>
/// <param name="finalSize">The final area within the parent that this element should use to arrange itself and its children.</param>
/// <returns>The actual size used.</returns>
protected override Size ArrangeOverride(Size finalSize)
{
foreach (FrameworkElement child in Children)
{
child.Arrange(new Rect(0, 0, child.DesiredSize.Width, child.DesiredSize.Height));
}
return finalSize;
}
/// <summary>
/// When overridden in a derived class, measures the size in layout required for child elements and determines a size for the <see cref="T:System.Windows.FrameworkElement"/>-derived class.
/// </summary>
/// <param name="availableSize">The available size that this element can give to child elements. Infinity can be specified as a value to indicate that the element will size to whatever content is available.</param>
/// <returns>
/// The size that this element determines it needs during layout, based on its calculations of child element sizes.
/// </returns>
protected override Size MeasureOverride(Size availableSize)
{
if (availableSize.Width != Double.PositiveInfinity)
{
_grid.Width = availableSize.Width;
}
if (availableSize.Height != Double.PositiveInfinity)
{
_grid.Height = availableSize.Height;
}
var resultSize = new Size(0, 0);
_grid.Measure(availableSize);
resultSize.Width = Math.Max(resultSize.Width, _grid.DesiredSize.Width);
resultSize.Height = Math.Max(resultSize.Height, _grid.DesiredSize.Height);
resultSize.Width = double.IsPositiveInfinity(availableSize.Width) ? resultSize.Width : availableSize.Width;
resultSize.Height = double.IsPositiveInfinity(availableSize.Height) ? resultSize.Height : availableSize.Height;
return resultSize;
}
#endregion
}
}