原文:[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以外的值时(即CanMinimizeCanResizeCanResizeWithGrip)这三个按钮才会出现,如果ResizeMode设置为CanMinimize最大化还原都会被禁用。

关闭按钮

因为关闭按钮基本上一定会存在所以把它独立出来,只是ResizeMode设置为NoResize关闭按钮会比较小。在Windows 10中最大化时关闭按钮贴着右上角,这样比较方便鼠标操作。

调整大小

当Window的ResizeMode设置为CanResizeCanResizeWithGrip时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

对话框概述 _ Microsoft Docs

SystemParameters Class (System.Windows) Microsoft Docs

[WPF自定义控件]?Window(窗体)的UI元素及行为的更多相关文章

  1. [WPF自定义控件]Window(窗体)的UI元素及行为

    1. 前言 本来打算写一篇<自定义Window>的文章,但写着写着发觉内容太多,所以还是把使用WindowChrome自定义Window需要用到的部分基础知识独立出来,于是就形成了这篇文章 ...

  2. 在WPF中减少逻辑与UI元素的耦合

    原文:在WPF中减少逻辑与UI元素的耦合             在WPF中减少逻辑与UI元素的耦合 周银辉 1,    避免在逻辑中引用界面元素,别把后台数据强加给UI  一个糟糕的案例 比如说主界 ...

  3. Wpf从资源中重用UI元素

    在我的界面上有几个选项卡,每个选项卡中都有下面的元素: <StackPanel Orientation="Horizontal"> <Button Content ...

  4. WPF移动Window窗体(鼠标点击左键移动窗体自定义行为)

    XAML代码部分:1.引用System.Windows.Interactivity 2.为指定的控件添加一个拖动的行为 3.很简单的了解行为的作用和用法 <Window xmlns=" ...

  5. WPF自定义Window窗体样式

    资源文件代码: <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation ...

  6. 【WPF】Window窗体禁用最大化/最小化按钮

    需求:弹窗的右上角不显示最大化.最小化按钮. 在< Window >节点添加属性以下属性即可: ResizeMode="NoResize" 或者直接在Propertie ...

  7. [WPF 自定义控件]自定义控件库系列文章

    Kino.Toolkit.Wpf Kino.Toolkit.Wpf是一组简单实用的WPF控件与工具,用于介绍自定义控件的入门.相关博客地址如下: 开始一个自定义控件库项目 介绍开始一个自定义控件库项目 ...

  8. [WPF自定义控件库]使用WindowChrome的问题

    1. 前言 上一篇文章介绍了使用WindowChrome自定义Window,实际使用下来总有各种各样的问题,这些问题大部分都不影响使用,可能正是因为不影响使用所以一直没得到修复(也有可能别人根本不觉得 ...

  9. 获取当前进程(程序)主窗体句柄并设置wpf的父窗体为此句柄

    有时候在c++调用wpf控件的时候,wpf控件想自己显示窗体,但需要设置owner属性.迂回解决办法是设置wpf的window窗体的父窗体为进程的句柄. 1.获取当前进程id int id = Pro ...

随机推荐

  1. Delphi StringGrid常用属性和常用操作

    StringGrid组件用于建立显示字符串的网格,与电子表格相似.它可使表格中的字符串和相关对象操作简单化.StringGrid组件提供了许多可控制网格外观念的属性,以及利用表格的结构响应用户操作的事 ...

  2. 【NOI2019模拟2019.7.1】为了部落 (生成森林计数,动态规划)

    Description: \(1<=n<=1e9,1<=m,k<=100\) 模数不是质数. 题解: 先选m个点,最后答案乘上\(C_{n}^m\). 不妨枚举m个点的度数和D ...

  3. Quick BI 支持多种数据源进行多维分析

    一.摘要 随着互联网的高速发展,数据量爆发式增长的同时,数据的存储形式也开始呈现出多样性,有结构化存储,如 Mysql, Oracle, SQLServer 等,半结构化甚至非结构化存储,如HBase ...

  4. 38 ubuntu/windows双系统安装

    0 引言 (1)针对bios 和 uefi引导,安装方式略有不同. (2)针对nvidia显卡,在安装时需要特殊设置. 1 EasyBCD安装方式介绍-适用于bios引导方式 参考百度经验贴安装即可, ...

  5. NX二次开发-对话框解锁UF_UI_unlock_ug_access

    VC/MFC调用UG Dialog要进入加锁状态 加锁 UF_UI_lock_ug_access ( UF_UI_FROM_CUSTOM ); 此处为UF_UI_select的函数 解锁 UF_UI_ ...

  6. csp-s模拟测试96

    csp-s模拟测试96 $T1$一眼慢速乘,$T2$稍证一手最优性尝试用神奇数据结构优化,无果,弃.$T3$暴力+信仰. 100 03:16:38 95 03:16:56 35 03:17:10 23 ...

  7. 6.RabbitMQ Linux安装

    RabbitMQ在Linux上安装,需要很多依赖库,如何不能解决依赖库德版本问题,可能会比较麻烦,最好结合Yum进行安装,我这里使用的Linux环境是64位CentOS6.2 ,使用Yum源是阿里云的 ...

  8. AtCoder ABC 126F XOR Matching

    题目链接:https://atcoder.jp/contests/abc126/tasks/abc126_f 题目大意 给定两个整数 M 和 K ,用小于 2M 的的所有自然数,每个两个,用这些数排成 ...

  9. 使用Pyppeteer进行gmail模拟登录

    import asyncio import time from pyppeteer import launch async def gmailLogin(username, password, url ...

  10. POJ 3237 /// 树链剖分 线段树区间修改(*-1)

    题目大意: 给定树的N个结点 编号为1到N 给定N-1条边的边权. 三种操作: CHANGE k w:将第 k 条边的权值改成 w. NEGATE x y:将x到y的路径上所有边的权值乘 -1. QU ...