您的位置:首页 > 其它

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
Page
or
UserControl
of
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
ViewModel
and
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
RowDefinitions
and
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 Property
that 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
Application
Class.


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().DeclaringType
with
GetType(
Application
) (Or whatever class you are declaring the Attached property within).

In order for an Attached Property to function, we must define the
Get
and
Set
Accessors.
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
GridRowsProperty
declaration, we assigned the PropertyChangedCallback method
GridRowsPropertyChanged
.
This method will be called when the value of the
GridRowsProperty
is 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
DefineGridRows
method 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
RowDefinitions
from 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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