Here is another tip to make you XAML code less cluttered. One area that you may find it hard to write and read and maintain is Grid layout control. Defining RowDefinitions and ColumnDefinitions waste too much space and adds too unnecessary lines of code. Hence it become too complicated to handle it.
The solution I am sugessting here is to use some attached properties to define RowDefinitions and ColumnDefinitions in a smarter way.
You can fetch the code for attached properties here: https://gist.github.com/2234500
This code adds these attached properties to Grid
- Grid.Rows
- Grid.Columns
and adds these attached properties to UIElemnet
- Cell
Grid.Rows and Grid.Columns are same thing but one is for specifying rows and one is for columns. These two properties are simply text value which you can set it according to the following Rules:
- Separate items with semi colon(;) , Example "auto;auto;*"
- Use auto for adding an Auto sized row or column , Example "auto;auto"
- Specify * for adding an star sized row or column, Example "1*,2*, *"
- Specify only a number for adding a fixed size row or column, Example "200,200,100"
- Append # followed by a number to add multiple rows and columns of same sizing. "1*#4,auto"
- The number of starts in an item will multiplies star factor. Example "**,***,*" instead of "2*,3*,*"
XAML Sample:
<Grid s:Grid.Rows="auto;1*;auto;auto" s:Grid.Columns="*#5;auto"> ... </Grid>
Adds 4 rows to grid which are all Auto sized except the second row which occupies all remained space. Also adds 5 star columns followed by an Auto sized column.
Implementaion
As I said this is simply 3 attached properties which allows you to specify the rows and columns using a textual representation. Here is the function which converts the textual value into a GridLength value:
public static GridLength ParseGridLength(string text) { text = text.Trim(); if (text.ToLower() == "auto") return GridLength.Auto; if (text.Contains("*")) { var startCount = text.ToCharArray().Count(c => c == '*'); var pureNumber = text.Replace("*", ""); var ratio = string.IsNullOrWhiteSpace(pureNumber) ? 1 : double.Parse(pureNumber); return new GridLength(startCount * ratio, GridUnitType.Star); } var pixelsCount = double.Parse(text); return new GridLength(pixelsCount, GridUnitType.Pixel); }
And here is the function which makes # operator working:
public static IEnumerable<GridLength> Parse(string text) { if (text.Contains("#")) { var parts = text.Split(new[] { '#' }, StringSplitOptions.RemoveEmptyEntries); var count = int.Parse(parts[1].Trim()); return Enumerable.Repeat(ParseGridLength(parts[0]), count); } else { return new[] { ParseGridLength(text) }; } }
And finally the attached property change callback:
var grid = d as System.Windows.Controls.Grid; var oldValue = e.OldValue as string; var newValue = e.NewValue as string; if (oldValue == null || newValue == null) return; if (oldValue != newValue) { grid.ColumnDefinitions.Clear(); newValue .Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) .SelectMany(Parse) .Select(l => new ColumnDefinition { Width = l }) .ToList().ForEach(grid.ColumnDefinitions.Add); }
The above code is for Columns attached property but this is the same for Rows attached property as well.
The whole source code for these attached properties:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Sam | |
{ | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Windows; | |
using System.Windows.Controls; | |
using System.Windows.Media; | |
public static class Grid | |
{ | |
#region helper methods | |
public static IEnumerable<GridLength> Parse(string text) | |
{ | |
if (text.Contains("#")) | |
{ | |
var parts = text.Split(new[] { '#' }, StringSplitOptions.RemoveEmptyEntries); | |
var count = int.Parse(parts[1].Trim()); | |
return Enumerable.Repeat(ParseGridLength(parts[0]), count); | |
} | |
else | |
{ | |
return new[] { ParseGridLength(text) }; | |
} | |
} | |
public static GridLength ParseGridLength(string text) | |
{ | |
text = text.Trim(); | |
if (text.ToLower() == "auto") | |
return GridLength.Auto; | |
if (text.Contains("*")) | |
{ | |
var startCount = text.ToCharArray().Count(c => c == '*'); | |
var pureNumber = text.Replace("*", ""); | |
var ratio = string.IsNullOrWhiteSpace(pureNumber) ? 1 : double.Parse(pureNumber); | |
return new GridLength(startCount * ratio, GridUnitType.Star); | |
} | |
var pixelsCount = double.Parse(text); | |
return new GridLength(pixelsCount, GridUnitType.Pixel); | |
} | |
#endregion | |
#region GridColumnsLayout | |
public static string GetColumns(DependencyObject obj) | |
{ | |
return (string)obj.GetValue(ColumnsProperty); | |
} | |
public static void SetColumns(DependencyObject obj, string value) | |
{ | |
obj.SetValue(ColumnsProperty, value); | |
} | |
// Using a DependencyProperty as the backing store for Columns. This enables animation, styling, binding, etc... | |
public static readonly DependencyProperty ColumnsProperty = | |
DependencyProperty.RegisterAttached("Columns", typeof(string), typeof(System.Windows.Controls.Grid), | |
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure, Columns_PropertyChangedCallback)); | |
public static void Columns_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) | |
{ | |
var grid = d as System.Windows.Controls.Grid; | |
var oldValue = e.OldValue as string; | |
var newValue = e.NewValue as string; | |
if (oldValue == null || newValue == null) | |
return; | |
if (oldValue != newValue) | |
{ | |
grid.ColumnDefinitions.Clear(); | |
newValue | |
.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) | |
.SelectMany(Parse) | |
.Select(l => new ColumnDefinition { Width = l }) | |
.ToList().ForEach(grid.ColumnDefinitions.Add); | |
} | |
} | |
#endregion | |
#region Rows | |
public static string GetRows(DependencyObject obj) | |
{ | |
return (string)obj.GetValue(RowsProperty); | |
} | |
public static void SetRows(DependencyObject obj, string value) | |
{ | |
obj.SetValue(RowsProperty, value); | |
} | |
// Using a DependencyProperty as the backing store for Rows. This enables animation, styling, binding, etc... | |
public static readonly DependencyProperty RowsProperty = | |
DependencyProperty.RegisterAttached("Rows", typeof(string), typeof(System.Windows.Controls.Grid), | |
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure, Rows_PropertyChangedCallback)); | |
public static void Rows_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) | |
{ | |
var grid = d as System.Windows.Controls.Grid; | |
var oldValue = e.OldValue as string; | |
var newValue = e.NewValue as string; | |
if (oldValue == null || newValue == null) | |
return; | |
if (oldValue != newValue) | |
{ | |
grid.ColumnDefinitions.Clear(); | |
newValue | |
.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) | |
.SelectMany(Parse) | |
.Select(l => new RowDefinition { Height = l }) | |
.ToList().ForEach(grid.RowDefinitions.Add); | |
} | |
} | |
#endregion | |
#region Cell | |
public static string GetCell(DependencyObject obj) | |
{ | |
return (string)obj.GetValue(CellProperty); | |
} | |
public static void SetCell(DependencyObject obj, string value) | |
{ | |
obj.SetValue(CellProperty, value); | |
} | |
// Using a DependencyProperty as the backing store for Cell. This enables animation, styling, binding, etc... | |
public static readonly DependencyProperty CellProperty = | |
DependencyProperty.RegisterAttached("Cell", typeof(string), typeof(UIElement), | |
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure, Cell_PropertyChangedCallback)); | |
public static void Cell_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) | |
{ | |
var element = d as UIElement; | |
var oldValue = e.OldValue as string; | |
var newValue = e.NewValue as string; | |
if (oldValue == null || newValue == null) | |
return; | |
if (oldValue != newValue) | |
{ | |
var rowAndColumn = newValue.Split(new char[] { ' ', ';' }); | |
var row = int.Parse(rowAndColumn[0]); | |
var column = int.Parse(rowAndColumn[1]); | |
System.Windows.Controls.Grid.SetRow(element, row); | |
System.Windows.Controls.Grid.SetColumn(element, column); | |
} | |
} | |
#endregion | |
} | |
} |