Visual->UIElement->FrameworkElement,带来更多功能的同时也带来了更多的限制
在 WPF 或 UWP 中,我们平时开发所遇到的那些 UI 控件或组件,都直接或间接继承自 Framework
。例如:Grid
、StackPanel
、Canvas
、Border
、Image
、Button
、Slider
。我们总会自然而然地认为这些控件都是有大小的,它们会在合适的位置显示自己,通常不会超出去。但是,FrameworkElement
甚至是 Control
用得久了,都开始忘记 Visual
、UIElement
带给我们的那些自由。
阅读本文将了解我们熟知的那些功能以及限制的由来,让我们站在限制之外再来审视 WPF 的可视化树,再来看清 WPF 各种控件属性的本质。
宽度和高度
如果问 Width
/Height
属性来自谁,只要在 WPF 和 UWP 里混了一点儿时间都会知道——FrameworkElement
。随着 FrameworkElement
的宽高属性一起带来的还有 ActualWidth
、ActualHeight
、MinWidth
、MinHeight
、MaxWidth
、MaxHeight
。正是这些属性的存在,让我们可以直观地给元素指定尺寸——想设置多少就设置多少。
然而……当你把宽或高设置得比父容器允许的最大宽高还要大的时候呢?我们会发现,控件被“切掉”了。
▲ 被切掉的椭圆
然而,因布局被“切掉”这一特性也是来自于 FrameworkElement
!
UIElement
布局时即便空间不够也不会故意去将超出边界的部分切掉,这一点从其源码就能得到证明:
/// <summary>
/// This method supplies an additional (to the <seealso cref="Clip"/> property) clip geometry
/// that is used to intersect Clip in case if <seealso cref="ClipToBounds"/> property is set to "true".
/// Typcally, this is a size of layout space given to the UIElement.
/// </summary>
/// <returns>Geometry to use as additional clip if ClipToBounds=true</returns>
protected virtual Geometry GetLayoutClip(Size layoutSlotSize)
{
if(ClipToBounds)
{
RectangleGeometry rect = new RectangleGeometry(new Rect(RenderSize));
rect.Freeze();
return rect;
}
else
return null;
}
只会在 ClipToBounds
设置为 true
的时候进行矩形切割。
然而 FrameworkElement
的切掉逻辑就复杂多了,鉴于有上百行,就只贴出链接 FrameworkElement.GetLayoutClip。其处理了各种布局、变换过程中的情况。
由于 FrameworkElement
的出现是为了让我们编程中像对待一个有固定尺寸的物体一样,所以也在切除上模拟了这样的空间有限的效果。
如果希望不被切掉,有两种方法修正:
- 确保布局的时候所需尺寸不大于可用尺寸(一点也不能大于,就算是
double
精度问题导致的细微偏大都不行)MeasureOverride
返回的尺寸不大于参数传入的尺寸ArrangeOverride
返回的尺寸不大于参数传入的尺寸
- 重写
GetLayoutClip
方法,并返回 null(或者写成UIElement
那样)
布局系统
提及 MeasureOverride
、ArrangeOverride
,大家都会认为这是 WPF 布局系统给我们提供的两个可供重写的方法。然而,这两个方法其实也是 FrameworkElement
才提供的。
真正布局的方法是 Measure
和 Arrange
,而可供重写的方法是 MeasureCore
、ArrangeCore
。这两组方法均来自于 UIElement
,而布局系统其实是 UIElement
引入的。
那么 FrameworkElement
做了什么呢?它密封了 MeasureCore
、ArrangeCore
这两个布局的重写方法,以便能够处理 Width
、Height
、MinWidth
、MinHeight
、MaxWidth
、MaxHeight
、Margin
这些属性对布局的影响。
你觉得 Width
、Height
属性是元素的最终宽高吗?我们在 宽度和高度 一节中已经说了不是,前面一段也说了不是——它们真的只是布局属性!然而,这真的很容易形成误解!Width``Height
属性其实和 MinWidth``MinHeight
、MaxWidth``MaxHeight
是完全一样的用途,只是在布局过程中为计算最终尺寸提供的布局限制而已。只不过 MinWidth``MinHeight
、MaxWidth``MaxHeight
用大于和小于进行尺寸的限制,而 Width``Height
用等于进行尺寸的限制。最终的尺寸依然是 ActualWidth``ActualHeight
,而这个值跟 RenderSize
其实是一个意思,因为内部获取的就是 RenderSize
。
值得注意的是,ActualWidth``ActualHeight
与 RenderSize
一样,是布局结束后才会更新的,开发中需要如果修改了属性立即获取这些值其实必然是旧的,拿这些值进行计算会造成错误的尺寸数据。
顺便吐槽一下:其实微软是喜欢用 Core
来作为子类重写方法的后缀的,比如 Freezable
、EasingFunction
都是用 Core
后缀来处理重写。Override
后缀纯属是因为 UIElement
把这个名字用了而已。
屏幕交互
UIElement
中存在着布局计算,FrameworkElement
中存在着带限制的布局计算,这很容易让人以为屏幕相关的坐标计算会存在于 UIElement
或者 FrameworkElement
中。
然而其实 UIElement
或者 FrameworkElement
只涉及到控件之间的坐标计算(TranslatePoint
),真正涉及到屏幕坐标的转换是位于 Visual
中的,典型的是这几个:
TransformToAncestor
TransformToDescendant
TransformToVisual
PointFromScreen
PointToScreen
所以其实如果希望做出非常轻量级的高性能 UI,继承自 Visual
也是一个大胆的选择。当然,真正遇到瓶颈的时候,继承自 Visual
也解决不了多少问题。
样式和模板
FrameworkElement
开始有了样式(Style
),Control
开始有了模板(Template
)。而模板极大地方便了样式定制的同时,也造成了强大的性能开销,因为本来的一个 Visual
瞬间变成了几个、几十个。一般情况下这根本不会是性能瓶颈,然而当这种控件会一次性产生几十个甚至数百个(例如表格)的时候,这种瓶颈就会非常明显。
总结容易出现理解偏差的几个点
Width
和Height
属性其实只是为布局过程中的计算进行限制而已,跟MinWidth
、MinHeight
、MaxWidth
、MaxHeight
没有区别,并不直接决定实际尺寸。- 如果发现元素布局中被切掉了,这并不是不可避免的问题;因为切掉是
FrameworkElement
为我们引入的特性,不喜欢可以随时关掉。 - 微软对于子类重写核心逻辑的方法喜欢使用
Core
后缀,布局中用了Override
只是因为名字被占用了。 Visual
就可以计算与屏幕坐标之间的转换。- 模板(
Template
)会额外产生很多个Visual
,有可能会成为性能瓶颈。
参考资料
Visual->UIElement->FrameworkElement,带来更多功能的同时也带来了更多的限制的更多相关文章
- jQuery+php+Ajax文章列表点击加载更多功能
jQuery+php+Ajax实现的一个简单实用的文章列表点击加载更多功能,点击加载更多按钮,文章列表加载更多数据,加载中有loading动画效果. js部分: <script type=&qu ...
- Android 上拉加载更多功能
前几天看了github上面的例子,参照它的实现,自己又稍微改了一点,往项目里面增加了一个上拉加载更多功能.具体的实现如下: 首先要重写ListView: import android.content. ...
- 关闭 Visual Studio 的 Browser Link 功能
最近公司弄新项目需要用 MVC,就把 IDE 升级到了 Visual Studio 2013,在开发的时候发现有好多请求一个本地49925的端口 . 很奇怪,一开始以为是 Visual Studio ...
- React-Native 之 GD (十一)加载更多功能完善 及 跳转详情页
1.加载更多功能完善 GDHome.js /** * 首页 */ import React, { Component } from 'react'; import { StyleSheet, Text ...
- (数据科学学习手札134)pyjanitor:为pandas补充更多功能
本文示例代码及文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 pandas发展了如此多年,所包含的功能已 ...
- Visual Studio 2013 Preview 新功能
先来看一下Visual Studio的版本历史: 1. Visual Studio.NET 2002 2. Visual Studio.NET 2003 3. Visual Studio.NET 20 ...
- Visual Studio Community 2013,功能完整,免费使用
http://www.infoq.com/cn/news/2014/11/VSC2013 微软刚刚宣布了.NET平台的开源计划,与此同时,它还推出了源自Visual Studio Profession ...
- 分页插件思想:pc加载更多功能和移动端下拉刷新加载数据
感觉一个人玩lol也没意思了,玩会手机,看到这个下拉刷新功能就写了这个demo! 这个demo写的比较随意,咱不能当做插件使用,基本思想是没问题的,要用就自己封装吧! 直接上代码分析下吧! 布局: & ...
- 内核调试神器SystemTap — 更多功能与原理(三)
a linux trace/probe tool. 官网:https://sourceware.org/systemtap/ 用户空间 SystemTap探测用户空间程序需要utrace的支持,3.5 ...
随机推荐
- 编译Python2.7.10
为了测试 mesos,搞了一个 centos7.1,使用最小化安装,然后自己安装了 net-tools,“开发工具”集.后来想装一下 DCOS Cli工具,结果发现 python 的 pip 不可用. ...
- Lucene 初步 之 HelloWorld
万恶的源头 HelloWorld 要完成lucene 的配置 需要几个jar包 (如果需要可以留言我私发) 创建索引API分析: 1. Directory: 类代表一个Lucene索引的位置,FSDi ...
- centos cgroup配置
centOS 6:1. 启用cgroup 查看内核是否支持cgroup功能:cat /boot/config-`uname -r` | grep -i rt_group 查看支持的子系统: ...
- javaScript实现点击按钮直接打印
很多网站都有此功能,当浏览到底部时都会有一个打印按钮,点击打印按钮就可以完成打印功能,功能非常不错,人性化,代码非常的简单. 一.只要调用window.print()函数就可以实现打印当前页面 < ...
- mysql 命令行导数据库
cd d: 然后应该会提示你已经进入D盘了,按照你数据库的地址,我的数据库是在D盘的wamp这个文件夹目录, 输入命令:cd ruanjian\mysql\bin 类似于这样,大家可以先在本 ...
- IntelliJ IDEA 左侧显示/展开类中的方法
困扰我很久的问题: project直接右键: 打开.关闭对应效果: 之前查到的都是 : 虽然也有类似的功能,但是展开的是右侧窗口中,打开的那个类的: 即使不是我想要的,但也是不错的功能!
- VS2017 MVC项目,新建控制器提示未能加载文件或程序集“Dapper.Contrib解决方法
VS2017中MVC项目中,右键新建控制器时,提示 未能加载文件或程序集“Dapper.Contrib, Version=1.50.0.0, Culture=neutral, PublicKeyTok ...
- leetcode算法总结
算法思想 二分查找 贪心思想 双指针 排序 快速选择 堆排序 桶排序 搜索 BFS DFS Backtracking 分治 动态规划 分割整数 矩阵路径 斐波那契数列 最长递增子序列 最长公共子系列 ...
- JAVA8新特性简单总结
速度更快代码更少强大的StreamAPI便于并行最大化的减少空指针异常Lambda表达式<具体做什么事>和函数式接口Lambda表达式(也叫做闭包)它允许我们将一个函数当作方法的参数(传递 ...
- [nodejs]npm国内npm安装nodejs modules终极解决方案
此方案用于设置代理和修改镜像地址都不能解决问题使用 1.npm root 确认node模块的根文件夹,全局要加-g. osx同样是此命令,先清除缓存. npm cache clean C:\Users ...