# 图形用户界面架构说明

本文描述 LarkSDK 图形用户界面架构的设计和实现。

LarkSDK 设计了一套完整的窗体组件语义。该系统完成了不同平台下多种窗口系统的调用封装,保证在所支持的平台环境下,用户只需维护一套代码,即可实现在各个平台下基本相同的图形界面效果与体验。

# 架构综述

LarkSDK 清晰划分了“窗体 (Window)”和“组件 (Component)”的语义概念:

  • 一个"窗体"描述在图形界面下,用户和操作系统交互的基本工具。通常的视觉效果为屏幕上一个带边框的矩形区域。上边框内还留有一定高度的区域作为标题栏。用户可以通过拖拽边框或角落来改变窗口的大小,也可以通过拖拽标题栏移动窗口的位置。标题栏通常还会在中央显示一段文本作为窗体的标题,在左侧或右侧提供若干按钮允许用户对窗体进行最小化、最大化和关闭操作。一个图形界面应用程序通常由一个或多个窗体组成,其中一般有一个窗体作为主窗体存在。
  • 一个“组件”是内嵌于窗体内部的一个可交互图形实体。其通常占据一块矩形区域,实现一定的用户交互功能。组件可以接受包括鼠标移入移出、单击双击甚至拖拽,键盘操作如按键输入(需要焦点)等。组件也负责自己的视觉效果渲染,如标签组件负责显示一段文本、按钮组件负责接受用户鼠标点击输入并实施响应等。

组件之间存在树形的关系结构。每个组件都有其父组件,每个组件都管理自己下属的零个或多个子组件。这允许组件不仅可以单独使用,还能通过嵌套组合构成更复杂的组件,不同的复杂组件甚至可以对构成自身的“零件”进行“复用”。例如菜单组件 LListView 可以单独使用,同时也能作为下拉菜单 LDropdown 的菜单零件使用。

组件树的根节点直接属于窗体管理,称为窗体的根组件。默认情况下根组件始终填满窗体,该窗体内所有组件,在组件树中都是根组件的直接或间接子节点。

TIP

LarkSDK 允许构建无父组件的“独立”组件。但通常都是临时的情况,例如向表格组件的某个单元格中添加一个按钮,就需要先构建一个“独立”的按钮,再调用表格组件的相关接口将该按钮绑定到某个单元格,而绑定后按钮的父组件将被自动设置。

组件的渲染都是通过其所属窗体发起的,因此“独立”的组件在任何情况下均不可见。

在 LarkSDK 中,通过 LWindow 类可以构建窗体,而通过 LComponent 类及其派生类可以构建组件。窗体和组件树的关系大致可以以下图描述:

Fig 1

# 模态与多窗体

现代应用程序允许多窗体。在多窗体的场合下,每一个窗体都负责管理自己的组件树。

应用程序中还存在一种“模态”窗体,其特点是当模态窗体存在时,用户必须完成与模态窗体的交互,才可以返回应用程序或上一级窗体。因此模态窗体分为两类:

  • 全局模态:用户必须完成与当前模态窗体的交互并关闭之,才可继续与应用程序内其他窗体交互;
  • 窗体模态:模态窗体指定另一个非模态窗体为临时(Transient)父窗体,用户必须完成与当前模态窗体的交互并关闭之,才可继续与其临时父窗体交互。期间不影响用户与该模态窗体的临时父窗体之外的窗体交互。

模态窗体带来了另一种窗体之间的关系。通常用于临时的对话框等场合:若以某普通窗体为基础弹出了一个对话框,则用户在完成与对话框的交互之前,其下的普通窗体都处于不可交互的状态。

LarkSDK 提供 LDialog 类描述对话框,对话框天生以模态形式存在方便用户使用。当然用户可以通过手动设置 LWindow 对象的属性来构建自定义的模态窗体。

Fig 2

TIP

目前 LarkSDK 只提供窗体模态实现。

# 组件定位与布局

定义屏幕上向右的方向为 X 轴正向,向下的方向为 Y 轴正向以描述坐标。在 LarkSDK 中,每个组件均记录自身相对于其父组件的坐标位置。为描述方便,下面我们以位置表示组件相对于其父组件的位置,以绝对位置表示组件相对于其所属窗体的位置。

例如若我们说一个组件的位置在 (0, 0),则表示该组件的左上角和父组件的左上角重合。如果位置在 (20, 10) 则表示该组件的左上角位置相对于其父组件的左上角,X 轴向右偏移 20 像素而 Y 轴向下偏移 10 像素。

在无布局情形(通常也称为绝对定位方式)下,组件的位置不会改变(除非手动设置)。绘制时,从窗体的根组件开始,一层层往下绘制:首先完成自身的绘制,再完成其下子组件的绘制。绘制子组件时,通过自身的绝对位置和子组件相对于自身的位置,可以计算出子组件的绝对位置,从而保证子组件可以绘制到窗体的正确位置上。

Fig 3

当然,为了实现复杂的图形用户界面,单单使用绝对定位来控制组件的位置,是远远不够的。绝对定位的最大问题就是无法实现组件位置和尺寸的自适应。因此我们需要布局系统的协助。在布局语境中,父组件称为子组件的“容器 (Container)”,子组件则称为父组件的“元素 (Element)”。容器的大小尺寸改变后,其内部的元素将会根据容器的布局策略自动计算并更新自身的位置,从而提高图形用户界面对显示设备的适应性。

TIP

关于 LarkSDK 布局系统的深入讨论,请参见弹性布局系统说明