量化交易軟件:在 GUI 控件中使用布局和容器CBox 類
1. 介紹
在大多應(yīng)用里, 在對話框窗口里使用控件絕對定位的直接方式來創(chuàng)建圖形用戶界面。然而, 在某些情況下, 這種 圖形用戶界面 (GUI) 設(shè)計的方式很不方便, 甚或很不實際。本文介紹一種基于布局和容器來創(chuàng)建 GUI (圖形用戶界面) 的替代方法, 使用一個布局管理器 — CBox 類。
在本文中實現(xiàn)并使用的布局管理器大致等同于一些可在主流編程語言找到的諸如 BoxLayout (Java) 和 幾何管理器包 (Python/Tkinter)。

編輯切換為居中
2. 目標(biāo)
查看 赫茲量化里提供的 SimplePanel 和控件例程, 赫茲量化可以看到在這些面板里的控件都按照像素定位 (絕對定位)。創(chuàng)建的每個控件都在客戶區(qū)域分配一個確定的位置, 且每個控件都依賴于在其之前創(chuàng)建的控件, 并附加一些偏移。雖然這是很自然的方式, 盡管在大多情況下不要求很高精度, 使用這種方法在許多方面都很不利。
任何有經(jīng)驗的程序員在設(shè)計圖形用戶界面是都可采用圖形控件的精確像素定位。不過, 這有以下不足:
通常, 當(dāng)一個部件的大小或位置被修改時, 它不能防止其它部件免受影響。
大多數(shù)代碼不可重用 — 界面上的微小改變可能導(dǎo)致代碼的極大修改。
它可能很耗時間, 尤其在設(shè)計更復(fù)雜界面時。
這促使赫茲量化創(chuàng)建一個布局系統(tǒng), 其目標(biāo)如下:
代碼應(yīng)可重用。
修改界面的一部分應(yīng)對其它部件的影響最小化。
界面中的部件定位應(yīng)自動計算。
在這篇文章里介紹了一種此類系統(tǒng)的實現(xiàn), 使用容器 — CBox 類。
3. 類 CBox
類 CBox 的一個實例作為一個容器或盒子 — 可將控件添加到該盒子中, 且 CBox 可自動計算控件在其所分配的空間里的定位。一個典型的 CBox 類實例應(yīng)有以下布局:

編輯
圖例 1. CBox 布局
外層盒子表示容器的整個大小, 而內(nèi)里的虛線盒子表示襯墊邊界。藍色區(qū)域表示襯墊空間。剩余白色空間則是控件在容器內(nèi)可用于定位的空間。
依據(jù)面板的復(fù)雜度, CBox 類可按不同方式使用。例如, 它可作為容器 (CBox) 來保存其它存有一套控件的容器?;蚴侨萜鲀?nèi)含有一個控件和其它容器。不過, 極力推薦在給定的父容器里使用同輩份容器。
赫茲量化通過擴展 CWndClient (不含滾動條) 來構(gòu)建 CBox, 如以下片段所示:
#include <Controls\WndClient.mqh> //+------------------------------------------------------------------+ //| ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?| //+------------------------------------------------------------------+ class CBox : public CWndClient ?{ public: ? ? ? ? ? ? ? ? ? ? CBox(); ? ? ? ? ? ? ? ? ? ?~CBox(); ? ? virtual bool ? ? ?Create(const long chart,const string name,const int subwin, ? ? ? ? ? ? ? ? ? ? ? ? ? const int x1,const int y1,const int x2,const int y2); ?}; //+------------------------------------------------------------------+ //| ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?| //+------------------------------------------------------------------+ CBox::CBox() ?{ ?} //+------------------------------------------------------------------+ //| ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?| //+------------------------------------------------------------------+ CBox::~CBox() ?{ ?} //+------------------------------------------------------------------+ //| ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?| //+------------------------------------------------------------------+ bool CBox::Create(const long chart,const string name,const int subwin, ? ? ? ? ? ? ? ? ?const int x1,const int y1,const int x2,const int y2) ?{ ? if(!CWndContainer::Create(chart,name,subwin,x1,y1,x2,y2)) ? ? ?return(false); ? if(!CreateBack()) ? ? ?return(false); ? if(!ColorBackground(CONTROLS_DIALOG_COLOR_CLIENT_BG)) ? ? ?return(false); ? if(!ColorBorder(clrNONE)) ? ? ?return(false); ? return(true); ?} //+------------------------------------------------------------------+
CBox 類也可以直接從 CWndContainer 繼承。但是, 這樣做會喪失一些這個類里的有用功能, 如背景和邊框。代之, 一個更簡單的版本可以直接從擴展 CWndObj來實現(xiàn), 但您將需要加入一個 CArrayObj 的實例作為其 私有或保護成員 并重新創(chuàng)建所涉及對象的類方法來保存該實例。
3.1. 布局樣式
CBox 有兩種布局樣式: 垂直樣式和水平樣式。
水平樣式有以下基本布局:

