窗口系统,这个名词听起来也许很普通,事实上我们也的确每天都在接触它。早在Microsoft Windows 这个世界级操作系统的最初版本发布之前,“窗口”这个概念就被引入了图形界面操作系统。这个名词生动而形象地描述了用户和操作系统交互的最小单位。用户通过窗口执行操作,对操作系统下达指令,操作系统将反馈以图形的形式,通过窗口输出到屏幕供用户检阅。
Xerox Star,可层叠窗口,1981年
几乎所有的图形用户界面元素,无一例外均可以用各种类型的窗口加以描述。换言之,窗口系统是图形界面操作系统的重要组成元素,是应用程序图形用户界面的底层基础。我们看到的应用程序五花八门的图形界面,本质上都是操作系统窗口的拼搭组合。
需要注意的是,上面提到的“窗口”的概念可能和我们平常理解的,操作系统中可以随意拖动和关闭的交互实体有所不同。接下来我们的讨论将围绕窗口系统这层内容来深入探讨。
基本概念
在窗口系统中,窗口(Window)指代屏幕上的一块可绘制、可交互的矩形区域。通俗来讲,窗口应具备以下特征:
可接受输入:即允许用户交互。交互的方式有很多种,包括但不限于通过鼠标点击、获取焦点后键盘输入、触摸等;
可输出图像:窗口的范围即为操作系统输出绘制图形的指定区域;
占据矩形区域:通常通过左上角横纵坐标、宽度和高度这四个参数描述。窗口可以隐藏甚至(部分)移出屏幕外,多窗口之间还可以相互重叠。
事实上,在窗口系统中,并没有边框和标题栏这些概念。窗口只是一块“可绘制,可交互”的矩形区域而已。至于如何为窗口添加“可交互”的标题栏等元素,均取决于基于该窗口系统构建的应用程序。应用程序完全可以将标题放在窗口正中央,或规定鼠标点击拖动窗口的某一块指定区域才可以移动窗体。换言之,这些都不是“窗口系统”这一层级所讨论的问题。
例如下面是在Wayland窗口系统下创建的一个最基础的窗口。它不接受交互,既无法拖动和缩放,同时在其显示范围内,仅仅简单的输出黑色。
Wayland经典范例:The Black Square
事实上,为窗口配备标题栏和边框是更上一层的中间件,通常称之为窗口管理器(Window Manager)。如Windows下的dwm.exe、Linux下的i3wm、Xfce、KDE等。窗口管理器可以赋予窗口一些基础行为和外观。
而相应地,窗口系统即管理窗口的系统。Linux下主流的窗口系统是X Window System,以及其年轻的竞争者Wayland。在Windows下,窗口系统是内置的,或者我们可以说Windows本身就是窗口系统,它通过窗口句柄(HWND数据类型)来管理窗口。
窗口树
前面已经提到,在窗口系统的语境中,窗口并不是我们平常所接触的可以拖来拖去的窗口,仅仅是指代屏幕上的一块可绘制可交互的矩形区域而已。我们平常所熟悉的按钮、菜单、文本框等组件,在窗口系统眼里同样是"窗口"。
Windows下的一个普通的OK按钮,同样是一个窗口
例如常见的计算器应用程序。上面的每一个按钮,其本质上都是一个窗口。窗口与窗口之间存在树形的关系结构,每个窗口都必定有其父窗口,同时也可以拥有零个或多个子窗口。
一般来说,当父窗口的位置发生改变时,内部子窗口的位置也会发生相应改变,但子窗口和父窗口的相对位置不会变化。这正是我们移动计算器时,内部的按钮也会跟着移动;同时,子窗口的绘制范围也不允许超出父窗口的范围。
下图即展示了一个Ubuntu下计算器应用的大致的窗口树结构:
Ubuntu 计算器
另外,对于最上层的窗口(如上图中顶层的“计算器”节点),事实上也存在父窗口,这个父窗口就是窗口系统提供的所谓根窗口。例如,在X窗口系统中,通过RootWindow宏可以获取当前根窗口。而直接以根窗口为父窗口的窗口,一般称之为顶层窗口。顶层窗口和其内部子窗口共同构成了我们所熟悉的应用程序窗体。
输入与输出
前面介绍窗口是图形界面操作系统面向用户的交互点。那么它们具体是如何与用户交互的呢?通俗的讲,窗口可以接受“输入”,即响应来自用户或操作系统的事件;窗口也可以进行“输出”,即了解如何绘制自身。
我们经常把来自用户的事件理解为“输入”。当我们使用鼠标左键点击某一个“按钮”的时候,“按钮窗口”将会接受这个输入,并产生一个类型为“鼠标按键按下”的窗口系统事件(在X 窗口系统中称为XEvent类型)对象。为方便应用程序处理,事件对象会携带一些参数,例如事件发生时鼠标的位置、事件发生的时间等。时间将被添加到事件队列中,等待进一步处理。
当然,事件生成的方式并不仅仅局限于用户输入。操作系统自身也会产生事件。例如“暴露”(Expose)事件:操作系统通常生成这类事件,表示窗口的一部分需要进行重绘。例如窗口从最小化状态恢复时,操作系统将会生成暴露事件传递给窗口,令窗口重新绘制自身。更进一步地,操作系统甚至可以指定重绘的区域而非重绘整个窗口,从而提升图形界面的显示效率。
我们举一个简单的例子,Windows 98的开始菜单:
经典的开始菜单
当我们将鼠标指针移到菜单某一项上的时候,菜单项会马上显示为高亮效果。这其实就是一次典型的重绘:菜单“窗口”接受了鼠标的“移入”(MouseEnter)事件,并让菜单项进行高亮重绘(Repaint)作为事件的响应。
小结
想要编写图形界面的应用程序,必定绕不过窗口系统。但现代的程序员有各种各样的开发工具和类库可以使用,在事先封装好的接口的帮助下,程序员基本已经不需要直接和窗口系统打交道了。
当你轻松编写了几行代码,能运行在各个不同的操作系统上,还能创建出观感几乎一致的应用程序窗体时,可能根本不会意识到,开发工具在背后究竟做了多少工作。这正体现了开发工具在快速软件开发中的重要性。遗憾的是目前国内并没有哪怕一款可用的,支持跨平台图形界面的开发工具。
为了实现基础开发工具尤其是具备图形界面支持的开发工具的全面国产化,窗口系统是合迅智灵必须要攻克的一道技术难关。