WPF中的Panel(面板),是继承自FrameworkElement的抽象类,表示一个可以用来排列子元素的面板。

在WPF中,一种预设了几种常用的面板,如Grid、StackPanel、WrapPanel等。他们都是继承自Panel类,并拥有自己的排列子元素的逻辑。

因此,想要自定义一个Panel,核心的问题就是如何排列子元素。

例如,我们做一个轴排列的元素,即元素沿着X、Y轴排列。我们定义,以面板宽度(Width)或者高度(Height)的中点,为坐标原点,水平向右为X轴,垂直向下位Y轴。如下图所示

先上源代码:

后代代码:

  1     public class AxisPanel:Panel
2 {
3 internal static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
4 "Oritentation",typeof(Orientation),typeof(AxisPanel),new FrameworkPropertyMetadata(Orientation.Horizontal,FrameworkPropertyMetadataOptions.AffectsArrange));
5
6
7 /// <summary>
8 /// 指示子元素的布局方向
9 /// </summary>
10 public Orientation Orientation
11 {
12 get => (Orientation)GetValue(OrientationProperty);
13 set => SetValue(OrientationProperty, value);
14 }
15
16
17 /// <summary>
18 /// 测量子元素,以确定自己的DesiredSize。
19 /// 此方法由父级元素自动调用
20 /// </summary>
21 /// <param name="availableSize">父级元素提供给该面板的可用空间尺寸</param>
22 /// <returns>此面板对象需要的尺寸(DesiredSize)</returns>
23 protected override Size MeasureOverride(Size availableSize)
24 {
25 double width = 0, height = 0;
26 switch(Orientation)
27 {
28 case Orientation.Horizontal:
29 // 水平布局
30 foreach(UIElement child in Children)
31 {
32 child.Measure(availableSize);
33 width += child.DesiredSize.Width; // 计算面板需要的宽度;
34
35 if(child.DesiredSize.Height > height) // 计算面板需要的高度:为所有元素中最大的高度
36 {
37 height = child.DesiredSize.Height;
38 }
39 }
40 break;
41
42 case Orientation.Vertical:
43 // 垂直布局
44 foreach(UIElement child in Children)
45 {
46 child.Measure(availableSize);
47 height += child.DesiredSize.Height; // 计算面板需要的高度
48
49 if(child.DesiredSize.Width > width)
50 {
51 width = child.DesiredSize.Width; // 计算面板需要宽度:为所有元素中最大的宽度
52 }
53 }
54 break;
55 }
56
57 return new Size(width, height);
58 }
59
60
61
62 /// <summary>
63 /// 排列子元素
64 /// 此方法由父级元素自动调用
65 /// </summary>
66 /// <param name="finalSize">父级元素提供给该面板用于布局的最终尺寸。</param>
67 /// <returns></returns>
68 protected override Size ArrangeOverride(Size finalSize)
69 {
70 double totalWidth = 0, totalHeight = 0; // 面板的总宽度、高度
71 switch(Orientation)
72 {
73 case Orientation.Horizontal: // 水平排列:需要计算子元素左上角的坐标点,已经用于排列子元素的区域大小
74 double x1, y1; // 子元素左上角点的坐标
75 for (int i=0;i<Children.Count;i++)
76 {
77 x1 = totalWidth; // 子元素的x坐标,应该为总宽度的值
78 y1 = (finalSize.Height - Children[i].DesiredSize.Height) / 2; // 子元素的y坐标,始终保存在轴上
79 totalWidth += Children[i].DesiredSize.Width; // 总宽度增加
80 Rect rect = new(x1, y1, Children[i].DesiredSize.Width, Children[i].DesiredSize.Height); // 放置子元素的矩形区域
81 Children[i].Arrange(rect); // 放置子元素
82 }
83 break;
84
85 case Orientation.Vertical:
86 double x2, y2=0;
87
88 for (int i = 0; i < Children.Count;i++)
89 {
90 x2 = (finalSize.Width - Children[i].DesiredSize.Width) / 2;
91 y2 = totalHeight;
92 totalHeight += Children[i].DesiredSize.Height; // 所有子元素的总高度
93 Rect rect2 = new(x2, y2, Children[i].DesiredSize.Width, Children[i].DesiredSize.Height);
94 Children[i].Arrange(rect2);
95 }
96 break;
97 }
98 return finalSize;
99 }
100 }

前端代码:

1     <rayPanel:AxisPanel Orientation="Horizontal">
2 <Button Content="Button1" Width="100" Height="40"/>
3 <Button Content="Button2" Width="100" Height="40" Canvas.Left="100"/>
4 <Button Content="Button3" Width="100" Height="40" Canvas.Left="200"/>
5 <TextBlock Text="你在么?"/>
6 </rayPanel:AxisPanel>

