WPF之路——Grid(网格)

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

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


Grid允许我们通过自定义行列来进行布局,这类似于表格. 我们可以通过表格来进行交复杂框架的布局,然后再在表格内部利用其他布局方式或嵌套表格个方式进行布局.

可以通过设置Columns和Rows的属性,通过定义Grid的ColumnDifinitions和RowDifinitions来实现对于表格的定义,然后根据Grid.Column和Grid.Row的对象来制定位置的方式实现布局.


使用Grid的时候,一般先定义Grid的行和列的设置,然后在其放置其他控件并且设置他们的行号和列号即可,语法和语义都很简单和清晰
<Window x:Class="WpfApplication3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     
        Title="MainWindow" Height="350" Width="525">
    <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>
</Window>



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

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

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

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

第一步:使用ShowGridLines属性


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

<Window x:Class="WpfApplication3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     
        Title="MainWindow" Height="350" Width="525">
    <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>
</Window>


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

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

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

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

 <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>

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


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

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


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

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

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


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

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

答案就是附加属性。

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

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中稍做修改

<Window x:Class="WpfApplication3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ext="clr-namespace:WpfApplication3"
        Title="MainWindow" Height="350" Width="525">
    <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>
</Window>

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

看起来不错对吧?先不要着急,看看另外一个情况,加入我们希望把第一行的两列进行合并(ColumnSpan)的话,会怎么样呢?
<Window x:Class="WpfApplication3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ext="clr-namespace:WpfApplication3"
        Title="MainWindow" Height="350" Width="525">
    <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>
</Window>



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

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


请注意,将代码做如下的改动
 
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);

                    }

                };
            }
        }

    }


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

我们会自然联想到,给Border设置Padding属性就可以了吧,那么试试吧
 
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);
                    }
                };
            }
        }
    }

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



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

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

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里面去了。此所谓移花接木也。大家可以看到,现在每个格子里面的内容都与边框有一定的距离了。


  • 10
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
机器学习是一种人工智能(AI)的子领域,致力于研究如何利用数据和算法让计算机系统具备学习能力,从而能够自动地完成特定任务或者改进自身性能。机器学习的核心思想是让计算机系统通过学习数据中的模式和规律来实现目标,而不需要显式地编程。 机器学习应用非常广泛,包括但不限于以下领域: 图像识别和计算机视觉: 机器学习在图像识别、目标检测、人脸识别、图像分割等方面有着广泛的应用。例如,通过深度学习技术,可以训练神经网络来识别图像中的对象、人脸或者场景,用于智能监控、自动驾驶、医学影像分析等领域。 自然语言处理: 机器学习在自然语言处理领域有着重要的应用,包括文本分类、情感分析、机器翻译、语音识别等。例如,通过深度学习模型,可以训练神经网络来理解和生成自然语言,用于智能客服、智能助手、机器翻译等场景。 推荐系统: 推荐系统利用机器学习算法分析用户的行为和偏好,为用户推荐个性化的产品或服务。例如,电商网站可以利用机器学习算法分析用户的购买历史和浏览行为,向用户推荐感兴趣的商品。 预测和预测分析: 机器学习可以用于预测未来事件的发生概率或者趋势。例如,金融领域可以利用机器学习算法进行股票价格预测、信用评分、欺诈检测等。 医疗诊断和生物信息学: 机器学习在医疗诊断、药物研发、基因组学等领域有着重要的应用。例如,可以利用机器学习算法分析医学影像数据进行疾病诊断,或者利用机器学习算法分析基因数据进行疾病风险预测。 智能交通和物联网: 机器学习可以应用于智能交通系统、智能城市管理和物联网等领域。例如,可以利用机器学习算法分析交通数据优化交通流量,或者利用机器学习算法分析传感器数据监测设备状态。 以上仅是机器学习应用的一部分,随着机器学习技术的不断发展和应用场景的不断拓展,机器学习在各个领域都有着重要的应用价值,并且正在改变我们的生活和工作方式。
WPF(Windows Presentation Foundation)是一种用于创建Windows应用程序的界面开发技术,其基于.NET Framework平台,并使用C#编程语言进行开发。WPF提供了一套强大的工具和框架,使开发人员能够轻松创建富有吸引力、功能丰富的用户界面。 WPF与传统的Windows Forms相比具有许多优势。首先,WPF支持更加灵活和现代化的用户界面设计,可以轻松地创建透明、动画和多媒体效果等视觉效果。其次,WPF具有更好的分离性,允许开发人员将界面逻辑与业务逻辑进行分离,使代码更加清晰和易于维护。此外,WPF还支持数据绑定和样式模板等功能,使界面开发更加高效和可重用。 在使用WPF进行编程时,首先需要了解XAML(Extensible Application Markup Language)语言,它用于定义WPF界面元素和布局。然后,使用C#语言编写代码逻辑,处理用户交互、数据绑定、事件处理等方面的功能。在WPF中,可以通过使用命令模式和MVVM(Model-View-ViewModel)架构来组织和管理代码,以实现更好的代码分离性。 另外,WPF提供了丰富的控件库,开发人员可以使用这些控件来构建各种功能和复杂的界面。同时,WPF还支持自定义控件的开发,开发人员可以根据具体需求创建自己的控件。 总之,WPF是一种强大的界面开发技术,可以帮助开发人员创建出具有丰富功能和吸引力的Windows应用程序。同时,使用C#语言进行开发可以使开发过程更加高效和灵活。无论是初学者还是有经验的开发人员,都可以从WPF编程宝典中学习到丰富的知识和技巧,提高自己的WPF编程能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值