
DoEasy. 控件 (第 22 部分): SplitContainer。 修改已创建对象的属性
内容
概述
函数库中的 SplitContainer 控件创建时均采用默认值。 我们可以在创建对象后更改对象的属性,但其外观不会改变。 若要避免这种情况发生,需在更改对象属性后,依据其属性的新值重绘对象。
在本文中,篇幅相对较小,我们将敲定设置控件属性的方法,如此在对其属性的所有更改后都能立即更改其外观。
改进库类
在 \MQL5\Include\DoEasy\Defines.mqh 中,将两个新事件添加到 WinForms 控件的可能事件列表之中:
//+------------------------------------------------------------------+ //| List of possible WinForms control events | //+------------------------------------------------------------------+ enum ENUM_WF_CONTROL_EVENT { WF_CONTROL_EVENT_NO_EVENT = GRAPH_OBJ_EVENTS_NEXT_CODE,// No event WF_CONTROL_EVENT_CLICK, // "Click on the control" event WF_CONTROL_EVENT_CLICK_CANCEL, // "Canceling the click on the control" event WF_CONTROL_EVENT_MOVING, // "Control relocation" event WF_CONTROL_EVENT_STOP_MOVING, // "Control relocation stop" event WF_CONTROL_EVENT_TAB_SELECT, // "TabControl tab selection" event WF_CONTROL_EVENT_CLICK_SCROLL_LEFT, // "Clicking the control left button" event WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT, // "Clicking the control right button" event WF_CONTROL_EVENT_CLICK_SCROLL_UP, // "Clicking the control up button" event WF_CONTROL_EVENT_CLICK_SCROLL_DOWN, // "Clicking the control down button" event }; #define WF_CONTROL_EVENTS_NEXT_CODE (WF_CONTROL_EVENT_CLICK_SCROLL_DOWN+1) // The code of the next event after the last graphical element event code //+------------------------------------------------------------------+
某些控件应当响应其移动,并将事件发送到程序,或它们在其中操作的父控件。 例如,当鼠标捕获到滚动区域中的滑块移动时,它应发送有关其移动,以及移动值的事件。 与此类似,控件中的隔板应响应其移动。
在其当前实现中,当鼠标光标悬停在其上时,它会更改其外观。 而当它被移动时,它会向父元素发送一个事件,父元素对此事件做出反应,即调整其面板尺寸。 随后,我将完成此行为,且当鼠标悬停时,对象将由虚线绘制的矩形勾勒出轮廓。 当被鼠标捕获时,它将以一个阴影区域填充。 当释放鼠标(完成移动)时,隔板将恢复其原始外观。
我已添加了两个新事件,从而能正确、及时地响应此类事件(移动控件,并完成其移动)。 我还从列表中删除了最后一个事件 WF_CONTROL_EVENT_SPLITTER_MOVE,因为现在它由 WF_CONTROL_EVENT_MOVING 事件替换,该事件对所有控件都通用。 由于列表中的最后一个事件现在是 WF_CONTROL_EVENT_CLICK_SCROLL_DOWN 事件,因此将其输入到下一个事件代码值的计算中。
如果我们运行上一篇文章中的 EA,,并开始移动主面板,那么它的隔板在 SplitContainer 控件上将不可见。 这是正确的,因为它应当被隐藏,直到鼠标光标悬停在其上。 但如果隔板对象至少移动一次,则此后主面板的任何移动都将导致显示此隐藏控件。
这是一个很难发现的逻辑错误。 但现在我已有了解决方案。 BringToTop() 方法的作用是,它首先隐藏对象,然后再显示它,从而将其置于前景。 正是在这个方法中,隐藏的隔板对象变得可见,因为该方法不考虑示意需要绘制元素的标志状态。
在 \MQL5\Include\DoEasy\Objects\Graph\Form.mqh 中,即在设置对象处于所有其它对象之上的方法中,添加检查示意需要绘制元素的标志。 如果不应显示该对象,则只需离开该方法:
//+------------------------------------------------------------------+ //| Set the object above all the rest | //+------------------------------------------------------------------+ void CForm::BringToTop(void) { //--- If the object should not be displayed, leave if(!this.Displayed()) return; //--- If the shadow usage flag is set if(this.m_shadow) { //--- If the shadow object is created, move it to the foreground if(this.m_shadow_obj!=NULL) this.m_shadow_obj.BringToTop(); } //--- Move the object to the foreground (the object is located above the shadow) CGCnvElement::BringToTop(); //--- In the loop by all bound objects, int total=this.m_list_elements.Total(); for(int i=0;i<total;i++) { //--- get the next object from the list CGCnvElement *obj=this.m_list_elements.At(i); if(obj==NULL) continue; //--- and bring it to the foreground if the object should be displayed if(!obj.Displayed()) continue; obj.BringToTop(); } } //+------------------------------------------------------------------+
将相同的检查添加到所有附着对象的清单当中 — 跳过需要绘制标志被禁用的对象,从而避免将对象带到前景。
在最后一个鼠标事件的处理程序中,添加检查对象需要绘制标志:
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CForm::OnMouseEventPostProcessing(void) { if(!this.IsVisible() || !this.Enabled() || !this.Displayed()) return; ENUM_MOUSE_FORM_STATE state=this.GetMouseState(); switch(state) { //--- The cursor is outside the form, the mouse buttons are not clicked //---... //---...
以前,最后一个鼠标事件不会处理隐藏和非活动对象 。 当前,也不会显示和处理该对象。
在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\SplitContainerPanel.mqh 的 SplitContainer 控制面板对象类中,在元素清除方法中添加检查对象需要绘制标志:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CSplitContainerPanel::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- If the element should not be displayed (hidden inside another control), leave if(!this.Displayed()) return; //--- Fill the element having the specified color and the redrawing flag CGCnvElement::EraseNoCrop(colour,opacity,false); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFrame(); //--- Update the element having the specified redrawing flag this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CSplitContainerPanel::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- If the element should not be displayed (hidden inside another control), leave if(!this.Displayed()) return; //--- Fill the element having the specified color array and the redrawing flag CGCnvElement::EraseNoCrop(colors,opacity,vgradient,cycle,false); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFrame(); //--- Update the element having the specified redrawing flag this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
这些检查还用于防止意外显示在 SplitContainer 控件中隐藏(折叠)的面板。
如果 SplitContainer 控件隔板设置了固定隔板标志,则它不应以任何方式与鼠标交互。 相应地,如果鼠标光标悬停在面板上(光标离开隔板并进入面板),则固定隔板无需处理此类事件。
在“光标在活动区域内,未单击鼠标按钮”事件处理程序中添加检查固定隔板标志:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CSplitContainerPanel::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- Get the pointer to the base object CSplitContainer *base=this.GetBase(); //--- If the base object is not received, or the separator is non-movable, leave if(base==NULL || base.SplitterFixed()) return; //--- Get the pointer to the separator object from the base object CSplitter *splitter=base.GetSplitter(); if(splitter==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER)); return; } //--- If the separator is displayed if(splitter.Displayed()) { //--- Disable the display of the separator and hide it splitter.SetDisplayed(false); splitter.Hide(); } } //+------------------------------------------------------------------+
这样的检查事实上,简单地限制了我们采取不必要的行动。 如果隔板对象从一开始就已被隐藏,则无需再次获取隔板对象,并将其隐藏。
所有主要更改和改进都将 SplitContainer WinForms 对象的 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\SplitContainer.mqh 文件中进行。
在类的私密部分中声明折叠/展开控制面板的新方法:
//--- Set the panel parameters bool SetsPanelParams(void); //--- (1) Collapse and (2) expand the panel 1 void CollapsePanel1(void); void ExpandPanel1(void); //--- (1) Collapse and (2) expand the panel 2 void CollapsePanel2(void); void ExpandPanel2(void); public:
在类主体之外实现所声明的方法。
折叠的面板将被隐藏,而其设置其需要显示属性为 false:
//+------------------------------------------------------------------+ //| Collapse the panel 1 | //+------------------------------------------------------------------+ void CSplitContainer::CollapsePanel1(void) { CSplitContainerPanel *panel=this.GetPanel1(); if(panel==NULL) return; panel.SetDisplayed(false); panel.Hide(); } //+------------------------------------------------------------------+ //| Collapse the panel 2 | //+------------------------------------------------------------------+ void CSplitContainer::CollapsePanel2(void) { CSplitContainerPanel *panel=this.GetPanel2(); if(panel==NULL) return; panel.SetDisplayed(false); panel.Hide(); } //+------------------------------------------------------------------+
对于展开的面板,设置需要显示,显示面板,并将其置于前景:
//+------------------------------------------------------------------+ //| Expand the panel 1 | //+------------------------------------------------------------------+ void CSplitContainer::ExpandPanel1(void) { CSplitContainerPanel *panel=this.GetPanel1(); if(panel==NULL) return; panel.SetDisplayed(true); panel.Show(); panel.BringToTop(); } //+------------------------------------------------------------------+ //| Expand the panel 2 | //+------------------------------------------------------------------+ void CSplitContainer::ExpandPanel2(void) { CSplitContainerPanel *panel=this.GetPanel2(); if(panel==NULL) return; panel.SetDisplayed(true); panel.Show(); panel.BringToTop(); } //+------------------------------------------------------------------+
函数库中一些设置对象属性的方法也可简单地用来设置属性值,或一并设置到图形对象。 我们在类方法里实现相同的功能 — 简单地在对象里写入属性值,或在写入属性值后,重绘整个对象,因为更改属性应导致 SplitContainer 控件的外观发生变化。
在方法的形式参数中,添加示意需要设置对象属性值的标志,并在类主体之外实现该方法,且在此处删除它:
//--- (1) set and (2) return the separator distance from the edge void SetSplitterDistance(const int value,const bool only_prop); int SplitterDistance(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE); } //--- (1) set and (2) return the separator non-removability flag void SetSplitterFixed(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED,flag); } bool SplitterFixed(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED); } //--- (1) set and (2) return the separator width void SetSplitterWidth(const int value,const bool only_prop); int SplitterWidth(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH); } //--- (1) set and (2) return the separator location void SetSplitterOrientation(const ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION value,const bool only_prop); ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION SplitterOrientation(void) const { return(ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION); }
为了令类对象能够独立处理从隔板区域删除鼠标光标,我们需要为最后一个鼠标事件添加一个虚拟处理程序。 我们在类的公开部分声明它:
//--- Event handler virtual void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler virtual void MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Last mouse event handler virtual void OnMouseEventPostProcessing(void); //--- Constructor CSplitContainer(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
在类构造函数中,将固定隔板属性的默认值设置为“可移动”:
//+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CSplitContainer::CSplitContainer(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.SetBorderSizeAll(0); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetPaddingAll(0); this.SetMarginAll(3); this.SetOpacity(0,true); this.SetBackgroundColor(CLR_CANV_NULL,true); this.SetBackgroundColorMouseDown(CLR_CANV_NULL); this.SetBackgroundColorMouseOver(CLR_CANV_NULL); this.SetBorderColor(CLR_CANV_NULL,true); this.SetBorderColorMouseDown(CLR_CANV_NULL); this.SetBorderColorMouseOver(CLR_CANV_NULL); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetSplitterFixed(false); this.CreatePanels(); } //+------------------------------------------------------------------+
创建对象后,默认情况下隔板将是可移动的。 如果以后应将隔板设置为不可移动,只需调用相同的方法将属性设置为 true。
设置面板参数的方法已修改。
现在,将为所有隐藏面板设置成与非隐藏面板的尺寸相等。 同时保持可见面板与其其容器的尺寸等同,即 SplitContainer 对象的整体尺寸:
//+------------------------------------------------------------------+ //| Set the panel parameters | //+------------------------------------------------------------------+ bool CSplitContainer::SetsPanelParams(void) { switch(this.SplitterOrientation()) { //--- The separator is positioned vertically case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL : //--- If both panels are not collapsed, if(!this.Panel1Collapsed() && !this.Panel2Collapsed()) { //--- set the panel1 coordinates and size this.m_panel1_x=0; this.m_panel1_y=0; this.m_panel1_w=this.SplitterDistance(); this.m_panel1_h=this.Height(); //--- set the panel2 coordinates and size this.m_panel2_x=this.SplitterDistance()+this.SplitterWidth(); this.m_panel2_y=0; this.m_panel2_w=this.Width()-this.m_panel2_x; this.m_panel2_h=this.Height(); //--- write separator coordinates and size this.m_splitter_x=this.SplitterDistance(); this.m_splitter_y=0; this.m_splitter_w=this.SplitterWidth(); this.m_splitter_h=this.Height(); } //--- If panel1 or panel2 is collapsed, else { //--- write the coordinates and sizes of panel1 and panel2 this.m_panel2_x=this.m_panel1_x=0; this.m_panel2_y=this.m_panel1_y=0; this.m_panel2_w=this.m_panel1_w=this.Width(); this.m_panel2_h=this.m_panel1_h=this.Height(); //--- write separator coordinates and size this.m_splitter_x=0; this.m_splitter_y=0; this.m_splitter_w=this.SplitterWidth(); this.m_splitter_h=this.Height(); } break; //--- The separator is located horizontally case CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL : //--- If both panels are not collapsed, if(!this.Panel1Collapsed() && !this.Panel2Collapsed()) { //--- set the panel1 coordinates and size this.m_panel1_x=0; this.m_panel1_y=0; this.m_panel1_w=this.Width(); this.m_panel1_h=this.SplitterDistance(); //--- set the panel2 coordinates and size this.m_panel2_x=0; this.m_panel2_y=this.SplitterDistance()+this.SplitterWidth(); this.m_panel2_w=this.Width(); this.m_panel2_h=this.Height()-this.m_panel2_y; //--- write separator coordinates and size this.m_splitter_x=0; this.m_splitter_y=this.SplitterDistance(); this.m_splitter_w=this.Width(); this.m_splitter_h=this.SplitterWidth(); } //--- If panel1 or panel2 is collapsed, else { //--- write the coordinates and sizes of panel1 and panel2 this.m_panel2_x=this.m_panel1_x=0; this.m_panel2_y=this.m_panel1_y=0; this.m_panel2_w=this.m_panel1_w=this.Width(); this.m_panel2_h=this.m_panel1_h=this.Height(); //--- write separator coordinates and size this.m_splitter_x=0; this.m_splitter_y=0; this.m_splitter_w=this.Width(); this.m_splitter_h=this.SplitterWidth(); } break; default: return false; break; } //--- Set the coordinates and sizes of the control area equal to the properties set by the separator this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,this.m_splitter_x); this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,this.m_splitter_y); this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,this.m_splitter_w); this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,this.m_splitter_h); return true; } //+------------------------------------------------------------------+
该方法为面板 1 设置折叠标志:
//+------------------------------------------------------------------+ //| Set the flag of collapsed panel 1 | //+------------------------------------------------------------------+ void CSplitContainer::SetPanel1Collapsed(const int flag) { //--- Set the flag, passed to the method, to the object property this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,flag); CSplitContainerPanel *p1=this.GetPanel1(); CSplitContainerPanel *p2=this.GetPanel2(); if(p1==NULL || p2==NULL) return; //--- Set the parameters of the panels and the separator this.SetsPanelParams(); //--- If panel1 should be collapsed if(this.Panel1Collapsed()) { //--- If panel1 is shifted to new coordinates and its size is changed, hide panel1 if(p1.Move(this.CoordX()+this.m_panel1_x,this.CoordY()+this.m_panel1_y) && p1.Resize(this.m_panel1_w,this.m_panel1_h,false)) { p1.SetCoordXRelative(p1.CoordX()-this.CoordX()); p1.SetCoordYRelative(p1.CoordY()-this.CoordY()); this.CollapsePanel1(); } //--- set the expanded flag for panel2 this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,false); //--- If panel2 is shifted to new coordinates and its size is changed, display panel2 if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y) && p2.Resize(this.m_panel2_w,this.m_panel2_h,true)) { p2.SetCoordXRelative(p2.CoordX()-this.CoordX()); p2.SetCoordYRelative(p2.CoordY()-this.CoordY()); this.ExpandPanel2(); } } //--- If panel1 should be expanded, else { //--- set the collapsed flag for panel2 this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,true); //--- If panel2 is shifted to new coordinates and its size is changed, hide panel2 if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y) && p2.Resize(this.m_panel2_w,this.m_panel2_h,false)) { p2.SetCoordXRelative(p2.CoordX()-this.CoordX()); p2.SetCoordYRelative(p2.CoordY()-this.CoordY()); this.CollapsePanel2(); } //--- If panel1 is shifted to new coordinates and its size is changed, display panel1 if(p1.Move(this.CoordX()+this.m_panel1_x,this.CoordY()+this.m_panel1_y) && p1.Resize(this.m_panel1_w,this.m_panel1_h,true)) { p1.SetCoordXRelative(p1.CoordX()-this.CoordX()); p1.SetCoordYRelative(p1.CoordY()-this.CoordY()); this.ExpandPanel1(); } } } //+------------------------------------------------------------------+
该方法的逻辑在代码中已有注释。 根据传递给方法的标志值,隐藏 panel1(传递给该方法的标志等于 true),并显示 panel2,将其扩展到容器的完整尺寸。 如果传递给该方法的是 false ,则隐藏 panel2,而 panel1 将扩展到容器的完整尺寸。
该方法为面板 2 设置折叠标志:
//+------------------------------------------------------------------+ //| Set the flag of collapsed panel 2 | //+------------------------------------------------------------------+ void CSplitContainer::SetPanel2Collapsed(const int flag) { //--- Set the flag, passed to the method, to the object property this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,flag); CSplitContainerPanel *p1=this.GetPanel1(); CSplitContainerPanel *p2=this.GetPanel2(); if(p1==NULL || p2==NULL) return; //--- Set the parameters of the panels and the separator this.SetsPanelParams(); //--- If panel2 should be collapsed, if(this.Panel2Collapsed()) { //--- If panel2 is shifted to new coordinates and its size is changed, hide panel2 if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y) && p2.Resize(this.m_panel2_w,this.m_panel2_h,false)) { p2.SetCoordXRelative(p2.CoordX()-this.CoordX()); p2.SetCoordYRelative(p2.CoordY()-this.CoordY()); this.CollapsePanel2(); } //--- set the expanded flag for panel1 this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,false); //--- If panel1 is shifted to new coordinates and its size is changed, display panel1 if(p1.Move(this.CoordX()+this.m_panel1_x,this.CoordY()+this.m_panel1_y) && p1.Resize(this.m_panel1_w,this.m_panel1_h,true)) { p1.SetCoordXRelative(p1.CoordX()-this.CoordX()); p1.SetCoordYRelative(p1.CoordY()-this.CoordY()); this.ExpandPanel1(); } } //--- If panel2 should be expanded, else { //--- set the collapsed flag for panel1 this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,true); //--- If panel1 is shifted to new coordinates and its size is changed, hide panel1 if(p1.Move(this.CoordX()+this.m_panel1_x,this.CoordY()+this.m_panel1_y) && p1.Resize(this.m_panel1_w,this.m_panel1_h,false)) { p1.SetCoordXRelative(p1.CoordX()-this.CoordX()); p1.SetCoordYRelative(p1.CoordY()-this.CoordY()); this.CollapsePanel1(); } //--- If panel2 is shifted to new coordinates and its size is changed, display panel2 if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y) && p2.Resize(this.m_panel2_w,this.m_panel2_h,true)) { p2.SetCoordXRelative(p2.CoordX()-this.CoordX()); p2.SetCoordYRelative(p2.CoordY()-this.CoordY()); this.ExpandPanel2(); } } } //+------------------------------------------------------------------+
该方法的逻辑与上述方法的逻辑类似,但标志是为 panel2 设置的,而 panel1 则根据 panel2 是折叠还是展开来隐藏/显示的。
该方法设置隔板与边缘的间距:
//+------------------------------------------------------------------+ //| Set the separator distance from the edge | //+------------------------------------------------------------------+ void CSplitContainer::SetSplitterDistance(const int value,const bool only_prop) { //--- Set the value, passed to the method, to the object property this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,value); //--- Depending on the direction of the separator (vertical or horizontal), //--- set the values to the coordinates of the object control area switch(this.SplitterOrientation()) { case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL : this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,this.SplitterDistance()); this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,0); break; //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL default: this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,0); this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,this.SplitterDistance()); break; } //--- If only setting the property, leave if(only_prop) return; //--- If there are no panels or separator, leave CSplitContainerPanel *p1=this.GetPanel1(); CSplitContainerPanel *p2=this.GetPanel2(); CSplitter *sp=this.GetSplitter(); if(p1==NULL || p2==NULL || sp==NULL) return; //--- Set the parameters of the panels and the separator this.SetsPanelParams(); //--- If the size of the separator object has been successfully changed if(sp.Resize(this.m_splitter_w,this.m_splitter_h,false)) { //--- Shift the separator if(sp.Move(this.CoordX()+this.m_splitter_x,this.CoordY()+this.m_splitter_y)) { //--- Set new relative separator coordinates sp.SetCoordXRelative(sp.CoordX()-this.CoordX()); sp.SetCoordYRelative(sp.CoordY()-this.CoordY()); //--- If panel 1 is resized successfully if(p1.Resize(this.m_panel1_w,this.m_panel1_h,true)) { //--- If panel 2 coordinates are changed to new ones if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y,true)) { //--- if panel 2 has been successfully resized, if(p2.Resize(this.m_panel2_w,this.m_panel2_h,true)) { //--- set new relative coordinates of panel 2 p2.SetCoordXRelative(p2.CoordX()-this.CoordX()); p2.SetCoordYRelative(p2.CoordY()-this.CoordY()); } } } } } } //+------------------------------------------------------------------+
如果传递的 only_prop 标志(仅设置属性)为 false,我们需要为面板和隔板设置新的坐标和尺寸,并根据新的隔板间距值重建面板,而不是为“隔板间距”属性设置新值。
设置隔板宽度的方法:
//+------------------------------------------------------------------+ //| Set the separator width | //+------------------------------------------------------------------+ void CSplitContainer::SetSplitterWidth(const int value,const bool only_prop) { //--- Set the value, passed to the method, to the object property this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,value); //--- Depending on the direction of the separator (vertical or horizontal), //--- set the values to the object control area width and height switch(this.SplitterOrientation()) { case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL : this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,this.SplitterWidth()); this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,this.Height()); break; //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL default: this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,this.Width()); this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,this.SplitterWidth()); break; } //--- If only setting the property, leave if(only_prop) return; //--- If there are no panels or separator, leave CSplitContainerPanel *p1=this.GetPanel1(); CSplitContainerPanel *p2=this.GetPanel2(); CSplitter *sp=this.GetSplitter(); if(p1==NULL || p2==NULL || sp==NULL) return; //--- Set the parameters of the panels and the separator this.SetsPanelParams(); //--- If the size of the separator object has been successfully changed if(sp.Resize(this.m_splitter_w,this.m_splitter_h,false)) { //--- If the separator is shifted to new coordinates if(sp.Move(this.CoordX()+this.m_splitter_x,this.CoordY()+this.m_splitter_y)) { //--- Set new relative separator coordinates sp.SetCoordXRelative(sp.CoordX()-this.CoordX()); sp.SetCoordYRelative(sp.CoordY()-this.CoordY()); //--- If panel 1 is resized successfully if(p1.Resize(this.m_panel1_w,this.m_panel1_h,true)) { //--- If panel 2 coordinates are changed to new ones if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y,true)) { //--- if panel 2 has been successfully resized, if(p2.Resize(this.m_panel2_w,this.m_panel2_h,true)) { //--- set new relative coordinates of panel 2 p2.SetCoordXRelative(p2.CoordX()-this.CoordX()); p2.SetCoordYRelative(p2.CoordY()-this.CoordY()); } } } } } } //+------------------------------------------------------------------+
当前方法细化的逻辑类似于上面曾研究方法的细化逻辑。
设置隔板位置的方法:
//+------------------------------------------------------------------+ //| set the separator location | //+------------------------------------------------------------------+ void CSplitContainer::SetSplitterOrientation(const ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION value,const bool only_prop) { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,value); //--- If only setting the property, leave if(only_prop) return; //--- If there are no panels or separator, leave CSplitContainerPanel *p1=this.GetPanel1(); CSplitContainerPanel *p2=this.GetPanel2(); CSplitter *sp=this.GetSplitter(); if(p1==NULL || p2==NULL || sp==NULL) return; //--- Set the parameters of the panels and the separator this.SetsPanelParams(); //--- If panel 1 is resized successfully if(p1.Resize(this.m_panel1_w,this.m_panel1_h,true)) { //--- If panel 2 coordinates are changed to new ones if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y,true)) { //--- if panel 2 has been successfully resized, if(p2.Resize(this.m_panel2_w,this.m_panel2_h,true)) { //--- set new relative coordinates of panel 2 p2.SetCoordXRelative(p2.CoordX()-this.CoordX()); p2.SetCoordYRelative(p2.CoordY()-this.CoordY()); } } //--- If the size of the separator object has been successfully changed, //--- set new values of separator coordinates if(sp.Resize(this.m_splitter_w,this.m_splitter_h,false)) this.SetSplitterDistance(this.SplitterDistance(),true); } } //+------------------------------------------------------------------+
该方法的逻辑与上述方法的逻辑雷同。 首先,把传递给方法的值设置到对象属性。 如果 only_prop 标志为 false,则设置面板和隔板参数,并根据在面板和隔板属性里设置的尺寸和坐标,重新排列面板和隔板的位置。
由于现在更改对象属性的方法可以立即重建面板和隔板的位置,因此事件处理程序变得更短了:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CSplitContainer::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Adjust subwindow Y shift CGCnvElement::OnChartEvent(id,lparam,dparam,sparam); //--- If the event ID is moving the separator if(id==WF_CONTROL_EVENT_MOVING) { //--- Get the pointer to the separator object CSplitter *splitter=this.GetSplitter(); if(splitter==NULL || this.SplitterFixed()) return; //--- Declare the variables for separator coordinates int x=(int)lparam; int y=(int)dparam; //--- Depending on the separator direction, switch(this.SplitterOrientation()) { //--- vertical position case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL : //--- Set the Y coordinate equal to the Y coordinate of the control element y=this.CoordY(); //--- Adjust the X coordinate so that the separator does not go beyond the control element //--- taking into account the resulting minimum width of the panels if(x<this.CoordX()+this.Panel1MinSize()) x=this.CoordX()+this.Panel1MinSize(); if(x>this.CoordX()+this.Width()-this.Panel2MinSize()-this.SplitterWidth()) x=this.CoordX()+this.Width()-this.Panel2MinSize()-this.SplitterWidth(); break; //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL //--- horizontal position of the separator default: //--- Set the X coordinate equal to the X coordinate of the control element x=this.CoordX(); //--- Adjust the Y coordinate so that the separator does not go beyond the control element //--- taking into account the resulting minimum height of the panels if(y<this.CoordY()+this.Panel1MinSize()) y=this.CoordY()+this.Panel1MinSize(); if(y>this.CoordY()+this.Height()-this.Panel2MinSize()-this.SplitterWidth()) y=this.CoordY()+this.Height()-this.Panel2MinSize()-this.SplitterWidth(); break; } //--- If the separator is shifted by the calculated coordinates, if(splitter.Move(x,y,true)) { //--- set the separator relative coordinates splitter.SetCoordXRelative(splitter.CoordX()-this.CoordX()); splitter.SetCoordYRelative(splitter.CoordY()-this.CoordY()); //--- Depending on the direction of the separator, set its new coordinates this.SetSplitterDistance(!this.SplitterOrientation() ? splitter.CoordX()-this.CoordX() : splitter.CoordY()-this.CoordY(),false); } } } //+------------------------------------------------------------------+如果隔板是固定的,则不应处理元素移动事件,因此将检查此值,若隔板是固定的,则处理程序将退出。 SetSplitterDistance() 方法在此执行双重功能:它设置隔板坐标的新值,并重建对象的面板,因为在调用该方法时 only_prop 标志被指定为 false。
“光标在活动区域内,未单击鼠标按钮”事件处理程序接收指向隔板对象的指针,并以阴影线形式显示它。 如果 SplitContainer 控件的隔板是固定的,则不应显示隔板对象。
因此,在处理程序的最开头,添加以下检查:如果隔板是固定的,则离开该方法:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CSplitContainer::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- If the separator is non-movable, leave if(this.SplitterFixed()) return; //--- Get the pointer to the separator CSplitter *splitter=this.GetSplitter(); if(splitter==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER)); return; } //--- If the separator is not displayed if(!splitter.Displayed()) { //--- Enable the display of the separator, show and redraw it splitter.SetDisplayed(true); splitter.Show(); splitter.Redraw(true); } } //+------------------------------------------------------------------+
在所有图形元素中,我们始终能看到对象的最后一个鼠标事件是什么。 为了实现这一点,窗体对象类带有虚拟的 OnMouseEventPostProcessing() 方法,如果父类方法的逻辑不适合处理最后一个鼠标事件,我们就可在派生类中重写该方法。 这正是我在这个类里所做的。
最后一个鼠标事件处理程序:
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CSplitContainer::OnMouseEventPostProcessing(void) { if(!this.IsVisible() || !this.Enabled() || !this.Displayed()) return; ENUM_MOUSE_FORM_STATE state=this.GetMouseState(); switch(state) { //--- The cursor is outside the form, the mouse buttons are not clicked //--- The cursor is outside the form, any mouse button is clicked //--- The cursor is outside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL : case MOUSE_FORM_STATE_NONE : if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_SPLITTER_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_SPLITTER_AREA_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_SPLITTER_AREA_WHEEL || this.MouseEventLast()==MOUSE_EVENT_NO_EVENT) { //--- Get the pointer to the separator CSplitter *splitter=this.GetSplitter(); if(splitter==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER)); return; } splitter.SetDisplayed(false); splitter.Hide(); this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); } break; //--- The cursor is inside the form, the mouse buttons are not clicked //--- The cursor is inside the form, any mouse button is clicked //--- The cursor is inside the form, the mouse wheel is being scrolled //--- The cursor is inside the active area, the mouse buttons are not clicked //--- The cursor is inside the active area, any mouse button is clicked //--- The cursor is inside the active area, the mouse wheel is being scrolled //--- The cursor is inside the active area, left mouse button is released //--- The cursor is within the window scrolling area, the mouse buttons are not clicked //--- The cursor is within the window scrolling area, any mouse button is clicked //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled //--- The cursor is within the window resizing area, the mouse buttons are not clicked //--- The cursor is within the window resizing area, the mouse button (any) is clicked //--- The cursor is within the window resizing area, the mouse wheel is being scrolled //--- The cursor is within the window resizing area, the mouse buttons are not clicked //--- The cursor is within the window resizing area, the mouse button (any) is clicked //--- The cursor is within the window separator area, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_NOT_PRESSED: case MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_WHEEL : break; //--- MOUSE_EVENT_NO_EVENT default: break; } } //+------------------------------------------------------------------+
如果最后一个事件是从对象区域中删除鼠标光标,则获取指向隔板对象的指针,为其设置非显示标志,隐藏隔板,并将当前鼠标状态设置到上一个状态。
现在,当鼠标移离 SplitContainer 控件时,其隔板对象将被隐藏。 稍后,我将添加从隔板区域移动鼠标光标的处理 — 如此就无需在 SplitContainer 控件的面板对象中执行此操作。
在 \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh 图形元素集合类文件中,在 FormPostProcessing() 方法中添加检查对象非显示标志,因为不显示的对象处理程序也不应理睬:
//+------------------------------------------------------------------+ //| Post-processing of the former active form under the cursor | //+------------------------------------------------------------------+ void CGraphElementsCollection::FormPostProcessing(CForm *form,const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Get the main object the form is attached to CForm *main=form.GetMain(); if(main==NULL) main=form; //--- Get all the elements attached to the form CArrayObj *list=main.GetListElements(); if(list==NULL) return; //--- In the loop by the list of received elements int total=list.Total(); for(int i=0;i<total;i++) { //--- get the pointer to the object CForm *obj=list.At(i); //--- if failed to get the pointer, move on to the next one in the list if(obj==NULL || !obj.IsVisible() || !obj.Enabled() || !obj.Displayed()) continue; obj.OnMouseEventPostProcessing(); //--- Create the list of interaction objects and get their number int count=obj.CreateListInteractObj(); //--- In the loop by the obtained list for(int j=0;j<count;j++) { //--- get the next object CWinFormBase *elm=obj.GetInteractForm(j); if(elm==NULL || !elm.IsVisible() || !elm.Enabled() || !elm.Displayed()) continue; if(elm.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) { CTabControl *tab_ctrl=elm; CForm *selected=tab_ctrl.SelectedTabPage(); if(selected!=NULL) elm=selected; } //--- determine the location of the cursor relative to the object //--- and call the mouse event handling method for the object elm.MouseFormState(id,lparam,dparam,sparam); elm.OnMouseEventPostProcessing(); } } ::ChartRedraw(main.ChartID()); } //+------------------------------------------------------------------+
这些就是目前的改进。 我们来检查一下成果。
测试
为了执行测试,我将延用来自上一篇文章中的 EA,并将其保存在 \MQL5\Experts\TestDoEasy\Part122\ 中,命名为 TestDoEasy122.mq5。
EA 创建 TabControl 需在其上构建的面板。 我们在前五个选项卡中的每一个选项卡上创建一个 SplitContainer 控件。 隔板在偶数索引上是垂直的,在奇数索引上是水平的。 在第三个选项卡上,固定隔板,在第四个和第五个选项卡上,分别折叠 panel2 和 panel1。
EA 的 OnInit() 处理程序现在如下所示:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set EA global variables ArrayResize(array_clr,2); // Array of gradient filling colors array_clr[0]=C'26,100,128'; // Original ≈Dark-azure color array_clr[1]=C'35,133,169'; // Lightened original color //--- Create the array with the current symbol and set it to be used in the library string array[1]={Symbol()}; engine.SetUsedSymbols(array); //--- Create the timeseries object for the current symbol and period, and show its description in the journal engine.SeriesCreate(Symbol(),Period()); engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions //--- Create the required number of WinForms Panel objects CPanel *pnl=NULL; for(int i=0;i<1;i++) { pnl=engine.CreateWFPanel("WinForms Panel"+(string)i,(i==0 ? 50 : 70),(i==0 ? 50 : 70),410,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false); if(pnl!=NULL) { pnl.Hide(); Print(DFUN,"Panel description: ",pnl.Description(),", Type and name: ",pnl.TypeElementDescription()," ",pnl.Name()); //--- Set Padding to 4 pnl.SetPaddingAll(3); //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs pnl.SetMovable(InpMovable); pnl.SetAutoSize(InpAutoSize,false); pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false); //--- Create TabControl pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,InpTabControlX,InpTabControlY,pnl.Width()-30,pnl.Height()-40,clrNONE,255,true,false); CTabControl *tc=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0); if(tc!=NULL) { tc.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode); tc.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment); tc.SetMultiline(InpTabCtrlMultiline); tc.SetHeaderPadding(6,0); tc.CreateTabPages(15,0,56,20,TextByLanguage("Вкладка","TabPage")); //--- Create a text label with a tab description on each tab for(int j=0;j<tc.TabPages();j++) { tc.CreateNewElement(j,GRAPH_ELEMENT_TYPE_WF_LABEL,322,120,80,20,clrDodgerBlue,255,true,false); CLabel *label=tc.GetTabElement(j,0); if(label==NULL) continue; //--- If this is the very first tab, then there will be no text label.SetText(j<5 ? "" : "TabPage"+string(j+1)); } for(int n=0;n<5;n++) { //--- Create a SplitContainer control on each tab tc.CreateNewElement(n,GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,10,10,tc.Width()-22,tc.GetTabField(0).Height()-22,clrNONE,255,true,false); //--- Get the SplitContainer control from each tab CSplitContainer *split_container=tc.GetTabElementByType(n,GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,0); if(split_container!=NULL) { //--- The separator will be vertical for each even tab and horizontal for each odd one split_container.SetSplitterOrientation(n%2==0 ? CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL : CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL,true); //--- The separator distance on each tab will be 50 pixels split_container.SetSplitterDistance(50,true); //--- The width of the separator on each subsequent tab will increase by 2 pixels split_container.SetSplitterWidth(4+2*n,false); //--- Make a fixed separator for the tab with index 2, and a movable one for the rest split_container.SetSplitterFixed(n==2 ? true : false); //--- For a tab with index 3, the second panel will be in a collapsed state (only the first one is visible) if(n==3) split_container.SetPanel2Collapsed(true); //--- For a tab with index 4, the first panel will be in a collapsed state (only the second one is visible) if(n==4) split_container.SetPanel1Collapsed(true); //--- On each of the control panels... for(int j=0;j<2;j++) { CSplitContainerPanel *panel=split_container.GetPanel(j); if(panel==NULL) continue; //--- ...create a text label with the panel name if(split_container.CreateNewElement(j,GRAPH_ELEMENT_TYPE_WF_LABEL,4,4,panel.Width()-8,panel.Height()-8,clrDodgerBlue,255,true,false)) { CLabel *label=split_container.GetPanelElementByType(j,GRAPH_ELEMENT_TYPE_WF_LABEL,0); if(label==NULL) continue; label.SetTextAlign(ANCHOR_CENTER); label.SetText(TextByLanguage("Панель","Panel")+string(j+1)); } } } } } } } //--- Display and redraw all created panels for(int i=0;i<1;i++) { pnl=engine.GetWFPanelByName("Panel"+(string)i); if(pnl!=NULL) { pnl.Show(); pnl.Redraw(true); } } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
处理程序的逻辑在代码中已有完整注释。
依据构建 SplitContainer 控件的选项卡数量循环,在每个选项卡上创建一个新对象。 创建后,获取指向它的指针,并为其设置新参数。
编译 EA,并在图表上启动它:
如您所见,在创建对象之后,为对象设置的所有新属性都会正确更改其外观。
至于缺点,鼠标光标从隔板对象移开后,我们可以看到隐藏隔板对象的模糊触发器。 但是我将修改行为和显示逻辑,以便更贴切地匹配 MS Visual Studio 中的行为逻辑。 这将令我们能够深入挖掘这个问题。
下一步是什么?
在下一篇文章中,我将继续研究 SplitContainer 控件。
*该系列的前几篇文章:
DoEasy. 控件 (第 13 部分): 优化 WinForms 对象与鼠标的交互,启动开发 TabControl WinForms 对象
DoEasy. 控件 (第 14 部分): 命名图形元素的新算法。 继续工作于 TabControl WinForms 对象
DoEasy. 控件 (第 15 部分): TabControl WinForms 对象 — 多行选项卡标题、选项卡处理方法
DoEasy. 控件 (第 16 部分): TabControl WinForms 对象 — 多行选项卡标题,拉伸标题适配容器
DoEasy. 控件 (第 17 部分): 裁剪对象不可见部分、辅助箭头按钮 WinForms 对象
DoEasy. 控件 (第 18 部分): TabControl 中滚动选项卡的功能
DoEasy. 控件 (第 19 部分): 在 TabControl 中滚动选项卡、WinForms 对象事件
DoEasy. 控件 (第 20 部分): SplitContainer WinForms 对象
DoEasy. 控件 (第 21 部分): SplitContainer 控件 面板隔板
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/11601