显示效果如下:

(当Orientation = “Horizontal”)                                                     (当Orientation="Vertical")

名词简写:

元素:表示一个继承自UIElement类的实例。

Frame元素:表示一个继承自FrameworkElement类的实例。

因为FrameworkElement是继承自UIElement的,因此在很多时候,一个FrameworkElement也可以被称为元素。只有在涉及到FrameworkElement类自己的属性、方法时,会被称为Frame元素。

WPF的布局,分为两个步骤:

1. 测量:确定子元素需要的尺寸(DesiredSize)。

2. 排列:确定子元素的位置,大小。

1. 测量:

在UIElement中,定义了两个方法:Measure() 和 MeasureCore()。在FrameworkElement中还定义了MeasureOverride()方法。他们的工作方式是:

1). 父级元素调用子元素实例的Measure(),此时Measure()方法会做一堆的逻辑检查,然后调用其MeasureCore()方法。在MeasureCore()方法中,真正的去计算元素的期望尺寸(DesiredSize)。在UIElement类中,MeasureCore()返回的尺寸永远是(0,0)(请看微软的源代码:UIElement.MeasureCore())。这意味着如果子元素是一个UIElement的实例,则其期望尺寸永远会是(0,0)。

2).FrameworkElement继承了UIElement,重写并密封了MeasureCore()方法。因此,当调用子元素的Measure()方法时,子元素的Measure()方法实际上会调用重写后的MeasureCore()方法,以计算Frame元素的期望尺寸。

3).在FrameworkElement重写的MeasureCore()方法中,实际上又会调用MeasureOverride()方法。MeasureOverride()方法是一个虚方法,可供用户重写,以实现用户自己的测量逻辑,同时还能保证相关的检查不被遗漏。

2. 排列:

排列的工作方式,与“测量”是一样的。不再赘述。

由此可知,要实现自己的布局,就有三个选择:

1)继承自UIElement,并重写MeasureCore()和ArrangeCore()方法(并添加需要的属性等)。此路径难度很大。

2)继承自FrameworkElement,并重写MeasureOverride()和ArrangeOverride()方法(并添加需要的属性等)。此路径难度不小。

3)继承自Panel,并重写MeasureOverride()和ArrageOverride()方法(并添加需要的属性等)。此路径难度最小。因为Panel类已经定义了很多相关的属性、方法可共用户使用。

参数的意义

  在MeasureOverride()和ArrageOverride()方法中,两个参数及返回值的意义是不同的:

  MeasureOverride():该方法的参数availableSize,表示父元素,可以用来布局该元素的可用空间。此参数由WPF布局系统确定;返回值为该元素期望的尺寸(DesiredSize)。自定义面板中,如果用户不重写该方法(实现自己的测量方式),则会返回一个(0,0)的期望尺寸。

  ArrangeOverride():该方法的参数finalSize,表示父元素,最终分配给该元素,用于排列子元素的尺寸。当WPF的布局系统测量完各控件的尺寸后,开始依次排列子元素。当排列到本元素时,剩余的空间可能与MeasureOverride()方法中的availableSize不同。因此此时传递进来的,是可以用于排列子元素的最终的空间大小。在ArrangeOverride()方法中的,可以按照自己的逻辑去排列子元素。并最终返回一个尺寸值(Size类型),告诉WPF的布局系统,该元素最终使用了多少空间。

以上为自己学习并理解的。如有错误,还请指正。

