轉(zhuǎn)帖|其它|編輯:郝浩|2011-09-23 14:03:16.000|閱讀 1371 次
概述:Grid是WPF和Silverlight中的一個(gè)重要的布局元素,其他的布局元素還有StackPanel, Canvas, Border等等。從字面上說,Grid是一個(gè)表格的意思,它的使用也確實(shí)很方便,從視覺上很像一個(gè)表格的樣式,有行,有列的概念,這種效果很適合于需要多多個(gè)子控件進(jìn)行布局,并希望保持左邊或者上對(duì)齊的效果。
# 界面/圖表報(bào)表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
Grid是WPF和Silverlight中的一個(gè)重要的布局元素,其他的布局元素還有StackPanel, Canvas, Border等等。從字面上說,Grid是一個(gè)表格的意思,它的使用也確實(shí)很方便,從視覺上很像一個(gè)表格的樣式,有行,有列的概念,這種效果很適合于需要多多個(gè)子控件進(jìn)行布局,并希望保持左邊或者上對(duì)齊的效果。
我們來看一個(gè)最簡(jiǎn)單的例子(本文采用Silverlight做演示,在WPF中也是一樣的)
使用Grid的時(shí)候,一般先定義Grid的行和列的設(shè)置,然后在其放置其他控件并且設(shè)置他們的行號(hào)和列號(hào)即可,語法和語義都很簡(jiǎn)單和清晰
<UserControl
x:Class="SilverlightApplicationGridBorderSample.MainPage"
xmlns="//schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="//schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="//schemas.microsoft.com/expression/blend/2008"
xmlns:mc="//schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid
x:Name="LayoutRoot"
Background="White">
<Grid.Resources>
<Style
TargetType="TextBlock">
<Setter
Property="FontSize"
Value="30"></Setter>
<Setter
Property="VerticalAlignment"
Value="Center"></Setter>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock
Text="左上角"></TextBlock>
<TextBlock
Text="左下角"
Grid.Row="1"></TextBlock>
<TextBlock
Text="右上角"
Grid.Column="1"></TextBlock>
<TextBlock
Text="右下角"
Grid.Row="1"
Grid.Column="1"></TextBlock>
</Grid>
</UserControl>
嗯,看起來很好理解的。但是,幾乎所有人(包括我在內(nèi))在最開始學(xué)習(xí)的時(shí)候,馬上就會(huì)想到一個(gè)問題:
既然是用一個(gè)表格形狀進(jìn)行布局了,那么能不能顯示出來表格的邊框線呢?
沒想到這會(huì)是一個(gè)問題,對(duì)吧?或者你想到過了,但沒有找到如何解決這個(gè)問題。
如果是這樣,那么請(qǐng)繼續(xù)往下看吧
本文完整源代碼,可以通過這里下載
//files.cnblogs.com/chenxizhang/SilverlightApplicationGridBorderSample.rar
第一步:使用ShowGridLines屬性
根據(jù)經(jīng)驗(yàn),我們會(huì)先從Grid這個(gè)元素上面去想辦法。我們確實(shí)可以找到一個(gè)與我們需求很相近的屬性:ShowGridLines,好吧,將它設(shè)為true之后,會(huì)怎么樣呢
<UserControl
x:Class="SilverlightApplicationGridBorderSample.MainPage"
xmlns="//schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="//schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="//schemas.microsoft.com/expression/blend/2008"
xmlns:mc="//schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid
x:Name="LayoutRoot"
Background="White" ShowGridLines="True" >
<Grid.Resources>
<Style
TargetType="TextBlock">
<Setter
Property="FontSize"
Value="30"></Setter>
<Setter
Property="VerticalAlignment"
Value="Center"></Setter>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock
Text="左上角"></TextBlock>
<TextBlock
Text="左下角"
Grid.Row="1"></TextBlock>
<TextBlock
Text="右上角"
Grid.Column="1"></TextBlock>
<TextBlock
Text="右下角"
Grid.Row="1"
Grid.Column="1"></TextBlock>
</Grid>
</UserControl>
哦,看起來確實(shí)有邊框了。但是,效果卻不理想。這個(gè)邊框線是虛線,坦白說,不是那么好看。從MSDN文檔中我們了解到,這個(gè)邊框線只是用來輔助我們做調(diào)試用的,而不適宜于在真正的產(chǎn)品中用。
私下里說,我并不認(rèn)為這是一個(gè)好的設(shè)計(jì),為什么不提供實(shí)線(甚至可以由開發(fā)人員配置)的邊框呢
第二步:使用手工定義的方式實(shí)現(xiàn)Grid邊框線
我們希望給Grid自動(dòng)添加邊框,應(yīng)該怎么實(shí)現(xiàn)呢?其實(shí),如果僅僅是給一個(gè)Grid添加的話,手工寫一點(diǎn)代碼就可以了
<UserControl
x:Class="SilverlightApplicationGridBorderSample.MainPage"
xmlns="//schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="//schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="//schemas.microsoft.com/expression/blend/2008"
xmlns:mc="//schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid
x:Name="LayoutRoot"
Background="White">
<Grid.Resources>
<Style
TargetType="TextBlock">
<Setter
Property="FontSize"
Value="30"></Setter>
<Setter
Property="VerticalAlignment"
Value="Center"></Setter>
</Style>
<Style
TargetType="Border">
<Setter
Property="BorderBrush"
Value="LightGray"></Setter>
<Setter
Property="BorderThickness"
Value="1"></Setter>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock
Text="左上角"></TextBlock>
<TextBlock
Text="左下角"
Grid.Row="1"></TextBlock>
<TextBlock
Text="右上角"
Grid.Column="1"></TextBlock>
<TextBlock
Text="右下角"
Grid.Row="1"
Grid.Column="1"></TextBlock>
<!--添加4個(gè)邊框-->
<Border></Border>
<Border
Grid.Row="1"></Border>
<Border
Grid.Column="1"></Border>
<Border
Grid.Row="1"
Grid.Column="1"></Border>
</Grid>
</UserControl>
所以,其實(shí)我們可以手工添加Border(文章開頭提到幾個(gè)布局元素中,只有Border有邊框線)實(shí)現(xiàn)我們的需求。
但問題在于,如果有很多Grid,都要這么去添加總是不好的吧,有沒有辦法更好地實(shí)現(xiàn)這樣功能呢
第三步:使用附加依賴屬性(Attached Dependency Property)實(shí)現(xiàn)Grid邊框線
既然官方并沒有提供我們需要的邊框線,那么我們就自己來實(shí)現(xiàn)一個(gè)吧。事實(shí)上,這并沒有多難,尤其是你理解了WPF和Silvelight中一些核心的概念的情況下,這些概念包括依賴屬性和附加依賴屬性。
對(duì)這兩個(gè)概念,大家可以參考上面我給出的兩個(gè)鏈接,這里我就簡(jiǎn)單地說幾句吧,
依賴屬性(Dependency Property)是WPF和Silverlight較之前的編程模型的一個(gè)核心改變,它使得基于綁定的編程變得可能,這個(gè)大家多少有些體會(huì)了,在WPF 和Silverlight中,綁定無處不在,而且功能確實(shí)強(qiáng)大,尤其是雙向綁定及自動(dòng)通知,減少了很多很多的用戶代碼。
附加屬性(Attached Property),則是另外一種場(chǎng)景,它一般用來對(duì)現(xiàn)有控件或者元素進(jìn)行擴(kuò)展。其實(shí),我們之前的XAML中已經(jīng)用到了附加屬性,請(qǐng)看下面的部分
<TextBlock
Text="右下角"
Grid.Row="1"
Grid.Column="1"></TextBlock>
TextBlock這個(gè)元素,其實(shí)并沒有行和列的概念,或者說它也不需要,除非它是放在一個(gè)Grid里面的時(shí)候。所以,行和列并不是 TextBlock的屬性,但是如果將它放在Grid里面,不提供這些信息又不行,所以附加屬性就應(yīng)運(yùn)而生了。Grid.Row和Grid.Column 就是附加屬性,很顯然,有了附加屬性,我們就可以在不改變TextBlock的前提下,為它添加很多特性或者功能。
理解了附加屬性,我們來說說現(xiàn)在我們要解決的問題:
我們能不能自動(dòng)給每個(gè)Grid添加一個(gè)屬性,讓它可以為自己添加必要的邊框線呢?
答案就是附加屬性。
請(qǐng)?zhí)砑右粋€(gè)代碼文件,將下面代碼粘貼進(jìn)去
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace SilverlightApplicationGridBorderSample
{
/// <summary>
/// 為Grid添加的一個(gè)特殊功能
/// 作者:陳希章
/// 反饋:ares@xizhang.com
/// </summary>
public class GridHelper
{
//請(qǐng)注意:可以通過propa這個(gè)快捷方式生成下面三段代碼
public static bool GetShowBorder(DependencyObject obj)
{
return (bool)obj.GetValue(ShowBorderProperty);
}
public static void SetShowBorder(DependencyObject obj, bool value)
{
obj.SetValue(ShowBorderProperty, value);
}
public static readonly DependencyProperty ShowBorderProperty =
DependencyProperty.RegisterAttached("ShowBorder", typeof(bool), typeof(GridHelper), new PropertyMetadata(OnShowBorderChanged));
//這是一個(gè)事件處理程序,需要手工編寫,必須是靜態(tài)方法
private static void OnShowBorderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = d as Grid;
if((bool)e.OldValue)
{
grid.Loaded -= (s, arg) => { };
}
if((bool)e.NewValue)
{
grid.Loaded += (s, arg) =>
{
//確定行和列數(shù)
var rows = grid.RowDefinitions.Count;
var columns = grid.ColumnDefinitions.Count;
//每個(gè)格子添加一個(gè)Border進(jìn)去
for(int i = 0; i < rows; i++)
{
for(int j = 0; j < columns; j++)
{
var border = new Border() { BorderBrush = new SolidColorBrush(Colors.Gray), BorderThickness = new Thickness(1) };
Grid.SetRow(border, i);
Grid.SetColumn(border, j);
grid.Children.Add(border);
}
}
};
}
}
}
}
上面的代碼應(yīng)該很好理解,我們是用代碼實(shí)現(xiàn)了與手工添加Border一樣的功能,奧妙在于,Grid有一個(gè)Loaded事件,我們完全可以在這個(gè)事件里面,根據(jù)計(jì)算得到的行和列去添加Border。
如何在頁面中使用我們附加屬性呢?我們需要在XAML中稍做修改
<UserControl
x:Class="SilverlightApplicationGridBorderSample.MainPage"
xmlns="//schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="//schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="//schemas.microsoft.com/expression/blend/2008"
xmlns:mc="//schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
xmlns:ext="clr-namespace:SilverlightApplicationGridBorderSample">
<Grid
ext:GridHelper.ShowBorder="True"
x:Name="LayoutRoot"
Background="White">
<Grid.Resources>
<Style
TargetType="TextBlock">
<Setter
Property="FontSize"
Value="30"></Setter>
<Setter
Property="VerticalAlignment"
Value="Center"></Setter>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock
Text="左上角"></TextBlock>
<TextBlock
Text="左下角"
Grid.Row="1"></TextBlock>
<TextBlock
Text="右上角"
Grid.Column="1"></TextBlock>
<TextBlock
Text="右下角"
Grid.Row="1"
Grid.Column="1"></TextBlock>
</Grid>
</UserControl>
請(qǐng)注意,我們只需要導(dǎo)入命名空間,然后在Grid上面設(shè)置ext:GridHelper.ShowBorder="True" 即可。這就是附加屬性的神奇之處
看起來不錯(cuò)對(duì)吧?先不要著急,看看另外一個(gè)情況,加入我們希望把第一行的兩列進(jìn)行合并(ColumnSpan)的話,會(huì)怎么樣呢?
<UserControl
x:Class="SilverlightApplicationGridBorderSample.MainPage"
xmlns="//schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="//schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="//schemas.microsoft.com/expression/blend/2008"
xmlns:mc="//schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
xmlns:ext="clr-namespace:SilverlightApplicationGridBorderSample">
<Grid
ext:GridHelper.ShowBorder="True"
x:Name="LayoutRoot"
Background="White">
<Grid.Resources>
<Style
TargetType="TextBlock">
<Setter
Property="FontSize"
Value="30"></Setter>
<Setter
Property="VerticalAlignment"
Value="Center"></Setter>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock
Text="第一行合并兩個(gè)列的內(nèi)容" Grid.ColumnSpan="2"></TextBlock>
<TextBlock
Text="左下角"
Grid.Row="1"></TextBlock>
<TextBlock
Text="右下角"
Grid.Row="1"
Grid.Column="1"></TextBlock>
</Grid>
</UserControl>
我們看到,雖然確實(shí)第一行是合并了,但是邊框線卻仍然有兩個(gè),也就是說,我們?cè)谔砑覤order的時(shí)候,沒有考慮到行或者列合并的情況。這樣就不是特別理想了。我們下面要改進(jìn)這個(gè)屬性。
第四步:改進(jìn)附加屬性適應(yīng)行和列的合并情況
請(qǐng)注意,將代碼做如下的改動(dòng)
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace SilverlightApplicationGridBorderSample
{
/// <summary>
/// 為Grid添加的一個(gè)特殊功能
/// 作者:陳希章
/// 反饋:ares@xizhang.com
/// </summary>
public class GridHelper
{
//請(qǐng)注意:可以通過propa這個(gè)快捷方式生成下面三段代碼
public static bool GetShowBorder(DependencyObject obj)
{
return (bool)obj.GetValue(ShowBorderProperty);
}
public static void SetShowBorder(DependencyObject obj, bool value)
{
obj.SetValue(ShowBorderProperty, value);
}
public static readonly DependencyProperty ShowBorderProperty =
DependencyProperty.RegisterAttached("ShowBorder", typeof(bool), typeof(GridHelper), new PropertyMetadata(OnShowBorderChanged));
//這是一個(gè)事件處理程序,需要手工編寫,必須是靜態(tài)方法
private static void OnShowBorderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = d as Grid;
if((bool)e.OldValue)
{
grid.Loaded -= (s, arg) => { };
}
if((bool)e.NewValue)
{
grid.Loaded += (s, arg) =>
{
//改進(jìn)后的做法,不是簡(jiǎn)單地根據(jù)行和列,而是根據(jù)Grid的頂層子控件的個(gè)數(shù)去添加邊框,同時(shí)考慮合并的情況
var controls = grid.Children;
var count = controls.Count;
for(int i = 0; i < count; i++)
{
var item = controls[i] as FrameworkElement;
var border = new Border()
{
BorderBrush = new SolidColorBrush(Colors.LightGray),
BorderThickness = new Thickness(1)
};
var row = Grid.GetRow(item);
var column = Grid.GetColumn(item);
var rowspan = Grid.GetRowSpan(item);
var columnspan = Grid.GetColumnSpan(item);
Grid.SetRow(border, row);
Grid.SetColumn(border, column);
Grid.SetRowSpan(border, rowspan);
Grid.SetColumnSpan(border, columnspan);
grid.Children.Add(border);
}
};
}
}
}
}
改動(dòng)之后的效果明顯比較理想了,它考慮到了行和列的合并的情況。那么,事情結(jié)束了么?先不要著急,我們?cè)賮碜鲆粋€(gè)事情,假設(shè)我們希望每個(gè)單元格中的內(nèi)容都與邊框(左,上,右,下)有一定的距離,怎么實(shí)現(xiàn)呢?
我們會(huì)自然聯(lián)想到,給Border設(shè)置Padding屬性就可以了吧,那么試試吧
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace SilverlightApplicationGridBorderSample
{
/// <summary>
/// 為Grid添加的一個(gè)特殊功能
/// 作者:陳希章
/// 反饋:ares@xizhang.com
/// </summary>
public class GridHelper
{
//請(qǐng)注意:可以通過propa這個(gè)快捷方式生成下面三段代碼
public static bool GetShowBorder(DependencyObject obj)
{
return (bool)obj.GetValue(ShowBorderProperty);
}
public static void SetShowBorder(DependencyObject obj, bool value)
{
obj.SetValue(ShowBorderProperty, value);
}
public static readonly DependencyProperty ShowBorderProperty =
DependencyProperty.RegisterAttached("ShowBorder", typeof(bool), typeof(GridHelper), new PropertyMetadata(OnShowBorderChanged));
//這是一個(gè)事件處理程序,需要手工編寫,必須是靜態(tài)方法
private static void OnShowBorderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = d as Grid;
if((bool)e.OldValue)
{
grid.Loaded -= (s, arg) => { };
}
if((bool)e.NewValue)
{
grid.Loaded += (s, arg) =>
{
//改進(jìn)后的做法,不是簡(jiǎn)單地根據(jù)行和列,而是根據(jù)Grid的頂層子控件的個(gè)數(shù)去添加邊框,同時(shí)考慮合并的情況
var controls = grid.Children;
var count = controls.Count;
for(int i = 0; i < count; i++)
{
var item = controls[i] as FrameworkElement;
var border = new Border()
{
BorderBrush = new SolidColorBrush(Colors.LightGray),
BorderThickness = new Thickness(1),
Padding= new Thickness(10)
};
var row = Grid.GetRow(item);
var column = Grid.GetColumn(item);
var rowspan = Grid.GetRowSpan(item);
var columnspan = Grid.GetColumnSpan(item);
Grid.SetRow(border, row);
Grid.SetColumn(border, column);
Grid.SetRowSpan(border, rowspan);
Grid.SetColumnSpan(border, columnspan);
grid.Children.Add(border);
}
};
}
}
}
}
看起來是可以的,但是運(yùn)行起來,情況好像沒有什么變化。我們的文字與邊框仍然沒有任何距離。
這是為什么呢?既然Border設(shè)置了Padding屬性,那么又為什么實(shí)現(xiàn)不了我們需要的效果呢?
其實(shí)很簡(jiǎn)單,Border的 Padding屬性只影響它內(nèi)部的子元素或者控件。我們上面的代碼,只是創(chuàng)建了Border,并且將其添加到Grid的Chiildren里面去。但并沒有將那些TextBlock移動(dòng)到Border里面去,所以就實(shí)現(xiàn)不了Padding效果了。
第五步:移動(dòng)TextBlock到相應(yīng)的Border
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace SilverlightApplicationGridBorderSample
{
/// <summary>
/// 為Grid添加的一個(gè)特殊功能
/// 作者:陳希章
/// 反饋:ares@xizhang.com
/// </summary>
public class GridHelper
{
//請(qǐng)注意:可以通過propa這個(gè)快捷方式生成下面三段代碼
public static bool GetShowBorder(DependencyObject obj)
{
return (bool)obj.GetValue(ShowBorderProperty);
}
public static void SetShowBorder(DependencyObject obj, bool value)
{
obj.SetValue(ShowBorderProperty, value);
}
public static readonly DependencyProperty ShowBorderProperty =
DependencyProperty.RegisterAttached("ShowBorder", typeof(bool), typeof(GridHelper), new PropertyMetadata(OnShowBorderChanged));
//這是一個(gè)事件處理程序,需要手工編寫,必須是靜態(tài)方法
private static void OnShowBorderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = d as Grid;
if((bool)e.OldValue)
{
grid.Loaded -= (s, arg) => { };
}
if((bool)e.NewValue)
{
grid.Loaded += (s, arg) =>
{
//這種做法自動(dòng)將控件移動(dòng)到Border里面來
var controls = grid.Children;
var count = controls.Count;
for(int i = 0; i < count; i++)
{
var item = controls[i] as FrameworkElement;
var border = new Border()
{
BorderBrush = new SolidColorBrush(Colors.LightGray),
BorderThickness = new Thickness(1),
Padding = new Thickness(20)
};
var row = Grid.GetRow(item);
var column = Grid.GetColumn(item);
var rowspan = Grid.GetRowSpan(item);
var columnspan = Grid.GetColumnSpan(item);
Grid.SetRow(border, row);
Grid.SetColumn(border, column);
Grid.SetRowSpan(border, rowspan);
Grid.SetColumnSpan(border, columnspan);
grid.Children.RemoveAt(i);
border.Child = item;
grid.Children.Insert(i, border);
}
};
}
}
}
}
為了大家看到效果,我將Padding設(shè)置為20. 請(qǐng)注意,上述代碼中,我們先從Grid中移除掉了有關(guān)的控件,然后將這些控件添加到Border里面去了。此所謂移花接木也。大家可以看到,現(xiàn)在每個(gè)格子里面的內(nèi)容都與邊框有一定的距離了。
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請(qǐng)務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請(qǐng)郵件反饋至chenjj@fc6vip.cn
文章轉(zhuǎn)載自:博客園