[WPF自定义控件]Window(窗体)的UI元素及行为
1. 前言
本来打算写一篇《自定义Window》的文章,但写着写着发觉内容太多,所以还是把使用WindowChrome自定义Window需要用到的部分基础知识独立出来,于是就形成了这篇文章。
无论是桌面编程还是日常使用,Window(窗体)都是最常接触的UI元素之一,既然Window这么重要那么多了解一些也没有坏处。
2.标准Window
这篇文章主要讨论标准的Window,不包括奇形怪状的无边框、非矩形Window,即只讨论WindowStyle="SingleBorderWindow"
(默认值)的Window。
一个标准的Window的基本构成如上图所示,它主要由非工作区(non-client area)和工作区(client area)组成。上图中中间白色的部分即client area,在WPF对应下面代码中注释的部分:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SDKSample.MarkupAndCodeBehindWindow">
<!-- Client area (for content) -->
</Window>
标准window中除client之外的部分称为non-client area,通常称之为chrome,它提供了提供了标准的窗口功能和行为,具体包含以下部分:
- 边框
- 阴影
- 标题栏
- Icon
- 标题
- SystemMenu
最小化
、最大化
和还原
按钮关闭
按钮- 大小调整手柄
边框
标准Window肯定会有边框的,在Windows 7上因为有Aero效果所以看上去很棒,现在偶尔用用Windows 7还是觉得很漂亮。但就如上图所示圆角不够平滑,如果电脑不是高分屏的话应该会更明显,例如这样:
因为圆角总是很难处理所以我不是很喜欢圆角的设计。
Windows 10的边框就时髦很多,如果在“个性化>颜色”设置页面取消标题栏和窗口边框
,看上去就像是无边框(其实是把边框做成白色的了):
阴影
阴影用于体现UI的深度,属于装饰元素,Windows 的窗体通常都带有阴影,除非在“系统属性->高级->性能选项->视觉效果”里关闭“在窗口下显示阴影”选项。
标题栏
只要是标准的Window就应该有标题栏。一些浏览器看上去没有标题栏;当Fluent Design System出来后流行将内容扩展到标题栏,越来越多的应用看上去没有了标题栏。其实标题栏总是存在,能拖动,点击右键会弹出SystemMenu
,并且最右边有关闭
按钮的部分就是标题栏了。
双击标题栏还可以执行最大化
或还原
操作。
有一点细节可能不太容易注意到,当Window处于最大化状态时标题栏比较矮。在100% DPI时标题栏的高度为30像素,最大化时变为22像素,这时候右上角的几个按钮缩小了,其它元素的Margin也减少了一些。
Icon
Icon是指标题栏左边的窗体图标,这倒真的很常消失。在100% DPI的情况下它是个16 * 16 像素的图片。
顺便一提双击Icon会关闭Window,但我想一般都会用右边的关闭
按钮的吧。
标题
标准Window的标题位于Icon右边。如果Window边框是深色,标题文字颜色为白色;反之则为黑色。
SystemMenu
在标题栏上点击鼠标右键出现的ContextMenu即是SystemMenu
,它包括调整大小、移动和关闭操作。在Icon上点击鼠标左键,或者按Alt
+空格都会在标题栏左下方弹出SystemMenu
。
不过很少见到有人用SystemMenu
,我也只是用它来确定标题栏的范围而已。
最小化、最大化和还原按钮
当Window的ResizeMode
设置为NoResize
以外的值时(即CanMinimize
、CanResize
和CanResizeWithGrip
)这三个按钮才会出现,如果ResizeMode
设置为CanMinimize
则最大化
和还原
都会被禁用。
关闭按钮
因为关闭
按钮基本上一定会存在所以把它独立出来,只是ResizeMode
设置为NoResize
时关闭
按钮会比较小。在Windows 10中最大化时关闭
按钮贴着右上角,这样比较方便鼠标操作。
调整大小
当Window的ResizeMode
设置为CanResize
或CanResizeWithGrip
时Window可以使用最大化
和还原
按钮或SystemMenu
调整大小,也可以通过拖动边框调整大小。
大小调整手柄
当Window的ResizeMode
设置为CanResizeWithGrip
并且WindowState = Normal
时右下角会出现大小调整手柄,外观为组成三角形的一些点。除了让可以操作的区域变大一些,还可以用来提示Window是可以调整大小的。
拖动
有些Window会做成整个Window都可以通过拖动来改变位置,标准Window则只有标题栏可以拖动。
激活
激活或非激活的Window之间的区别主要体现在标题栏、边框及标题文字的颜色。在标题栏使用了AcrylicBrush的UWP应用还体现在非激活时AcrylicBrush变成纯色不透明的Brush。
焦点
一个Window中只有client area中的内容可以获得键盘焦点,而且tab
键只会让键盘焦点在Window的内容中循环。当一个Window从非激活状态会到激活状态,之前获得键盘焦点的元素将重新获得键盘焦点。
动画
Window在最大化、最小化、还原有缩放的动画,这个动画可以清晰地指示Window的最终位置。当任务栏内容很多的时候,向下缩放到任务栏对应位置的动画尤其重要。
FlashWindow
如果一个Window设置了Owner并且以ShowDialog的方式打开,点击它的Owner将对这个Window调用FlashWindowEx功能,即闪烁几下,并且还有提示音。除了这种方式还可以用编程的方式调用FlashWindow功能。
Window的大小
最后要说的是Window的大小。Window的实际大小并不是表面上看到的大小。在Windows 10,以1920 * 1080 分辨率,100% DPI为例,打开以下XAML定义的一个Window:
<Window x:Class="WpfApp1.MainWindow"
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"
Title="MainWindow"
Height="600"
Width="800">
<Grid x:Name="LayoutRoot">
</Grid>
</Window>
通过实时可视化树可以看到,Window本身的小时确实是800 * 600,但LayoutRoot的大小只有784 * 561。将Window最大化后Window的大小变为1936 * 1066,而LayoutRoot的大小变为1920 * 1027。
如果将Window设置为启动位置在左上角:
WindowStartupLocation="Manual"
Top="0"
Left="0"
结果它并不会完全贴着左上角,而是左边有一点空间,上面没有。
通过Inspect看到的Window如下,黄色边框为它的实际范围:
可以看到系统理解的Window范围和我们看到的不同,这是Window设计的问题,有几个值用于计算chrome的尺寸:
属性 | 值(像素) | 描述 |
---|---|---|
SM_CXFRAME/SM_CYFRAME | 4 | The thickness of the sizing border around the perimeter of a window that can be resized, in pixels. SM_CXSIZEFRAME is the width of the horizontal border, and SM_CYSIZEFRAME is the height of the vertical border.This value is the same as SM_CXFRAME. |
SM_CXPADDEDBORDER | 4 | The amount of border padding for captioned windows, in pixels.Windows XP/2000: This value is not supported. |
SM_CYCAPTION | 23 | The height of a caption area, in pixels. |
在有标题的标准Window,chrome的顶部尺寸为SM_CYFRAME + SM_CXPADDEDBORDER + SM_CYCAPTION = 31,左右两边尺寸为SM_CXFRAME + SM_CXPADDEDBORDER = 8,底部尺寸为SM_CYFRAME + SM_CXPADDEDBORDER = 8。
最大化情况下Border和ResizeBorder都超出屏幕范围而且被隐藏了,所以Window的尺寸会超过显示器工作区的尺寸,这时候标题栏也会相应地变矮。在Windows 10,系统认为Window有4像素的ResizeBorder,但因为Windows 10是窄边框设计,而且在普通状态下和最大化状态下的标题栏高度还不一样,导致用UISpy观察Window和我们看到的Window不一致,也常常导致位置计算上的问题。
注意,上面的尺寸计算都是基于100 % DPI,在不同DPI的情况下还需要将DPI的值纳入计算。
3. 结语
标准Window的外观和行为基本上已经列出来了(其实还有很多,例如按住标题栏抖一抖可以缩小其它所有窗口这种功能,但这些不影响自定义Window的行为就不一一列出了),更多的内容请见下面给出的参考链接。
顺便一提设置SizeToContent="WidthAndHeight"
并且 WindowState="Maximized"
的Window行为很怪异,最好不要这样设置。
4. 参考
WPF Windows 概述 _ Microsoft Docs
SystemParameters Class (System.Windows) Microsoft Docs
[WPF自定义控件]Window(窗体)的UI元素及行为的更多相关文章
- [WPF自定义控件]?Window(窗体)的UI元素及行为
原文:[WPF自定义控件]?Window(窗体)的UI元素及行为 1. 前言 本来打算写一篇<自定义Window>的文章,但写着写着发觉内容太多,所以还是把使用WindowChrome自定 ...
- 在WPF中减少逻辑与UI元素的耦合
原文:在WPF中减少逻辑与UI元素的耦合 在WPF中减少逻辑与UI元素的耦合 周银辉 1, 避免在逻辑中引用界面元素,别把后台数据强加给UI 一个糟糕的案例 比如说主界 ...
- Wpf从资源中重用UI元素
在我的界面上有几个选项卡,每个选项卡中都有下面的元素: <StackPanel Orientation="Horizontal"> <Button Content ...
- WPF 中使用附加属性,将任意 UI 元素或控件裁剪成圆形(椭圆)
不知从什么时候开始,头像流行使用圆形了,于是各个平台开始追逐显示圆形裁剪图像的技术.WPF 作为一个优秀的 UI 框架,当然有其内建的机制支持这种圆形裁剪. 不过,内建的机制仅支持画刷,而如果被裁剪的 ...
- WPF FindName()查找命名注册的元素
一.查找xaml中命名注册的元素 <Button x:Name="btn1" Content="显示内容" HorizontalAlignment=&qu ...
- 拒绝卡顿——在WPF中使用多线程更新UI
原文:拒绝卡顿--在WPF中使用多线程更新UI 有经验的程序员们都知道:不能在UI线程上进行耗时操作,那样会造成界面卡顿,如下就是一个简单的示例: public partial class MainW ...
- WPF中不规则窗体与WebBrowser控件的兼容问题解决办法
原文:WPF中不规则窗体与WebBrowser控件的兼容问题解决办法 引言 这几天受委托开发一个网络电视项目,要求初步先使用内嵌网页形式实现视频播放和选单,以后再考虑将网页中的所有功能整合进桌面程序. ...
- WPF教程十四:了解元素的渲染OnRender()如何使用
上一篇分析了WPF元素中布局系统的MeasureOverride()和ArrangeOverride()方法.本节将进一步深入分析和研究元素如何渲染它们自身. 大多数WPF元素通过组合方式创建可视化外 ...
- CSharpGL(6)在OpenGL中绘制UI元素
CSharpGL(6)在OpenGL中绘制UI元素 2016-08-13 由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了.CSharpGL源码中包含10多个独立的Demo,更适合入 ...
随机推荐
- 02.第二章_C++ Primer学习笔记_变量和基本类型
2.1 基本内置类型 2.1.1 算术类型 算术类型包括两类:整型和浮点型 2.2 变量 2.3 复合类型 2.4 const限定符 2.5 处理类型 2.6 自定义数据结构
- SecureCRT中vi或vim编辑器显示中文乱码问题
vi ~/.vimrc //新建文件 syntax on set showmode set autowrite set number set encoding=utf-8 fileencodings ...
- c/s架构搭建
1.socket(套接字) Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接 ...
- hdu4003/蓝桥杯 金属采集
思路: 树形dp + 分组背包dp. 参考https://www.cnblogs.com/kuangbin/archive/2012/08/29/2661928.html 实现: #include & ...
- cordova安装方法
安装cordova之前需要先安装node.js和npm 然后在命令行运行:npm install -g cordova即可安装到最新版本的cordova 如果要安装指定版本,命令为:npm insta ...
- Quartz使用一 通过getJobDataMap传递数据
Quartz定时器使用比较广泛,介绍一点简单的使用 上代码:定义一个Job,执行具体的任务 package org.tonny.quartz; import java.text.SimpleDateF ...
- pylint安装失败的解决方法
原文链接http://www.cnblogs.com/Loonger/p/7815335.html 使用命令pip3 install pylint安装pylint是出现错误.查了一圈也找不到答案.仔细 ...
- (三)docker 的启动,重启,关闭命令
docker启动命令,docker重启命令,docker关闭命令 启动 systemctl start docker 守护进程重启 sudo systemctl daemon-reload 重 ...
- 指针-AC自动机
大家都不喜欢指针,但是这个AC自动机仿佛不用不行…… 先引用我最喜欢的话:“AC自动机,不是自动AC的机器.” 如果写不好还可能一直WA AC自动机是KMP与Trie树的完美结合,适用于多字符串匹配, ...
- vscode 用户代码片段 vue初始化模板 Snippet #新加入开头注释 自动生成文件名 开发日期时间等内容
vue文件模板 模板变量 https://code.visualstudio.com/docs/editor/userdefinedsnippets#_variables vue.json { // ...