📄 lion-tut-c32.htm
字号:
<HTML>
<head>
<link rel="stylesheet" href="../../asm.css">
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>Iczelion's win32 asm tutorial</title>
</head>
<body bgcolor="#FFFFFF" background="../../images/back01.jpg">
<h1 align="center">教程32:多文档界面(MDI)</h1>
<p align="left">本教程告诉你怎样创建MDI应用程序.事实上并不是很困难.下载<a
href="files/tut32.zip">例子</a>.</p>
<h3 align="left">理论:</h3>
<p align="left">多文档界面(MDI)是同一时刻处理多个文档的应用程序的一个规范. 你很熟悉记事本.它是单文档界面(SDI)的一个例子.记事本在一个时候只能处理一个文档.假如你希望打开另一个文档,你首先必须关闭你前面打开的那一个.你可以想象这有多麻烦.
和Microsoft Word相比:Word可以随心所欲的在同一时刻打开任意多个文档,而且可以让用户选择使用哪一个文档.Microsoft Word 是多文档界面(MDI)的一个例子.</p>
<p align="left">MDI应用程序有几个显著的特征:我列举其中的一些:</p>
<ul>
<li>有主窗口,在客户区可以有多个子窗口.所有的子窗口都位于客户区. </li>
<li>最小化一个子窗口,它最小化到了主窗口的客户区的左下角. </li>
<li>最大化一个子窗口,它的标题和主窗口的标题合并到了一起. </li>
<li>你可以通过按Ctrl+F4键来关闭子窗口,还可以通过按Ctrl+Tab键在子窗口之间来切换. </li>
</ul>
<p>包含子窗口的主窗口被称为<b>框架窗口.</b>主窗口的客户区是子窗口活动的地方,因此有了'框架'这个名字.主窗口的任务要比普通窗口精细一些,因为它需要为MDI处理一些协调工作.</p>
<p>为了在你的客户区控制任意多个数目的子窗口,你需要一个特殊的窗口:<b>客户窗口</b>.你可以把客户窗口看成是覆盖框架窗口的整个客户区的一个透明的窗口.客户窗口才是所有MDI子窗口的实际的父亲.客户窗口是MDI子窗口的真实的监督者.</p>
<table width="100%">
<TBODY>
<tr>
<td></td>
<td></td>
<td>
<table align="center" border="1" cellPadding="3">
<TBODY>
<tr>
<th noWrap>
<p align="center">框架窗口
</th>
</tr>
</TBODY>
</table>
</td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td>
<p align="center">|
</td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td>
<table align="center" border="1" cellPadding="3">
<TBODY>
<tr>
<th noWrap>客户窗口</th>
</tr>
</TBODY>
</table>
</td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td>
<p align="center">|
</td>
<td></td>
<td></td>
</tr>
<tr>
<td colSpan="5">
<hr noShade>
</td>
</tr>
<tr>
<td>
<p align="center">|
</td>
<td>
<p align="center">|
</td>
<td>
<p align="center">|
</td>
<td>
<p align="center">|
</td>
<td>
<p align="center">|
</td>
</tr>
<tr>
<td>
<table align="center" border="1" cellPadding="3">
<TBODY>
<tr>
<th noWrap>
<p align="center">MDI 自窗口1
</th>
</tr>
</TBODY>
</table>
</td>
<td>
<table align="center" border="1" cellPadding="3">
<TBODY>
<tr>
<th noWrap>
<p align="center">MDI 自窗口 2
</th>
</tr>
</TBODY>
</table>
</td>
<td>
<table align="center" border="1" cellPadding="3">
<TBODY>
<tr>
<th noWrap>
<p align="center">MDI自窗口3
</th>
</tr>
</TBODY>
</table>
</td>
<td>
<table align="center" border="1" cellPadding="3">
<TBODY>
<tr>
<th noWrap>
<p align="center">MDI自窗口4
</th>
</tr>
</TBODY>
</table>
</td>
<td>
<table align="center" border="1" cellPadding="3">
<TBODY>
<tr>
<th noWrap>
<p align="center">MDI 自窗口 n
</th>
</tr>
</TBODY>
</table>
</td>
</tr>
</TBODY>
</table>
<p><b>图1.一个MDI应用程序的层次结构</b></p>
<h3>创建框架窗口</h3>
<p>现在我们将注意力放到细节上来.首先你需要创建框架窗口. 创建框架窗口的方法和普通窗口是相同的:调用CreateWindowEx. 和普通窗口相比,有两个主要的不同.</p>
<p>第一个不同是你<b>必须</b>调用<b><font color="#000099">DefFrameProc</font></b>来处理你的窗口不想处理的窗口信息而不是调用<font
color="#000099"><b>DefWindowProc</b></font>.这是让Windows为你作的保持一个MDI应用程序的垃圾任务的一个方法.假如你忘记使用<b><font
color="#000099">DefFrameProc</font></b><font color="#000099">,</font>你的应用程序将不具有MDI的功能.
<b><font color="#000099">DefFrameProc</font></b>具有下列语法:</p>
<blockquote>
<p><b>DefFrameProc proc hwndFrame:DWORD, <br>
hwndClient:DWORD,<font
color="#000099"><br>
</font> uMsg:DWORD, <br>
wParam:DWORD, <br>
lParam:DWORD</b></p>
</blockquote>
<p>假如你将 <font
color="#003399"><b>DefFrameProc</b></font> 和 <font
color="#000099"><b>DefWindowProc</b></font>作一个对比,你将会注意到它们之间的唯一的不同在于<font
color="#000099"><b>DefFrameProc</b></font>有5个参数,而<font color="#000099"><b>DefWindowProc</b></font>只有4个.这个增加的参数是客户窗口的句柄.这个句柄是必须的,有了它Windows才可以发送MDI-相关的消息给客户窗口.</p>
<p>第二个不同是你必须在你的框架窗口的消息循环中调用 <font color="#000099"><b>TranslateMDISysAccel</b></font>
.假如你希望Windows为你处理MDI相关的加速键,比如Ctrl+F4,Ctrl+Tab,那么这是必须的.它具有下列语法: </p>
<blockquote>
<pre><b>TranslateMDISysAccel proc hwndClient:DWORD,
lpMsg:DWORD </b></pre>
</blockquote>
<p>第一个参数是客户窗口的句柄.对此你应该不会觉得惊讶.因为客户窗口是所有MDI子窗口的父亲. 第二个参数是你通过调用<font
color="#000099"><b>GetMessage</b></font>获得的MSG框架的地址. 我们的想法是传递MSG结构给客户窗口,这样客户窗口可以检测在MSG结构中所包含的MDI相关的按键是不是按下去了.假如是的话,
客户窗口处理这个信息,然后返回一个非零值,否则返回一个假值..</p>
<p>创建框架窗口的步骤总结如下:
<ol>
<li>像平常一样填写 WNDCLASSEX 结构. </li>
<li>通过调用 <font color="#000099"><b>RegisterClassEx</b></font>注册框架窗口类<font color="#000099"><b>.</b></font>
</li>
<li>通过调用<font color="#000099"><b>CreateWindowEx</b></font>创建框架窗口. </li>
<li>在消息循环中调用<font
color="#000099"><b>TranslateMDISysAccel</b></font>. </li>
<li>在窗口过程中,将未处理的消息传递给 <font
color="#000099"><b>DefFrameProc</b></font> 而不是<font color="#000099"><b>DefWindowProc</b></font>.
</li>
</ol>
<h3>创建客户窗口</h3>
<p>现在我们有了框架窗口,我们可以开始创建客户窗口了. 客户窗口类是由Windows预先注册的. 类的名称为"MDICLIENT".
你同样也需要将 <font color="#990099"><b>CLIENTCREATESTRUCT</b></font> 的地址传递给 <font
color="#000099"><b>CreateWindowEx</b></font>. 这个结构具有以下定义:</p>
<blockquote>
<pre><b>CLIENTCREATESTRUCT struct
hWindowMenu dd ?
idFirstChild dd ?
CLIENTCREATESTRUCT ends</b></pre>
</blockquote>
<p><b>hWindowMenu</b> 是子菜单的句柄,这个子菜单显示Windows将要添加的MDI子窗口名称列表. 我们需要对这一功能进行一点解释.假如你以前曾经使用过类似Microsoft
Word的MDI 应用程序,你将会注意到有一个名称为"窗口"的子菜单. 这个菜单一旦激活的话,将会在底部显示出和窗口管理相关的各种各样的菜单项,
还有当前打开的MDI子窗口的列表. 这个列表是由Windows自己内部保持的. 你不需要为它作任何特殊的事情. 仅仅只在需要在<b>hWindowMenu</b>
中传递你所希望显示列表的子菜单的句柄, Windows 将会处理剩下的事情. 注意这个子菜单可以是<b>任何</b>的子菜单:它并不一定要是名称为"窗口"的子菜单.
重要的是你<b>应该</b>传递你希望显示窗口列表的子菜单的句柄. 假如你不想要这个列表,你就给 <b>hWindowMenu </b>赋一个NULL的值就行了.
你可以通过调用<font
color="#000099"><b>GetSubMenu</b></font>来获得子菜单的句柄.</p>
<p><b>idFirstChild</b> 是<b>第一个</b>MDI子窗口的标识号. Windows为应用程序所创建的每一个新的MDI子窗口相应的增加标识号.
举个例子, 假如你传递100给这个域, 第一个MDI子窗口将会有一个值为100的标识符, 那么第二个MDI子窗口也就会有一个值为101的标识符, 如此这样下去.
当从窗口列表中选择MDI子窗口时, 被选择的MDI子窗口的标识符通过WM_COMMAND传递给框架窗口. 正常情况下,你将传递"未处理"的WM_COMMAND消息给DefFrameProc.
我用"未处理"这个词语,是因为窗口列表中的菜单项不是由你的应用程序创建的, 这样你的应用程序不知道它们的标识符,而且也没有它们的句柄.
这是MDI框架窗口又一个特殊的地方. 假如你有窗口列表的话,你必须像这样来修改你的WM_COMMAND句柄:</p>
<blockquote>
<pre><b>.elseif uMsg==WM_COMMAND
.if lParam==0 ; 这条消息是由菜单产生的
mov eax,wParam
.if ax==IDM_CASCADE
.....
.elseif ax==IDM_TILEVERT
.....
.else
<font
color="#cc0033"> invoke DefFrameProc, hwndFrame, hwndClient, uMsg,wParam, lParam
ret</font>
.endif</b></pre>
</blockquote>
<p>一般来说,你可以忽略未处理的消息. 但是在MDI的情况下,如果你忽略它们, 当用户点击窗口列表中的一个MDI子窗口的名称时,,这个窗口不会被激活.
你需要将这些消息传递给<font color="#003399"><b>DefFrameProc</b></font> 这样它们才会得到适当的处理.</p>
<p><b>idFirstChild </b>赋值的注意之处: 你不能使用0. 你窗口列表将会表现的不正常. 举个例子, 即使某一个MDI子窗口被激活的话,
窗口列表中的这个MDI子窗口名字前的复选标记也不会显现. 我们可以选择一个安全的值,比如100或是一个比100大的值.</p>
<p>给 CLIENTCREATESTRUCT 结构赋值后,你可以通过调用 <font
color="#000099"><b>CreateWindowEx</b></font> 用预先注册好的类名"MDICLIENT",
在lParam中传递CLIENTCREATESTRUCT结构的地址来创建客户窗口. 你同样需要在hWndParent参数中指定框架窗口的句柄, 这样Windows才可以知道框架窗口和客户窗口之间的父-子关系.
你可以使用的窗口风格有:WS_CHILD ,WS_VISIBLEHE
WS_CLIPCHILDREN . 假如你忘了WS_VISIBLE的话, 即使MDI子窗口成功地创建了,你也看不到它们.</p>
<p>以下是创建客户窗口的步骤:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -