• 为WPF和Silverlight的Grid添加边框线


    Grid是WPF和Silverlight中的一个重要的布局元素,其他的布局元素还有StackPanel, Canvas, Border等等。从字面上说,Grid是一个表格的意思,它的使用也确实很方便,从视觉上很像一个表格的样式,有行,有列的概念,这种效果很适合于需要多多个子控件进行布局,并希望保持左边或者上对齐的效果。

    我们来看一个最简单的例子(本文采用Silverlight做演示,在WPF中也是一样的)

    使用Grid的时候,一般先定义Grid的行和列的设置,然后在其放置其他控件并且设置他们的行号和列号即可,语法和语义都很简单和清晰

    <UserControl
        x:Class="SilverlightApplicationGridBorderSample.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://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>
    

    image

    嗯,看起来很好理解的。但是,几乎所有人(包括我在内)在最开始学习的时候,马上就会想到一个问题:

    既然是用一个表格形状进行布局了,那么能不能显示出来表格的边框线呢

    没想到这会是一个问题,对吧?或者你想到过了,但没有找到如何解决这个问题。

    如果是这样,那么请继续往下看吧

    本文完整源代码,可以通过这里下载

    https://files.cnblogs.com/chenxizhang/SilverlightApplicationGridBorderSample.rar

    第一步:使用ShowGridLines属性

    根据经验,我们会先从Grid这个元素上面去想办法。我们确实可以找到一个与我们需求很相近的属性:ShowGridLines,好吧,将它设为true之后,会怎么样呢

    <UserControl
        x:Class="SilverlightApplicationGridBorderSample.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://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>
    

    image

    哦,看起来确实有边框了。但是,效果却不理想。这个边框线是虚线,坦白说,不是那么好看。从MSDN文档中我们了解到,这个边框线只是用来辅助我们做调试用的,而不适宜于在真正的产品中用。

    私下里说,我并不认为这是一个好的设计,为什么不提供实线(甚至可以由开发人员配置)的边框呢

    第二步:使用手工定义的方式实现Grid边框线

    我们希望给Grid自动添加边框,应该怎么实现呢?其实,如果仅仅是给一个Grid添加的话,手工写一点代码就可以了

    <UserControl
        x:Class="SilverlightApplicationGridBorderSample.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://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个边框-->
            <Border></Border>
            <Border
                Grid.Row="1"></Border>
            <Border
                Grid.Column="1"></Border>
            <Border
                Grid.Row="1"
                Grid.Column="1"></Border>
        </Grid>
    </UserControl>
    

    image

    所以,其实我们可以手工添加Border(文章开头提到几个布局元素中,只有Border有边框线)实现我们的需求。

    但问题在于,如果有很多Grid,都要这么去添加总是不好的吧,有没有办法更好地实现这样功能呢

    第三步:使用附加依赖属性(Attached Dependency Property)实现Grid边框线

    既然官方并没有提供我们需要的边框线,那么我们就自己来实现一个吧。事实上,这并没有多难,尤其是你理解了WPF和Silvelight中一些核心的概念的情况下,这些概念包括依赖属性附加依赖属性

    对这两个概念,大家可以参考上面我给出的两个链接,这里我就简单地说几句吧,

    依赖属性(Dependency Property)是WPF和Silverlight较之前的编程模型的一个核心改变,它使得基于绑定的编程变得可能,这个大家多少有些体会了,在WPF和Silverlight中,绑定无处不在,而且功能确实强大,尤其是双向绑定及自动通知,减少了很多很多的用户代码。

    附加属性(Attached Property),则是另外一种场景,它一般用来对现有控件或者元素进行扩展。其实,我们之前的XAML中已经用到了附加属性,请看下面的部分

            <TextBlock
                Text="右下角"
                Grid.Row="1"
                Grid.Column="1"></TextBlock>

    TextBlock这个元素,其实并没有行和列的概念,或者说它也不需要,除非它是放在一个Grid里面的时候。所以,行和列并不是TextBlock的属性,但是如果将它放在Grid里面,不提供这些信息又不行,所以附加属性就应运而生了。Grid.Row和Grid.Column就是附加属性,很显然,有了附加属性,我们就可以在不改变TextBlock的前提下,为它添加很多特性或者功能。

    理解了附加属性,我们来说说现在我们要解决的问题:

    我们能不能自动给每个Grid添加一个属性,让它可以为自己添加必要的边框线呢?

    答案就是附加属性。

    请添加一个代码文件,将下面代码粘贴进去

    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    
    namespace SilverlightApplicationGridBorderSample
    {
        /// <summary>
        /// 为Grid添加的一个特殊功能
        /// 作者:陈希章
        /// 反馈:ares@xizhang.com
        /// </summary>
        public class GridHelper
        {
    
            //请注意:可以通过propa这个快捷方式生成下面三段代码
    
            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));
    
    
            //这是一个事件处理程序,需要手工编写,必须是静态方法
            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) =>
                    {
                        //确定行和列数
                        var rows = grid.RowDefinitions.Count;
                        var columns = grid.ColumnDefinitions.Count;
    
                        //每个格子添加一个Border进去
                        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);
                            }
                        }
    
                    };
                }
    
            }
    
        }
    
    }
    

    上面的代码应该很好理解,我们是用代码实现了与手工添加Border一样的功能,奥妙在于,Grid有一个Loaded事件,我们完全可以在这个事件里面,根据计算得到的行和列去添加Border。

    如何在页面中使用我们附加属性呢?我们需要在XAML中稍做修改

    <UserControl
        x:Class="SilverlightApplicationGridBorderSample.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://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>
    

    请注意,我们只需要导入命名空间,然后在Grid上面设置ext:GridHelper.ShowBorder="True" 即可。这就是附加属性的神奇之处

    看起来不错对吧?先不要着急,看看另外一个情况,加入我们希望把第一行的两列进行合并(ColumnSpan)的话,会怎么样呢?

    <UserControl
        x:Class="SilverlightApplicationGridBorderSample.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://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="第一行合并两个列的内容" Grid.ColumnSpan="2"></TextBlock>
            <TextBlock
                Text="左下角"
                Grid.Row="1"></TextBlock>
    
    
    
            <TextBlock
                Text="右下角"
                Grid.Row="1"
                Grid.Column="1"></TextBlock>
        </Grid>
    </UserControl>
    
     
    image
     

    我们看到,虽然确实第一行是合并了,但是边框线却仍然有两个,也就是说,我们在添加Border的时候,没有考虑到行或者列合并的情况。这样就不是特别理想了。我们下面要改进这个属性。

    第四步:改进附加属性适应行和列的合并情况

    请注意,将代码做如下的改动

    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    
    namespace SilverlightApplicationGridBorderSample
    {
        /// <summary>
        /// 为Grid添加的一个特殊功能
        /// 作者:陈希章
        /// 反馈:ares@xizhang.com
        /// </summary>
        public class GridHelper
        {
    
            //请注意:可以通过propa这个快捷方式生成下面三段代码
    
            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));
    
    
            //这是一个事件处理程序,需要手工编写,必须是静态方法
            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) =>
                    {
                        //改进后的做法,不是简单地根据行和列,而是根据Grid的顶层子控件的个数去添加边框,同时考虑合并的情况
                        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);
    
                        }
    
                    };
                }
    
            }
    
        }
    
    }
    

    image

    改动之后的效果明显比较理想了,它考虑到了行和列的合并的情况。那么,事情结束了么?先不要着急,我们再来做一个事情,假设我们希望每个单元格中的内容都与边框(左,上,右,下)有一定的距离,怎么实现呢?

    我们会自然联想到,给Border设置Padding属性就可以了吧,那么试试吧

    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    
    namespace SilverlightApplicationGridBorderSample
    {
        /// <summary>
        /// 为Grid添加的一个特殊功能
        /// 作者:陈希章
        /// 反馈:ares@xizhang.com
        /// </summary>
        public class GridHelper
        {
    
            //请注意:可以通过propa这个快捷方式生成下面三段代码
    
            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));
    
    
            //这是一个事件处理程序,需要手工编写,必须是静态方法
            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) =>
                    {
                        //改进后的做法,不是简单地根据行和列,而是根据Grid的顶层子控件的个数去添加边框,同时考虑合并的情况
                        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);
    
                        }
    
                    };
                }
    
            }
    
        }
    
    }
    

    看起来是可以的,但是运行起来,情况好像没有什么变化。我们的文字与边框仍然没有任何距离。

    image

    这是为什么呢?既然Border设置了Padding属性,那么又为什么实现不了我们需要的效果呢?
    其实很简单,Border的Padding属性只影响它内部的子元素或者控件。我们上面的代码,只是创建了Border,并且将其添加到Grid的Chiildren里面去。但并没有将那些TextBlock移动到Border里面去,所以就实现不了Padding效果了。

    第五步:移动TextBlock到相应的Border

    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    
    namespace SilverlightApplicationGridBorderSample
    {
        /// <summary>
        /// 为Grid添加的一个特殊功能
        /// 作者:陈希章
        /// 反馈:ares@xizhang.com
        /// </summary>
        public class GridHelper
        {
    
            //请注意:可以通过propa这个快捷方式生成下面三段代码
    
            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));
    
    
            //这是一个事件处理程序,需要手工编写,必须是静态方法
            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) =>
                    {
    
                        //这种做法自动将控件移动到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设置为20. 请注意,上述代码中,我们先从Grid中移除掉了有关的控件,然后将这些控件添加到Border里面去了。此所谓移花接木也。大家可以看到,现在每个格子里面的内容都与边框有一定的距离了。

    image

    本文完整源代码,可以通过这里下载

    https://files.cnblogs.com/chenxizhang/SilverlightApplicationGridBorderSample.rar

    总结:

    本文采用循序渐进的方式演示了如何为Grid元素添加边框线的功能,从最原始的手工方式,逐渐演化到最后的解决方案。通过本文,你可以学会这个具体的场景问题解决方法,更可以体会到如何通过附加属性实现更多特殊的功能。

  • 相关阅读:
    对C# .Net4.5异步机制测试
    权限系统设计
    C#基础知识
    eclipse+pyDev
    Ubuntu下使用sublime text进行py开发
    110_02 补充模块:BeatifulSoup模块
    034 如何判断一个对象是否是可调用对象
    037 简单计算器实现
    036 re模块的小练习
    035 用Python实现的二分查找算法(基于递归函数)
  • 原文地址:https://www.cnblogs.com/chenxizhang/p/2185414.html
Copyright © 2020-2023  润新知