編輯
圖利 2. 水平樣式 (居中)
垂直樣式有以下基本布局:
編輯
圖例 3. 垂直樣式 (居中)
CBox 省缺使用水平樣式。
使用這兩種布局的組合 (也許使用多容器), 這可以重建幾乎任何類型的 GUI 面板設(shè)計。此外, 在容器內(nèi)布置控件也允許分段設(shè)計。即, 它允許在給定容器里自定義控件的大小和位置, 且不影響其它容器的所在。
為了在 CBox 內(nèi)實現(xiàn)水平和垂直樣式, 赫茲量化需要聲明一個枚舉, 然后我們在所述類中將其作為一個成員保存:
enum LAYOUT_STYLE ?{ ? LAYOUT_STYLE_VERTICAL, ? LAYOUT_STYLE_HORIZONTAL ?};
3.2. 計算控件之間的間隔
CBox 在其所分配的可用空間里最大化, 并為其所含控件均勻定位, 如前圖所示。
綜觀上圖, 赫茲量化可以推導(dǎo)出計算給定 CBox 容器內(nèi)控件之間間隔的公式, 使用以下的偽代碼:
對于水平布局: x 間隔 = ((可用空間 x)-(所有控件的總計 x 大小))/(控件總數(shù) + 1) y 間隔 = ((可用空間 y)-(y 控件大小))/2 對于垂直布局: x 間隔 = ((可用空間 x)-(x 控件大小))/2 y 間隔 = ((可用空間 y)-(所有控件的總計 y 大小))/(控件總數(shù) + 1)
3.3. 對齊
如上一章節(jié)所述, 控件間的間隔計算僅用于居中對齊。我們希望 CBox 類能容納更多對齊方式, 所以我們需要在計算中進行一些小修改。
對于水平對齊, 可用的選項, 除了容器居中, 是靠左, 靠右, 和居中 (無邊), 如下圖所示:

編輯
圖例 4. 水平樣式 (左對齊)

編輯
圖例 5. 水平樣式 (右對齊)

編輯
圖例 6. 水平樣式 (居中, 無邊)
對于水平對齊, 可用的選項, 除了容器居中, 是靠頂, 靠底, 居中, 和居中 (無邊), 如下圖所示:

編輯

編輯

