視窗
視窗是在使用者界面上的一個可見的範圍。視窗一般都是長方形的。它包含著各種控件,是用作輸入和輸出的界面。雖然視窗一般用於圖形使用者界面,但又有時用於命令行界面的。而一個使用視窗為主要用戶界面的系統則稱為視窗系統。這個想法源於施乐帕洛阿尔托研究中心。
在視窗系統上,視窗一般被畫成一些二維的物件,被安置在桌面之上。大多數的視窗都可以隨使用者的意願而更改大小、移動、隱藏、回復和關閉。當兩個視窗重疊的時候,就如日常生活中一樣,其中一個要位於另一個之上,而下方的則會有些地方被上方個視窗所遮蓋。而視窗系統中管理這個操作的部份,則叫做視窗管理員。
視窗是數種圖形使用者界面中的一種重要的widget。當中有VMS的DEC Windows、GNU/Unix-like系統的X Window系統、Microsoft Windows和Open Windows。
历史
这个概念是由斯坦福大學研究所(由 道格拉斯·恩格尔巴特 领导)开发实现的。[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。
参考文献
- Reimer, Jeremy. . Ars Technica. 2005 [2009-09-14]. (原始内容存档于2013-07-07).
- Reimer, Jeremy. . Ars Technica. 2005 [2009-09-14]. (原始内容存档于2013-07-07).
- . Palo Alto Research Center Incorporated. [2009-09-14]. (原始内容存档于2009-09-04).
- Barger, Jorn. . 2002 [2009-09-14]. (原始内容存档于2009-08-08).