视窗

包含某種用戶界面的可視區域

视窗是在用户界面上的一个可见的范围。视窗一般都是长方形的。它包含着各种控件,是用作输入和输出的界面。虽然视窗一般用于图形用户界面,但又有时用于命令行界面的。而一个使用视窗为主要用户界面的系统则称为视窗系统。这个想法源于施乐帕洛阿尔托研究中心

Window
图形用户界面GNOME上的两个程序窗口,其中一个窗口与另一个的一部分重叠。(图为Ubuntu操作系统

在视窗系统上,视窗一般被画成一些二维物件,被安置在桌面之上。大多数的视窗都可以随用户的意愿而更改大小、移动、隐藏、恢复和关闭。当两个视窗重叠的时候,就如日常生活中一样,其中一个要位于另一个之上,而下方的则会有些地方被上方个视窗所遮盖。而视窗系统中管理这个操作的部分,则叫做窗口管理器

视窗是数种图形用户界面中的一种重要的widget。当中有VMSDEC WindowsGNU/Unix-like系统的X窗口系统Microsoft WindowsOpen Windows

历史

 
GTK+的文件“另存为……”对话框

这个概念是由斯坦福大学研究所英语SRI International(由 道格拉斯·恩格尔巴特 领导)开发实现的。[1] 他们最早的系统支持多窗口,但没明显的方法显示它们的分界线(比如窗口边框、标题栏等)。[2]

研究在 Xerox 公司的 Palo Alto 研究中心 / PARC (由 艾伦·凯领导)中继续。他们使用了重叠的窗口。[3]

在1980年代,PARC 提出术语"WIMP",分别代表了窗口、图标、菜单、指针。

那时Apple与PARC进行了简单的合作。Apple 根据 PARC 的界面开发了自己的新界面。它首次应用是在 Apple的 Lisa上,此后用于 Macintosh 电脑。与此同时,Microsoft在为"Mac"开发办公软件。之后他们在 Apple 系统的基础上开发了自己的窗口系统。[2]

子类化与超类化

窗口子类化是对一个窗口实例通过GetWindowLong和SetWindowLong将窗口过程地址修改为一个新函数地址,新的窗口过程函数处理那些感兴趣的消息,将其它消息传递给原窗口过程。即实例子类化。在某控件已经创建的情况下,为了获得窗口消息,必须子类化它。子类化不需要创建一个完整的新窗口类,只需要拦截单个窗口。

窗口超类化是一个已存在的窗口类(WNDCLASS或WNDCLASSEX),改变窗口类的特征。仅影响窗口类新创建的窗口实例,不会影响窗口过程地址修改前已经创建的窗口。超类化只能改变用户自创的窗口的特性,不能用于系统创建的窗口(如对话框上的标准按钮)。超类话过程示例如下:

WNDCLASSEX wc;
wc.cbsize=sizeof(wc);
GetClassInfoEx(hinst,"XXXX",&wc); //hinst---定义窗口类XXXX的模块的句柄,如为系统定义的窗口类(如: Edit,Button)则hinst=NULL
wc.lpszClassName="YYYY";//必须改变窗口类的名字
wc.hbrBackground=CreateSolidBrush(RGB(0,0,0));
wc.lpfnWndProc=NewWndProc;//改变窗口过程
RegisterClassEx(&wc);

MFC的子类化与超类化

MFC的CWindowImplBaseT::SubclassWindow用于动态地把一个窗口实例绑定到当前的CWnd对象,消息循环首先调用CWnd对象的窗口过程,传递给基类的消息将被默认的窗口过程处理。CWnd::SubclassDlgItem用于把对话框的一个控件子类化绑定到一个基于CWnd的类对象。CWindowImplBaseT::UnsubclassWindow恢复一个被子类化的窗口。

模板类CWindowImplBaseT提供一个数据成员WNDPROC m_pfnSuperWindowProc并且初始化为 ::DefWindowProc.然而在窗口超类化处理时它存储了已注册窗口类的窗口过程,在窗口子类化时它保存窗口实例句柄原有的窗口过程。

CWnd::CreateEx在创建窗口前调用SetWindowsHookEx函数安装了一个钩子函数_AfxCbtFilterHook。窗口刚创建好,钩子函数_AfxCbtFilterHook就被调用。_AfxCbtFilterHook调用SetWindowLong将窗口过程替换为AfxWndProcBase,并将SetWindowLong返回的原窗口地址保存到成员变量oldWndProc。可见,通过CWnd::CreateEx创建的所有窗口都会被子类化。在DefWindowProc函数中,消息会传给子类化时保存的原窗口地址oldWndProc。

ATL提供DECLARE_WND_SUPERCLASS来支持超类化。

Windows窗口类型

使用Windows API函数CreateWindowEx,可以指定创建的窗口的类型:

  • 层叠窗口 Overlapped Window:一种顶层(top-level)窗口,作为应用程式的主窗口。有标题条、边界与客户区,可以有菜单、最大最小化按钮、滚动条、可改变窗体尺寸的边界(sizing border)。使用CreateWindowEx函数指定WS_OVERLAPPED 或 WS_OVERLAPPEDWINDOW风格。
  • 弹出窗口 Pop-up Window:另一种顶层窗口,用于对话框、MessageBox,以及其他出现在应用程式主窗口之外的临时窗口。标题条可选,除此以外与层叠窗口相同。使用CreateWindowEx函数,指定WS_POPUP风格。如果需要标题条,指出WS_CAPTION风格。使用WS_POPUPWINDOW风格有border与window菜单。弹出窗口可以拥有其他顶层窗口;顶层窗口也可以拥有弹出窗口;前述二者可以同时成立。弹出窗口总是有WS_CLIPSIBLINGS风格。弹出窗口的尺寸或定位参数不能使用CW_USEDEFAULT,否则尺寸、定位被设为0。
  • 顶层窗口 top-level window:就是“非Child window”。其parent恒定是Desktop。总是有WS_CLIPSIBLINGS风格。
  • 子窗口 Child Window:出现在父窗口(parent window)的客户区中,超出的部分不可见。子窗口一定有Parent,一定没有Owner。非子窗口的Parent一定是桌面,它们不一定有Owner。典型用于把父窗口的客户区分割成一些子区域。使用CreateWindowEx函数指定WS_CHILD风格。父窗口可以是Overlapped、Pop-up、其他子窗口。子窗口的唯一必要特性是有客户区;不能有菜单;其他特性可选。定位子窗口时总是用父窗口客户区左上角坐标系。Destroyed、Hidden、Moved、Shown等窗口操作与父窗口一致。父窗口如果指定了WS_CLIPCHILDREN风格,则父窗口不能在子窗口上绘制;否则重绘父窗口会覆盖子窗口的内容。兄弟子窗口(sibling window)可以在彼此重叠的客户区上绘制,除非指定了WS_CLIPSIBLINGS风格,则不论子窗口之间z-order,与其他子窗口重叠区域不被绘制。不能使用CW_USEDEFAULT,否则将没有尺寸、没有定位位置。子窗口的输入消息直接发给子窗口,并不传递给父窗口。但子窗口被函数EnableWindow 禁止时,系统把子窗口的输入消息发给父窗口。在子窗口上点击滑鼠,子窗口收到WM_LMOUSEBUTTONDOWN消息;如果主窗口没有键盘输入焦点(focus),则主窗口获得输入焦点(WM_SETFOCUS);如果兄弟子窗口有输入焦点,则当前子窗口获得输入焦点。子窗口有独一无二整数标识符。应用程式通过空间的整数标识符给它发消息。控件给父窗口发送notification消息时,用整数标识符来表示自身。
  • Layered Window:带有WS_EX_LAYERED风格的窗口就是分层窗口,用于实现异形窗口和窗口整体透明。异形窗口要把控件的透明色和窗口后面的图像进行融合,需要用UpdateLayeredWindow函数来绘制。窗口整体透明,需要用SetLayeredWindowAttribute函数设置透明度。调用UpdateLayeredWindow函数后,WM_PAINT消息将失效;更新窗口需要自行调用UpdateLayeredWindow函数。子窗口无法应用WS_EX_LAYERED风格。子窗口如果需要是异形或者整体透明,只能去掉WS_CHILD风格,作为popup窗口,然后MOVE到一定位置,在父窗口移动的时候跟随移动。
  • Message-Only Window,允许发送/接收Windows消息,不可见,没有z-order,不能被枚举,不接收广播消息。CreateWindowEx函数的参数hWndParent为HWND_MESSAGE常量或者一个已存在的message-only窗口的句柄。对已经存在的窗口,通过函数SetParent的参数hWndNewParent为HWND_MESSAGE,把该窗体修改为message-only。为搜到message-only窗口,调用函数FindWindowEx指定参数hwndParent为HWND_MESSAGE。此外,函数FindWindowEx搜索message-only窗口与顶层层口,如果参数hwndParent与hwndChildAfter都为NULL。
  • Owner/Owned Window:Overlapped Window/Pop-up Window可以被其他Overlapped Window/Pop-up Window拥有。子窗口一定没有Owner。这种关系具有如下特性:在z-order上,Owned总是在Owner之上。当Owner被摧毁时,系统自动摧毁它的所有Owned。当Owner极小化时,Owned自动隐藏(没有了WS_VISIBLE),不显示在任务栏上;但Owned窗口自身拥有的窗口不会自动隐藏,即没有传递性。Owner隐藏(SW_HIDE),不会影响其拥有的窗口。CreateWindowEx函数创建具有WS_OVERLAPPED或WS_POPUP风格的窗口时,通过参数hwndParent指定Owner。对话框与消息框默认都是Owned。通过GetWindow函数指出GW_OWNER标志获取窗口的Owner的句柄。
    • Owner Window:Owner window不能是Child window。 Child window一定没有Owner,因此Owner window是对popup和Overlapped而言。popup和Overlapped不一定有Owner。Owner为NULL的非Child窗口可以在任务栏上出现它们的按钮。
    • Owned Window
  • top most window:具有“WS_EX_TOPMOST”属性。总是显示在其它非top most窗口的上面。同时是top most的窗口是“公平竞争”的关系。owner为top most,则其全部owned都变成了top most。child不能是top most窗口。

GetParent函数,对于子窗口返回其父窗口句柄;对于弹出窗口,返回拥有者窗口句柄;对于层叠窗口,返回0.

GetWindow函数,指出GW_OWNER参数,则可以返回弹出窗口或层叠窗口的拥有者窗口句柄。

FindWindowEx函数,用于找到指定父窗口、指定子窗口的下一个子窗口。

Windows窗口类与窗口实例的额外存储空间

WNDCLASS中cbClsExtra和cbWndExtra,分别用于指定附加于窗口类与窗口实例的额外存储空间字节大小,不能超过40字节。分别类似于C++类中的static变量与类似于C++类中的成员变量。

GetClassLong(hwnd,GCL_CBCLSEXTRA)获取的是类附加空间的大小,即cbClsExtra的值。GetClassLong(hwnd,GCL_CBWNDEXTRA)获取的是窗口附加空间的大小,即cbWndExtra的值。

使用方法:

  • 访问类附加空间的数据GetClassLong(hwnd,index),index表示访问附加空间以0开始的索引。
  • 设置类附加空间的数据SetClassLong(hwnd,index,value),index表示访问附加空间以0开始的索引,value表示要设置的值。
  • 访问窗口附加空间的数据GetWindowLong(hwnd,index),index表示访问附加空间以0开始的索引。
  • 设置窗口附加空间的数据SetWindowLong(hwnd,index,value),index表示访问附加空间以0开始的索引,value表示要设置的值。

其它相关Windows API有:GetWindowWord、GetWindowLongPtr、SetWindowWord、SetWindowLongPtr。

窗口管理器

视窗系统中管理视窗的部分被成为"窗口管理器"[4]

常见的窗口系统:

参考文献

  1. ^ Reimer, Jeremy. A History of the GUI (Part 1). Ars Technica. 2005 [2009-09-14]. (原始内容存档于2013-07-07). 
  2. ^ 2.0 2.1 Reimer, Jeremy. A History of the GUI (Part 2). Ars Technica. 2005 [2009-09-14]. (原始内容存档于2013-07-07).  引用错误:带有name属性“ArsTechnica2”的<ref>标签用不同内容定义了多次
  3. ^ Milestones: 1975 - Graphical User Interface (GUI). Palo Alto Research Center Incorporated. [2009-09-14]. (原始内容存档于2009-09-04). 
  4. ^ Barger, Jorn. Linux desktops (GUIs, widgets, window managers, etc). 2002 [2009-09-14]. (原始内容存档于2009-08-08).