編輯
圖例 7. 垂直對齊樣式: (左) 居頂, (中) 居中 - 無邊, (右) 居底
需要注意的是的 CBox 類應(yīng)基于這些對齊設(shè)置來自動計算控件間的 x- 和 y-間隔。所以, 最好使用除數(shù)
(控件總數(shù) + 1)
來獲取控件間隔, 我們赫茲量化控件總數(shù)作為除數(shù), 以及 (控件總數(shù) - 1) 作為邊界無余量的居中控件。
類似于布局樣式, 實現(xiàn) CBox 類的對齊特性將需要枚舉。我們要為每種對齊樣式聲明一個枚舉, 如下:
enum VERTICAL_ALIGN ?{ ? VERTICAL_ALIGN_CENTER, ? VERTICAL_ALIGN_CENTER_NOSIDES, ? VERTICAL_ALIGN_TOP, ? VERTICAL_ALIGN_BOTTOM ?}; enum HORIZONTAL_ALIGN ?{ ? HORIZONTAL_ALIGN_CENTER, ? HORIZONTAL_ALIGN_CENTER_NOSIDES, ? HORIZONTAL_ALIGN_LEFT, ? HORIZONTAL_ALIGN_RIGHT ?};
3.4. 部件渲染
一般地, 赫茲量化通過指定 x1, y1, x2, 和 y2 參數(shù)來創(chuàng)建控件, 譬如以下創(chuàng)建一個 按鈕 的片段:
CButton m_button; int x1 = currentX; int y1 = currentY; int x2 = currentX+BUTTON_WIDTH; int y2 = currentY+BUTTON_HEIGHT if(!m_button.Create(m_chart_id,m_name+"Button",m_subwin,x1,y1,x2,y2)) ? ? ?return(false);
此處 x2 減去 x1 以及 y2 減去 y1 分別等于控件的寬度和高度。若不用這種方法, 我們可以利用 CBox, 采用更簡單的方法來創(chuàng)建同樣的按鈕, 如以下片段所示:
if(!m_button.Create(m_chart_id,m_name+"Button",m_subwin,0,0,BUTTON_WIDTH,BUTTON_HEIGHT)) ? ? ?return(false);
類 CBox 將會在之后創(chuàng)建的面板窗口里自動重定位部件。調(diào)用方法 Pack() 用于控件和容器的重定位, 它會再調(diào)用 Render() 方法, :
bool CBox::Pack(void) ?{ ? GetTotalControlsSize(); ? return(Render()); ?}
方法 Pack() 簡單地獲取容器的組合大小, 之后調(diào)用 Render() 方法, 在此處會有更多動作。以下片段示意通過 Render() 方法對真實容器內(nèi)的控件渲染:
bool CBox::Render(void) ?{ ? int x_space=0,y_space=0; ? if(!GetSpace(x_space,y_space)) ? ? ?return(false); ? int x=Left()+m_padding_left+ ? ? ?((m_horizontal_align==HORIZONTAL_ALIGN_LEFT||m_horizontal_align==HORIZONTAL_ALIGN_CENTER_NOSIDES)?0:x_space); ? int y=Top()+m_padding_top+ ? ? ?((m_vertical_align==VERTICAL_ALIGN_TOP||m_vertical_align==VERTICAL_ALIGN_CENTER_NOSIDES)?0:y_space); ? for(int j=0;j<ControlsTotal();j++) ? ? { ? ? ?CWnd *control=Control(j); ? ? ?if(control==NULL) ? ? ? ? continue; ? ? ?if(control==GetPointer(m_background)) ? ? ? ? continue; ? ? ?control.Move(x,y); ? ? ? ? ?if (j<ControlsTotal()-1) ? ? ? ? Shift(GetPointer(control),x,y,x_space,y_space); ? ? ? ? ? } ? return(true); ?}
3.5. 部件大小調(diào)整
當(dāng)控件尺寸大于其容器的可用空間, 應(yīng)調(diào)整控件大小以便適應(yīng)可用空間。否則, 控件將溢出容器, 致使整個面板的外觀問題。當(dāng)您希望某個控件最大化, 占據(jù)整個客戶或其容器空間, 這種方法也一樣便利。如果給定控件的寬度或高度超出容器的寬度或高度減去襯墊 (雙層邊), 將調(diào)整控件大小至最大可用寬度或高度。
需要注意的是, CBox 包含的所有控件的大小總和超過可用空間時, 不會調(diào)整容器大小。在此情況下, 或是主對話框窗口的大小 (CDialog 或 CAppDialog), 或是單獨的控件將需要手工調(diào)整。