WPF学习 - 自定义Panel的更多相关文章

  1. WPF教程十三:自定义控件进阶可视化状态与自定义Panel

    如果你敲了上一篇的代码,经过上一篇各种问题的蹂躏,我相信自定义控件基础部分其实已经了解的七七八八了.那么我们开始进阶,现在这篇讲的才是真正会用到的核心的东西.简化你的代码.给你提供更多的可能,掌握了这 ...

  2. WPF: FishEyePanel/FanPanel - 自定义Panel

    原文:WPF: FishEyePanel/FanPanel - 自定义Panel 原文来自CodeProject,主要介绍如何创建自定义的Panel,如同Grid和StackPanel. 1) Int ...

  3. WPF学习之路初识

    WPF学习之路初识   WPF 介绍 .NET Framework 4 .NET Framework 3.5 .NET Framework 3.0 Windows Presentation Found ...

  4. 我的面板我做主 -- 淘宝UWP中自定义Panel的实现

    在Windows10 UWP开发平台上内置的XMAL布局面板包括RelativePanel.StackPanel.Grid.VariableSizedWrapGrid 和 Canvas.在开发淘宝UW ...

  5. WPF学习之资源-Resources

    WPF学习之资源-Resources WPF通过资源来保存一些可以被重复利用的样式,对象定义以及一些传统的资源如二进制数据,图片等等,而在其支持上也更能体现出这些资源定义的优越性.比如通过Resour ...

  6. wpf学习

    http://www.jikexueyuan.com/course/1231_3.html?ss=1 WPF入门教程系列二——Application介绍 http://www.cnblogs.com/ ...

  7. WPF学习笔记-用Expression Design制作矢量图然后导出为XAML

    WPF学习笔记-用Expression Design制作矢量图然后导出为XAML 第一次用Windows live writer写东西,感觉不错,哈哈~~ 1.在白纸上完全凭感觉,想象来画图难度很大, ...

  8. UWP开发入门(五)——自定义Panel

    各位好,终于讲到自定义Panel了.当系统自带的几个Panel比如Gird,StackPanel,RelativePanel不能满足我们的特定要求时(其实不常见啦),自定义Panel就显得非常必要,而 ...

  9. WPF学习资源整理

    WPF(WindowsPresentation Foundation)是微软推出的基于Windows Vista的用户界面框架,属于.NET Framework 3.0的一部分.它提供了统一的编程模型 ...

  10. 命令——WPF学习之深入浅出

    WPF学习之深入浅出话命令   WPF为我们准备了完善的命令系统,你可能会问:“有了路由事件为什么还需要命令系统呢?”.事件的作用是发布.传播一些消息,消息传达到了接收者,事件的指令也就算完成了,至于 ...

随机推荐

  1. 为什么 GPU 能够极大地提高仿真速度?

    这里的提速主要是针对时域电磁算法的.因为时域算法的蛙跳推进模式仅对大量存放在固定 位置的数据进行完全相同的且是简单的操作(移位相加),这正是 GPU 这类众核 SIMD 架构所进行的运算,即 ALU ...

  2. Kali系统 连接 Xshell

    1> 进入kali系统,修改ssh配置文件:vi /etc/ssh/sshd_config 2> 将PermitRootLogin without-password修改为:PermitRo ...

  3. 【原创】浅谈EtherCAT主站EOE(上)-EOE网络

    这篇文章的标题虽然是关于EtherCAT EOE,但其实主要内容是关于整个EOE网络结构,属于计算机网络原理.而EtherCAT EoE只是简单介绍,并不是文章的重点.需要注意的是,我们的描述主要基于 ...

  4. 案例分享-被*队友的mybatis蠢哭的一天

    昨晚加班的时候被队友拉着看一个mybatis的问题,耗费了我一个小时时间,最后差点没被我打死,实在是觉得滑稽,今天回家写下来跟大伙分享一下. 问题现象 Invalid bound statement ...

  5. 【技术积累】Java中的JVM【一】

    什么是JVM JVM英文全称为Java Virtual Machine,中文意为Java虚拟机.JVM是一种能够执行Java语言编写的程序的虚拟机器,它首次作为Java语言的一部分,后来又被移植到了许 ...

  6. 【PAT】1001 害死人不偿命的(3n+1)猜想(动态更新)

    卡拉兹(Callatz)猜想: 对任何一个正整数 n,如果它是偶数,那么把它砍掉一半:如果它是奇数,那么把 ( 砍掉一半.这样一直反复砍下去,最后一定在某一步得到 n=1.卡拉兹在 1950 年的世界 ...

  7. Linux下NFS服务配置

    NFS(Network File System)即网络文件系统,是FreeBSD支持的文件系统中的一种,它允许网络中的计算机之间通过TCP/IP网络共享资源.在NFS的应用中,本地NFS的客户端应用可 ...

  8. 记一次 .NET 某埋线管理系统 崩溃分析

    一:背景 1. 讲故事 经常有朋友跟我反馈,说看你的文章就像看天书一样,有没有一些简单入手的dump 让我们先找找感觉,哈哈,今天就给大家带来一篇入门级的案例,这里的入门是从 WinDbg 的角度来阐 ...

  9. 简约版八股文(day1)

    Java基础 面向对象的三大基本特征 封装:将一些数据和对这些数据的操作封装在一起,形成一个独立的实体.隐藏内部的操作细节,并向外提供一些接口,来暴露对象的功能. 继承:继承是指子类继承父类,子类获得 ...

  10. base64详解

    base64详解 前置知识 位与字节 二进制系统中,每个0或1就是一个位(bit,比特),也叫存储单元,位是数据存储的最小单位. 其中8bit就称为一个字节(Byte). 1B=8位 位运算 与运算: ...