DoEasy. 控件 (第 20 部分): SplitContainer WinForms 对象·进阶篇
(2/3)· 从透明容器派生出带 Fixed3D 边框的辅助面板,把 WinForms 布局能力往前推一步
「SplitContainer 面板的折叠与最小尺寸属性」
在 MT5 的 Canvas 图形库里,SplitContainer 控件用两个枚举常量控制子面板状态:MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED 与 PANEL2_COLLAPSED 分别对应面板 1、面板 2 的折叠标志位,MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE 与 PANEL2_MIN_SIZE 则限定各自最小像素尺寸。 这类属性在源码里以俄英双语字符串表登记,例如「Флаг свёрнутости панели 1」映射为「Flag to indicate that panel 1 is collapsed」,调试时若界面面板异常消失,优先查这两个 collapsed 标志。 实际写 EA 面板时,给 Panel1 设最小尺寸 30、Panel2 设 60,可避免拖拽分隔条把图表挤没;外汇与贵金属图表叠加多面板时波动行情下易误触,属高风险操作环境,参数需留余量。
MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED, class=class="str">"cmt">// Flag for collapsed panel class="num">1 MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE, class=class="str">"cmt">// Panel class="num">1 minimum size MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED, class=class="str">"cmt">// Flag for collapsed panel class="num">1 MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE, class=class="str">"cmt">// Panel class="num">2 minimum size class=class="str">"cmt">//--- Real properties of graphical elements class=class="str">"cmt">//--- String properties of graphical elements MSG_CANV_ELEMENT_PROP_NAME_OBJ, class=class="str">"cmt">// Graphical element object name MSG_CANV_ELEMENT_PROP_NAME_RES, class=class="str">"cmt">// Graphical resource name MSG_CANV_ELEMENT_PROP_TEXT, class=class="str">"cmt">// Graphical element text MSG_CANV_ELEMENT_PROP_DESCRIPTION, class=class="str">"cmt">// Graphical element description }; class=class="str">"cmt">//+------------------------------------------------------------------+ {"Поле вкладки элемента управления \"TabControl\"","Tab field of the Control element \"TabControl\""}, {"Элемент управления \"TabControl\"","Control element \"TabControl\""}, {"Панель элемента управления \"SplitContainerPanel\"","Panel of the Control element \"SplitContainerPanel\""}, {"Элемент управления \"SplitContainer\"","Control element \"SplitContainer\""}, {"Элемент управления \"ArrowButton\"","Control element \"ArrowButton\""}, {"Элемент управления \"UpArrowButton\"","Control element \"UpArrowButton\""}, class=class="str">"cmt">//--- CTabControl {"Не удалось получить вкладку элемента управления TabControl","Failed to get tab of TabControl"}, class=class="str">"cmt">//--- CSplitContainer {"Не удалось получить панель элемента управления SplitContainer","Failed to get panel of SplitContainer control"}, class=class="str">"cmt">//--- Integer properties of graphical elements {"Номер столбца вкладки","Tab column number"}, {"Местоположение объекта внутри элемента управления","Location of the object inside the control"}, {"Панель, сохраняющая свои размеры при изменении размера контейнера","Panel that retains its size when the container is resized"}, {"Флаг перемещаемости разделителя","Separator relocatability flag"}, {"Расстояние от края до разделителя","Distance from edge to separator"}, {"Толщина разделителя","Separator Width"}, {"Флаг свёрнутости панели class="num">1","Flag to indicate that panel class="num">1 is collapsed"}, {"Минимальный размер панели class="num">1","Min size of Panel class="num">1"}, {"Флаг свёрнутости панели class="num">2","Flag to indicate that panel class="num">2 is collapsed"}, {"Минимальный размер панели class="num">2","Min size of Panel class="num">2"}, class=class="str">"cmt">//--- String properties of graphical elements {"Имя объекта-графического элемента","The name of the graphic element object"}, {"Имя графического ресурса","Image resource name"}, {"Текст графического элемента","Text of the graphic element"}
◍ 图形元素类型描述的查表写法
在 MT5 自定义指标或 EA 的界面层里,常需要把一个枚举 ENUM_GRAPH_ELEMENT_TYPE 转成可读字符串。上面这段来自图形基类的实现,用嵌套三元运算符一口气覆盖了从标准图元到 WinForms 容器的十余种类型。
这种写法把类型与文案的映射压进一个返回表达式,编译后就是一次线性比较,运行时几乎没有查表开销。如果你自己写面板库,直接抄这个结构比弄个 switch 更易维护,新增类型只在问号表达式里加一行。
注意被高亮的 GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER 分支:它对应可拆分布局容器,很多第三方面板用它做左右可调区域,但标准控件枚举里容易漏掉,导致描述回退成空串。开 MT5 在 CGBaseObj::TypeElementDescription 里断点跑一遍,就能确认你用的图形库版本是否带这条。
class="type">class="kw">string CGBaseObj::TypeElementDescription(class="kw">const ENUM_GRAPH_ELEMENT_TYPE type) { class="kw">return ( type==GRAPH_ELEMENT_TYPE_STANDARD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD) : type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) : type==GRAPH_ELEMENT_TYPE_ELEMENT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT) : type==GRAPH_ELEMENT_TYPE_SHADOW_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ) : type==GRAPH_ELEMENT_TYPE_FORM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM) : type==GRAPH_ELEMENT_TYPE_WINDOW ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW) : class=class="str">"cmt">//--- WinForms type==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY) : type==GRAPH_ELEMENT_TYPE_WF_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE) : class=class="str">"cmt">//--- Containers type==GRAPH_ELEMENT_TYPE_WF_CONTAINER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER) : type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX) : type==GRAPH_ELEMENT_TYPE_WF_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL) : type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) : type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER) : class=class="str">"cmt">//--- Standard controls type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE) : type==GRAPH_ELEMENT_TYPE_WF_LABEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL) : type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX) :
WinForm 控件类型与属性描述的分发写法
在 MT5 的自定义图形库里,CWinFormBase 用了一长串三元运算符把图形元素类型映射成可读文本。上面那段代码覆盖了从单选按钮、列表框到上下/左右箭头盒、分割容器面板等约 17 种 WF 控件类型,命中失败后回退到 "Unknown" 字符串。
这种写法把 type==GRAPH_ELEMENT_TYPE_WF_XXX 逐个比对,再调用 CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_XXX) 取本地化文案。最后一行高亮的 GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL 是分割布局面板,若你自己的面板没显示名称,先确认这个枚举是否进了映射链。
另一个函数 GetPropertyDescription 处理整型属性,比如 CANV_ELEMENT_PROP_ID。它用 only_prop 参数决定只回属性名还是连值一起回;不支持的属性会拼上 MSG_LIB_PROP_NOT_SUPPORTED 提示。开 MT5 把这两段塞进你的窗体基类,断点看一下返回的字符串,就能确认控件树里每个节点叫什么。
type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON) : type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM) : type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX) : class=class="str">"cmt">//--- Auxiliary control objects type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER) : type==GRAPH_ELEMENT_TYPE_WF_TAB_FIELD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX) : type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL) : "Unknown" ); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the description of the control integer class="kw">property | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string CWinFormBase::GetPropertyDescription(ENUM_CANV_ELEMENT_PROP_INTEGER class="kw">property,class="type">bool only_prop=class="kw">false) { class="kw">return ( class="kw">property==CANV_ELEMENT_PROP_ID ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ID)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class=class="str">"cmt">//---... class=class="str">"cmt">//---...
「拆分容器属性的字符串拼装逻辑」
在 MT5 的 Canvas 界面库里,拆分容器(Split Container)有一组专属属性,代码用三元嵌套把属性枚举映射成可读文本。 对齐属性走的是特殊分支:如果是 CANV_ELEMENT_PROP_ALIGNMENT,会调用 AlignmentDescription 把枚举转成文字;其余拆分容器属性(如固定面板、分割条宽度、面板折叠状态等)统一用 (string)this.GetProperty(property) 直接转型输出。 当 only_prop 为 true 时只返回属性名,不附带值;若当前元素不支持该属性,则追加 MSG_LIB_PROP_NOT_SUPPORTED 提示。这套写法覆盖了至少 9 个拆分容器相关枚举,改界面调试输出时可以直接复用。 外汇与贵金属图表上挂这类自定义面板属于高风险操作,参数误设可能导致 EA 界面异常,建议在策略测试器先验证。
class="kw">property==CANV_ELEMENT_PROP_ALIGNMENT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ALIGNMENT)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(class="kw">property)) ) : class="kw">property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL ? CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED ? CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH ? CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED ? CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED ? CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED)+ (only_prop ? "" : !this.SupportProperty(class="kw">property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : class="kw">property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE)+
◍ 分隔容器结构体的字段布局
在 MQL5 图形库底层,SData 结构体承担了图形元素序列化与状态还原的载体角色。它先放整型字段:id 标记元素编号,type 区分图形类别,visible_area_h 控制可视区域高度,displayed 决定控件是否在非隐藏态绘制。 被高亮的那一组字段专门服务于分隔容器(split container)。split_container_fixed_panel 指定缩放时保持尺寸的面板;split_container_splitter_fixed 控制分隔条是否可拖动;split_container_splitter_distance 是边缘到分隔条的距离,split_container_splitter_width 为其像素宽度。 split_container_panel1_collapsed 与 split_container_panel2_collapsed 是两个面板的折叠标志,配套最小尺寸字段 panel1_min_size / panel2_min_size 限制拖拽下限。字符串侧用 uchar name_obj[64] 与 uchar name_res[64] 存对象名和资源名,定长 64 字节避免动态分配。 开 MT5 在自定义指标里打印 sizeof(SData) 可验证该结构体内存占用;改 split_container_splitter_width 从默认 2 调到 5,面板分隔条会明显变粗,便于触屏点按。外汇与贵金属图表挂 EA 改 UI 参数属高风险操作,应先在策略测试器跑通。
class="kw">struct SData { class=class="str">"cmt">//--- Object integer properties class="type">int id; class=class="str">"cmt">// Element ID class="type">int type; class=class="str">"cmt">// Graphical element type class=class="str">"cmt">//---... class=class="str">"cmt">//---... class="type">int visible_area_h; class=class="str">"cmt">// Visibility scope height class="type">bool displayed; class=class="str">"cmt">// Non-hidden control display flag class="type">int split_container_fixed_panel; class=class="str">"cmt">// Panel that retains its size when the container is resized class="type">bool split_container_splitter_fixed; class=class="str">"cmt">// Separator moveability flag class="type">int split_container_splitter_distance; class=class="str">"cmt">// Distance from edge to separator class="type">int split_container_splitter_width; class=class="str">"cmt">// Separator width class="type">bool split_container_panel1_collapsed; class=class="str">"cmt">// Flag for collapsed panel class="num">1 class="type">int split_container_panel1_min_size; class=class="str">"cmt">// Panel class="num">1 minimum size class="type">bool split_container_panel2_collapsed; class=class="str">"cmt">// Flag for collapsed panel class="num">2 class="type">int split_container_panel2_min_size; class=class="str">"cmt">// Panel class="num">2 minimum size class=class="str">"cmt">//--- Object real properties class=class="str">"cmt">//--- Object class="type">class="kw">string properties class="type">uchar name_obj[class="num">64]; class=class="str">"cmt">// Graphical element object name class="type">uchar name_res[class="num">64]; class=class="str">"cmt">// Graphical resource name };
图形元素的构造函数落地
把前面定义的 SData 结构真正用起来,靠的是 CGCnvElement 的带参构造函数。它一口气收下了元素类型、ID、编号、图表 ID、子窗口号,以及坐标、宽高、颜色、透明度等十几个参数,等于把画布上一个可交互控件的全部静态属性在实例化时一次性固化。 注意两个默认参数:movable 默认 true 代表用户可拖拽,redraw 默认 false 意味着除非主动调用否则不重绘。在外汇或贵金属这类高频跳动品种上,滥用 redraw=true 可能让主图卡顿,建议保持默认并在必要时手动触发。 构造函数体里先置 m_shadow 为 false,再设元素类型与 m_type=OBJECT_DE_TYPE_GELEMENT,最后把 m_element_main 和 m_element_base 置空。这意味着基类只搭骨架,具体图形指针留给后续绑定,你开 MT5 新建一个 CGCnvElement 对象时若没挂主图对象,这两个指针就会是 NULL,调试时别漏看。
class="type">uchar text[class="num">256]; class=class="str">"cmt">// Graphical element text class="type">uchar descript[class="num">256]; class=class="str">"cmt">// Graphical element description }; SData m_struct_obj; class=class="str">"cmt">// Object structure class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Parametric constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(class="kw">const ENUM_GRAPH_ELEMENT_TYPE element_type, class="kw">const class="type">int element_id, class="kw">const class="type">int element_num, class="kw">const class="type">long chart_id, class="kw">const class="type">int wnd_num, class="kw">const class="type">class="kw">string descript, class="kw">const class="type">int x, class="kw">const class="type">int y, class="kw">const class="type">int w, class="kw">const class="type">int h, class="kw">const class="type">color colour, class="kw">const class="type">uchar opacity, class="kw">const class="type">bool movable=true, class="kw">const class="type">bool activity=true, class="kw">const class="type">bool redraw=class="kw">false) : m_shadow(class="kw">false) { this.SetTypeElement(element_type); this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_main=NULL; this.m_element_base=NULL;