wpf 自定义Grid RowDefinitions and ColumnDefinitions 赋*值
2013-02-26 10:21
465 查看
Introduction
My favorite part of programming in WPF is the ease with which we compartmentalize the application components. Every Pageor
UserControlof
course has a markup and a code-behind file, but if you are serious about architectural patterns like I am, then you also have a
ViewModeland
probably a related resource file as well.
I prefer to keep my markup files clean, defining visual attributes in a separate resource file. In doing so, adding or removing elements later, or altering visual properties is done more quickly and with more flexibility...all while providing the serenity that
comes with S.O.C. (Separation of concerns).
Whether your styles are defined in the local file markup or a merged resource dictionary, there are some inherent limitations to stylization that can quite easily be overcome. Take for instance
RowDefinitionsand
ColumnDefinitions...you
can be diligent about parsing out the visual attributes of your
UserControl, but ultimately you are forced to define
these in your markup file, and unless you modify it in managed code at runtime, these values are inevitably STATIC. Not very frindly to a skinnable interface, is it?
Background
When I decided that this was a problem that I wanted to resolve, I knew that it had to be something simple. My first thought was a custom Behavior,
but I quickly got away from that idea. Instead, I went to one of the most underrated elements in WPF - the
Attached Property.
Attached Property is a special type of
Dependency Propertythat can be assigned to any
Dependency Object. Since a Grid just happens to be a Dependency Object, the solution was not only sufficient, it was elegant. I use Attached Properties daily, and for a wide variety of purposes. I rarely build an app without them!
Using the code
Attached Properties can be defined within ANY class in your application. For demonstration purposes, this article will assume that you placed the declaration in the ApplicationClass.
C#:
Collapse | Copy
Code
public static DependencyProperty GridRowsProperty= DependencyProperty.RegisterAttached("GridRows", typeof(string), MethodBase.GetCurrentMethod().DeclaringType, new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsArrange, new PropertyChangedCallback(GridRowsPropertyChanged)));
We declare the Attached Property as a String type, so that in markup we can enter a comma separated list of Column/Row definitions.
Note: Because I use an extensive library of code snippets, I use Reflection to set
the ownerType in my Dependency Propertydeclarations. It is perfectly fine to replace
MethodBase.GetCurrentMethod().DeclaringTypewith
GetType(
Application) (Or whatever class you are declaring the Attached property within).
In order for an Attached Property to function, we must define the
Getand
SetAccessors.
If you are new to the Dependency Propertysystem, it is important to note that naming convention is very specific.
C#:
Collapse | Copy
Code
public static string GetGridRows(Grid This) { return Convert.ToString(This.GetValue(GridRowsProperty)); } public static void SetGridRows(Grid This, string Value) { This.SetValue(GridRowsProperty, Value); }
In the
GridRowsPropertydeclaration, we assigned the PropertyChangedCallback method
GridRowsPropertyChanged.
This method will be called when the value of the
GridRowsPropertyis initialized or modified.
C#:
Collapse | Copy
Code
private static void GridRowsPropertyChanged(object Sender, DependencyPropertyChangedEventArgs e) { object This = Sender as Grid; if (This == null) throw new Exception("Only elements of type 'Grid' can utilize the 'GridRows' attached property"); DefineGridRows(This); }
In the callback method, we are simply checking that the property was attached to a Grid element, and then calling the method that modifies the
RowDefinitions.
C#:
Collapse | Copy
Code
private static void DefineGridRows(Grid This) { object Rows = GetGridRows(This).Split(Convert.ToChar(",")); This.RowDefinitions.Clear(); foreach ( Row in Rows) { switch (Row.Trim.ToLower) { case "auto": This.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }); break; case "*": This.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }); break; default: if (System.Text.RegularExpressions.Regex.IsMatch(Row, "^\\d+\\*$")) { This.RowDefinitions.Add(new RowDefinition { Height = new GridLength( Convert.ToInt32(Row.Substring(0, Row.IndexOf(Convert.ToChar("*")))), GridUnitType.Star) }); } else if (Information.IsNumeric(Row)) { This.RowDefinitions.Add(new RowDefinition { Height = new GridLength(Convert.ToDouble(Row), GridUnitType.Pixel) }); } else { throw new Exception("The only acceptable value for the 'GridRows' " + "attached property is a comma separated list comprised of the following options:" + Constants.vbCrLf + Constants.vbCrLf + "Auto,*,x (where x is the pixel " + "height of the row), x* (where x is the row height multiplier)"); } break; } } }
The
DefineGridRowsmethod first calls the GetAccessor for the
GridRowsProperty,
which returns the string that represents a comma separated list of RowDefinition values.
Next we clear any existing
RowDefinitionsfrom the RowDefinitionCollection.
Using a String type allows us to define an indefinite number of Rows/Columns in XAML or through databinding, but it means we have to parse the string data to determine what our RowDefinitionswill actually be.
Before we evaluate the String value, we call the String Functions Trim() and ToLower(). This adds to the flexibility of the XAML definition (or Binding) because we need not worry about case sensitivity or white space.
Our attached property will support the 4 (I know it's literally 3, but since there's really 4 implementations it makes more sense to call it so) types of Row/Column definitions, explained in detail here.
You can see above that we iterate through the supplied values and append a new RowDefinition according to the value.
Note that if there is a parsing error, we are throwing a descriptive error message for clarity during the debug process.
That's it for the code! Now all that's left is to mark it up. Make sure that you reference your assembly in your directive:
Collapse | Copy
Code
<UserControlx:class="MyUserControl" mc:ignorable="d"
xmlns:local="clr-namespace:MyApplication"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"/>
Now you can define the RowDefinitionsin XAML like this:
Collapse | Copy
Code
<Grid local:Application.GridRows="Auto,50,*,Auto" />
Which is the equivalent of:
Collapse | Copy
Code
<Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="50"/> <RowDefinition/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> </Grid>
Or more importantly, you can define the RowDefinitionsfrom within a Style, defined anywhere in your application as a resource:
Collapse | Copy
Code
<Style x:Key="StylizedRowDefinitions" TargetType="Grid"> <Setter Property="local:Application.GridRows" Value="3*,5*,Auto"/> </Style>
Collapse | Copy
Code
<Grid Style="{DynamicResource StylizedRowDefinitions}"> </Grid>
Now we will implement the
GridColumnsProperty, and we can see all the code together:
C#:
Collapse | Copy
Code
public static DependencyProperty GridColumnsProperty = DependencyProperty.RegisterAttached("GridColumns", typeof(string), MethodBase.GetCurrentMethod().DeclaringType, new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsArrange, new PropertyChangedCallback(GridColumnsPropertyChanged))); private static void GridColumnsPropertyChanged(object Sender, DependencyPropertyChangedEventArgs e) { if (_GridColumnsChanged != null) { _GridColumnsChanged(Sender, e); } object This = Sender as Grid; if (This == null) throw new Exception("Only elements of type 'Grid' can " + "utilize the 'GridColumns' attached property"); DefineGridColumns(This); } public static string GetGridColumns(Grid This) { return Convert.ToString(This.GetValue(GridColumnsProperty)); } public static void SetGridColumns(Grid This, string Value) { This.SetValue(GridColumnsProperty, Value); } private static void DefineGridColumns(Grid This) { object Columns = GetGridColumns(This).Split(Convert.ToChar(",")); This.ColumnDefinitions.Clear(); foreach ( Column in Columns) { switch (Column.Trim.ToLower) { case "auto": This.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }); break; case "*": This.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); break; default: if (System.Text.RegularExpressions.Regex.IsMatch(Column, "^\\d+\\*$")) { This.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(Convert.ToInt32(Column.Substring(0, Column.IndexOf(Convert.ToChar("*")))), GridUnitType.Star) }); } else if (Information.IsNumeric(Column)) { This.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(Convert.ToDouble(Column), GridUnitType.Pixel) }); } else { throw new Exception("The only acceptable value for the 'GridColumns' attached " + "property is a comma separated list comprised of the following options:" + Constants.vbCrLf + Constants.vbCrLf + "Auto,*,x (where x is the pixel width of the column), " + "x* (where x is the column width multiplier)"); } break; } } }
Now defining your Column Definitions is as simple as defining your RowDefinitions
Collapse | Copy
Code
<Style x:Key="MyGridStyle" TargetType="Grid"> <Setter Property="local:Application.GridColumns" Value="Auto,*"/> <Setter Property="local:Application.GridRows" Value="1*,*,Auto"/> <Style> <Grid Style="{DynamicResource MyGridStyle}"> <Grid>
Points of Interest
Stylizing Row/Column Definitions bridges a gap that exists in WPF scalability, and helps make your application more dynamic. Attached Properties are a vastly underrated way of achieving scalability with often very little code compared to other approaches.
相关文章推荐
- [Grid Layout] Place grid items on a grid using grid-column and grid-row
- 有序矩阵中查找第k小的元素 Kth smallest element in a row-wise and column-wise sorted 2D array
- WPF中自定义GridLengthAnimation
- [Twitter] Given a matrix with all elements sorted on each individual row and column find
- ExtJS Tips->Grid column的自定义格式
- Write an algorithm such that if an element in an MxN matrix is 0, its entire row and column is set to 0.
- Xaml技术:浅谈Grid.ColumnDefinitions和Grid.RowDefinitions属性
- NPOI 1.2.3教程 -17 隐藏行列HideColumnAndRow
- codechef Row and Column Operations 题解
- sourcegrid 应用实例(全部来自官网下载的例子)——rowspan and columnspan
- WPF and Silverlight 学习笔记(二十一):数据绑定值的自定义转换
- generate html code from QTableWidget with row and column span support
- numpy : the difference between row and column vector
- silverlight中Grid.ColumnDefinitions属性设置错误
- WPF: Changing sizes of grid rows and columns during runtime
- WPF 下的自定义控件以及 Grid 中控件的自适应
- AndrowListView实现(自定义游戏列表)防止屏幕闪烁,设置分割线android中不推荐的方法,要考虑向下兼容,用了推荐的新方法,可能不兼容旧版本系统的手机
- sourcegrid 应用实例(全部来自官网下载的例子)——show and hide column
- radgrid column filter, filtered by a “From” and “To” date, inclusive
- WPF and Silverlight 学习笔记(八):WPF布局管理之Grid、UniformGrid