EvaLayout, 随心布局!






4.93/5 (56投票s)
高效灵活的布局管理器

引言
难道不能像配置 Color 或 Font 属性一样,动态地配置和切换应用程序的布局,那该多好?有没有哪个布局管理器能做到这一点?
布局,我们指的是在对话框中排列和调整 GUI 组件的大小。这可以通过手动方式(例如,使用编辑器)或通过一个称为布局管理器的软件来完成。在这两种情况下,布局一直都是一种难以配置、更改,并且在大多数情况下,无法动态切换的事情。直到现在,当然!在本文中,我们将看到使用 EvaLayout 进行布局有多么容易。
请注意,布局的灵活性和配置,正如将要描述的那样,不仅在应用程序的外观方面起着重要作用,而且在修改应用程序、编写变体以及实现通用和丰富的组件时也起着重要作用。
背景
有很多布局管理器,也有相当多的方法。EvaLayout 属于网格布局组。列、行、填充空间、跨列和跨行的概念,在所有这些系统中基本上是相同的。EvaLayout 新颖之处在于,它以一种简单、清晰且灵活的方式,将所有布局信息表示在单个文本中,而且,同样重要的是,布局信息和物理组件的分离使得引言中提到的优点得以实现。
EvaLayout 中的布局信息和文本格式
让我们看看定义 EvaLayout 的规则。
EvaLayout 将所有组件放置在具有 n 列和 m 行的网格或表格的单元格内。对于整个网格,可以定义以下内容:
- 对称的(左右相同)水平边距
- 对称的(上下相同)垂直边距
- 水平间距(两个相邻列之间的空间)
- 垂直间距(两个相邻行之间的空间)
对于列和行,每个列/行的标题决定了其行为。这些标题有三种可能性:
- A (默认): 列/行的尺寸将适应该列/行中组件所需的最小尺寸。也就是说,是默认尺寸的最大值。
- X: 可扩展的列/行。所有可扩展的列/行将平均分配剩余空间。
- 数字: 表示列/行的宽度/高度(以像素为单位)。
最后,网格中的每个单元格可能包含以下内容之一:
- 空白,未指定任何内容
- 一个名称,它代表组件的逻辑名称,该名称出现的单元格是组件的左上角单元格
- 符号 -,想要占据右侧更多单元格(跨列)的组件必须在那些单元格中使用此符号
- 符号 +,想要在较低行中占据更多单元格(跨行)的组件必须在那些单元格中使用此符号
所有这些都以一个文本形式提供给布局管理器对象 (EvaLayoutManager),信息以逗号分隔。第一行是整体边距和间距,其余部分用于指定网格或表格。请注意,空格或空白仅用于使其更易读,EvaLayoutManager 实际上并不需要它们。我们来看一个例子。文本
EvaLayout, 10, 10, 5, 5
grid,    75    ,    X    ,    A   ,
   A, boton1   , memo    ,    -   ,
   A, boton2   ,   +     ,        ,
   A, boton3   ,   +     ,        ,
   X,          ,   +     ,        ,
   A, text1    ,   -     , boton4 ,
描述了一个布局,具有:
- 10 像素的左右、上、下边距
- 5 像素的水平和垂直间距
- 第一列 75 像素,第二列可扩展,第三列自适应
- 五行,除可扩展行外,其余均为自适应
- 一个备忘录组件,垂直跨 3 行,水平跨 1 列
- 一个文本组件,水平跨 1 列
整个设计可以是这样的:

生成的对话框是:

