# 窗口程序入门指南

## 窗口程序基础知识 <a id="basic" href="#basic">&#x23;</a>


### 一. 理解窗口应用程序执行流程

1. 界面线程(一般也是主线程)的主消息循环由 win.loopMessage 启动,
窗口应用程序有且只能有一个 win.loopMessage()，使用 win.loadForm() 或者 winform.loadForm() 加载。
窗体代码文件时会自动忽略重复启动消息循环的 win.loopMessage() 调用。

2. 所有非模态、非 MessageOnly 的独立窗口（ 或 mainForm 窗口 ）都关闭后，
将会自动调用 `win.quitMessage()` 终止 `win.loopMessage` 创建的消息循环（通常也就是退出界面线程）。
如果显式指定 `win.autoQuitMessage` 的值为 `false` 则禁止当前线程在关闭上述窗口时自动调用 `win.quitMessage()` 。如果窗口对象自身的 `autoQuitMessage` 的属性值为 `false` 也会禁止在关闭该窗口时自动调用 `win.quitMessage()` 。

3. 使用 winform.doModal() 创建模态窗口, 模态窗口会禁用所有者窗口（也就是背景窗口）直到模态窗口关闭。
模态窗口不能是子窗口（禁用父窗口就是禁用自己）。模态窗口创建独立的消息循环阻塞调用代码继续向后执行
直到模态窗口退出,而调用 winform.show() 显示的非模态窗口不会阻塞调用代码向后执行。

请参考： [启动与退出窗口消息循环](../_.md#loopMessage)

### 二. aardio 工程内如何加载代码文件 

1. 在工程中加载其他代码文件时可使用 loadcodex 函数,例如：loadcodex("/res/x.aardio") 在工程中
加载 aardio 代码文件时,路径应当总是使用斜杠字符开始的应用程序根目录相对路径。可以在工程管理器
中右键点击代码文件,在弹出菜单中点击【复制路径】。

2. 如果加载窗体代码文件可以使用 win.loadForm("/dlg/winform.aardio") 。
win.loadForm 会忽略 win.loopMessage() 。加载的窗体如果没有返回非空值,则默认返回创建的窗体对象。

3. 也可以使用窗体对象加载另一个窗体,例如 `winfrom.loadForm("/dlg/frmChild.aardio") `,  
winfrom.loadForm 的作用与 win.loadForm 相同，但是会自动把 winfrom 指定为被加载窗体的父窗口(或所有者窗口)。
在工程管理器中拖动窗体文件到父窗口代码内会自动生成此代码。

4. 可以在标准库、用户库中创建命名空间、类、窗口类，并使用 import 语句加载。

## 理解窗口间的关系 <a id="parent-vs-owner" href="#parent-vs-owner">&#x23;</a>

### 一. 包含关系（整体局部关系）

也就是 “ 父窗口 / 子窗口 " 关系（ parent / child 关系 ）。

* 父窗口包含子窗口，子窗口拥有 `_WS_CHILD` 样式，显示在父窗口客户区内部。
* 移动父窗口，子窗口必然被移动，因为他们是整体局部的包含关系。
* 父窗口销毁时，子窗口也会被销毁。
* 一个子窗口的父窗口（ parent ）通常是不变的，改动父窗口这属于非常规用法，可能导致潜在的问题。

### 二. 从属关系（责任归属关系）

也就是 “ 所有者窗口 / 从属窗口 ” 关系（ owner / owned 关系 ），消息对话框(msgbox)就是最常见的从属窗口。

* 从属窗口是在视觉上自独立的窗口，没有  `_WS_CHILD` 样式，独立显示在所有者窗口外部。
* 从属窗口( owned window )总是显示在所有者窗口（owner window）前面。
* 移动所有者窗口，从属窗口不会跟着移动。
* 所有者窗口销毁时，从属窗口也会销毁。
* 一个子窗口的所有者窗口（ owner ）是可变的。

设置一个不具有 `_WS_CHILD` 样式的独立窗口的父窗口实际上设置的是 owner 窗口。使用代码 `var winform = win.form({ parent = mainForm })` 为一个独立窗口指定父窗口时，实际上是指定了所有者窗口，而代码 `var childForm = win.form({ mode = "child"; parent = mainForm })` 则为一个嵌入的子窗口指定了父窗口。

除了 win.form 构造参数的 parent 字段以外，aardio 在标准库函数命名时明确区分了 owner 与 parent 窗口。获取与修改所有者窗口的函数为 `win.getOwner()`，`win.setOwner()` ，获取与修改父窗口的函数命名为 `win.getParent()`，`win.setParent()` 。实际上修改父窗口属于非常规用法，子窗口的代码通常与其父窗口密切相关，正常应当属于同一线程。可以这样理解: parent 是亲爹，owner 是干爹，干爹可换，亲爹不可换。

aardio 中还有一种比较特别的 orphanWindow窗口。orphanWindow 即悬浮窗口，指的将普通的控件子窗口（ child window ）孤立出来成为独立的从属窗口（ owned window ），但是仍然可以显示在原来的位置，如影随形的跟随父窗口移动、显示、隐藏，在视觉上模拟类似嵌入式子窗口的效果。 请参考：[orphanWindow](../ui/orphanWindow.md)

## 如何加载子窗口 <a id="loadForm" href="#loadForm">&#x23;</a>


### 一. 使用 winform.loadForm 函数加载子窗口

例如 mainForm是父窗口， "/dlg/frmChild.aardio" 是要加载的子窗口。
我们只要在工程中将 "/dlg/frmChild.aardio" 往 mainForm 的源代码一拖就可以生成下面的加载代码了：

```aardio
var frmChild = mainForm.loadForm("/dlg/frmChild.aardio")
```

这时候如果子窗口 frmChild 指定了 child 样式就会作为嵌入式子窗口显示在父窗口内部。
否则就会作为独立的从属窗口(外部子窗口)独立显示在所有者窗口前面。

从属窗口(外部子窗口)有两种显示模式
1. 调用 winform.show() 显示的非模态窗口，不阻塞调用代码向后执行。
2. 调用 winform.doModal() 显示的模态窗口，模态窗口会创建独立的消息循环
并禁用上级所有者窗口(即背景窗口),阻塞调用代码向后执行直到窗口关闭。

child 样式的嵌入式子窗口不能显示为模态窗口，这样既没意义也会因为禁用父窗口而禁用自身。

### 二. 使用 tab 选项卡控件可以加载并管理多个子窗口：

例如我们窗口上有名为 winform.tab 的选项卡控件，则使用 
winform.tab.loadForm("/dlg/tab-page1.aardio") 加载子窗口。
使用这种方式加载的是嵌入式子窗口,会自动把窗口修改为 child 样式。
win.ui.tabs 实现的高级选项卡可实现类似功能。

### 三. 使用 custom 控件加载子窗口，有3种方法

1. 使用 custom 控件直接加载嵌入式子窗口，在 custom 的类名中输入子窗口的代码文件路径即可。
2. 使用 custom 控件的 loadForm 函数载并管理多个嵌入式子窗口，可以结合高级选项卡(win.ui.tabs)使用。
3. 在 win.ui.ctrl 命名空间下创建控件类库（参考 win.ui.ctrl.custom 等控件的源码），
使用 import 语句导入自定义的控件类，拖动 custom 控件到界面上并且把类名改为对应的自定义控件类名就可以了。

### 四. 使用窗口类库加载窗口

这种窗口类使用 import 导入，通过类构造函数创建窗口。
标准库中的 win.inputBox 就是这样的窗口类。