现在,我们将看到如何在 C++ Windows 应用程序中实现它。我们将展示如何使用 Windows API 来实现,就像在演示项目中那样,但也可以在 MFC 应用程序中实现,并且有一篇如何实现的文章对此进行了说明。
Using the Code
为了实现,我们将需要以下对象(请注意,除了 EvaLayoutInfo,其余对象都可以是局部对象):
#include "EvaLayoutManager.h"
#include "EvaLine.h"
#include "EvaUnit.h"
#include "EvaFile.h"
...
// to load layout info from a file
EvaFile eFile;
// to load layout info from a file
// or to store more layout info's
EvaUnit eUnit;
// layout info object
Eva     eLayoutInfo;
// the layout manager
EvaLayoutManager manager;
我们将使用 EvaLayoutManager 的步骤分为四个:
- 准备或加载布局信息
- 将布局信息设置到管理器中
- 声明要处理的实际组件
- 进行布局(定位和调整组件大小)
准备或加载布局信息
准备工作可以在例如 WM_INITDIALOG 消息中完成,并且包括设置至少一个 Eva 对象,或者为了加载更多布局,设置一个 EvaUnit 对象,该对象可以像 Evas 数组一样使用。这种准备工作可以通过两种方式完成:
- DirectlyBOOL CALLBACK MainDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { ... case WM_INITDIALOG: { Eva layInfo; layInfo.addLine (EvaLine ("EvaLayout, 10, 10, 5, 5")); layInfo.addLine (EvaLine (" grid, 75 , X , A ,")); layInfo.addLine (EvaLine (" A, boton1 , memo , - ,")); layInfo.addLine (EvaLine (" A, boton2 , + , ,")); layInfo.addLine (EvaLine (" A, boton3 , + , ,")); layInfo.addLine (EvaLine (" X, , + , ,")); layInfo.addLine (EvaLine (" A, edit1 , - , boton4 ,")); ...
- 从文件(Eva 格式,请参阅演示项目中的文件 WinLayouts.eva)BOOL CALLBACK MainDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { ... case WM_INITDIALOG: { EvaFile eFile; EvaUnit eUnit; Eva eLayInfo; eFile.load (eUnit, "WinLayouts.eva", "data"); layInfo = eUnit[0]; ...
将布局信息设置到管理器中
这只是调用布局管理器的方法 setLayout。WM_INITDIALOG 消息也是一个很好的位置,但请注意,也可以在任何时间、任何地点执行此操作,以动态更改布局。执行此操作后,应强制重绘,或更好的是,强制发送大小调整消息。在演示项目中,它是以一种“棘手”但有效的方式完成的。
...
   manager.setLayout(layInfo);
   // force WM_SIZE (resize) here!
...
声明要处理的实际组件
此步骤将组件的物理窗口句柄与其在布局信息中给出的逻辑名称关联起来。这被认为是只执行一次,但也可以多次执行。出现在我们布局信息中的组件以及与对话框关联的所有组件都应在此处添加。布局管理器根据布局信息显示或隐藏组件,但它必须知道所有组件。
manager.removeComponents(); // if appropriate
manager.addComponent("memo",    GetDlgItem(hDlg, ID_MEMO));
manager.addComponent("boton1",  GetDlgItem(hDlg, ID_BUTTON_1));
manager.addComponent("boton2",  GetDlgItem(hDlg, ID_BUTTON_2));
manager.addComponent("boton3",  GetDlgItem(hDlg, ID_BUTTON_3));
...
进行布局(定位和调整组件大小)
在 WM_SIZE 消息处理程序中完成此操作应该足够了。
BOOL CALLBACK MainDlgProc(HWND hDlg, UINT uMsg, 
              WPARAM wParam, LPARAM lParam)
{
   ...
      case WM_SIZE:
         {
            manager.doLayout(wParam, lParam);
         }
         break;
版本 4:组合和遮罩组件
此版本为 C++ EvaLayout 库添加了两个新功能,使得定义复杂布局更加容易,并允许动态更改不仅是所有内容,而且是布局的一部分。
布局组合
鉴于我们向管理器传递了多个布局(或布局信息)。通过组合,指的是一个布局不仅可以引用组件,还可以引用另一个布局,这将由管理器自动解析。这通过将复杂布局分解为更小的部分来极大地简化了其定义。
组件遮罩机制
EvaLayoutManager 类中的一个新函数 maskElement 允许用另一个组件(窗口小部件或布局)替换一个组件。换句话说,能够以一种直接的方式动态更改窗口的一部分。
这两个功能都在 EvaLayoutV4 下载中包含的演示中得到展示。
JavaScript 版本
此 JavaScript 版本的 EvaLayout 具有与 C++ 版本 4 相同的特性,即组合和遮罩。事实上,它是从后者派生出来的。
我必须感谢 Domingo Alvarez,他率先将 C++ Evalayout 的第一个版本翻译成 JavaScript,在他的 Github 项目 https://github.com/mingodad/eva-layout-js 中。
在附带的示例中,使用了 JavaScript evalayout 的一个中间版本,最新的版本保存在 gastona Github 项目的 META-GASTONA/js 下。
使用 Evalayout js 的简单示例的 HTML 代码。
      <html>
        <body>
            <!-- elements will be arranged by layout manager -->
            <button id="bButton1">Boton 1</button>
            <button id="bButton2">Boton 2</button>
            <textarea id="xText">Text area</textarea>
         <script src="Eva.js"> </script>
         <script src="EvaLayout.js"> </script>
         <script src="LayoutManager.js"> </script>
         <script>
           var layoutText = function () {
           /*
             #layouts#
                <main>
                   Evalayout, 10, 10, 20, 20
                      --- , X       ,   X
                          , bButton1, bButton2
                       X  , xText   , -
           #**#
           */
           }.toString ();
         var managr = layoutManager (evaFileStr2obj (layoutText));
         managr.doLayout (500, 400);
        </script>
      </body>
      </html>
以及浏览器中的结果

还包含一个小型交互式示例,使用遮罩方法动态更改布局。
所有示例都非常简单,只需尝试添加更多元素和布局,它就能处理一切!
结论和其他版本
Evalayout 简单而灵活,它有 Java、C++(本文)和 JavaScript 版本。
具体来说,开源项目 gastona 和 jGastona 在此处 github 上可用 使用 Evalayout 以及其他库,不仅用于排列组件,还用于动态生成它们。文章 Http sweet Http (with gastona) 展示了如何在小型 http 应用程序中使用它,通过在服务器端使用 Java,在客户端使用 JavaScript 来生成 GUI。
这篇文章中的这张图片展示了客户端(浏览器)部分:

历史
- 2006年4月22日 - 首次发布
- 2008年1月2日 - 修复了一些 bug 并添加了一个更简洁的 MFC 示例
- 2016年1月21日 - 版本 4:布局组合和遮罩机制
- 2016年8月6日 - JavaScript 版本


