DoEasy. 控件 (第 5 部分): 基准 WinForms 对象,面板控件,AutoSize 参数·进阶篇
(2/3)· 面板里对象一多就画歪、留残影?本篇从基类重构讲清 Dock 与 AutoSize 的咬合逻辑
「给画布元素加阴影的底层接口」
在 MT5 的自定义画布元素类里,阴影效果并不是靠系统自动渲染,而是留了一组虚函数和一个 Draw 方法让你自己控制。基类里 SupportProperty 对整数型和字符串型属性都直接返回 true,意味着继承后默认不限制属性写入,实际项目里最好按需重写以免误设。 Draw 方法的签名是 Draw(const int shift_x, const int shift_y, const uchar blur_value, const bool redraw),前两个参数是阴影相对主体的像素偏移,blur_value 是 0~255 的模糊强度,redraw 决定是否立刻重绘。外汇和贵金属图表上叠加这类视觉层会多吃一点 GPU,低配 VPS 可能掉帧,属于高风险环境下的体验取舍。 颜色、透明度、模糊量都给了成对的 Set/Get:SetColor 写 m_color,Color 读回;SetOpacity 控 m_opacity(uchar 0~255),SetBlur 写 m_blur。下面这段是接口声明原文,可直接抄进你的 CCanvas 派生类头文件里改。
class="kw">const class="type">int w, class="kw">const class="type">int h); class=class="str">"cmt">//--- Supported object properties(class="num">1) integer and(class="num">2) class="type">class="kw">string ones class="kw">virtual class="type">bool SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING class="kw">property) { class="kw">return true; } class=class="str">"cmt">//--- Draw an object shadow class="type">void Draw(class="kw">const class="type">int shift_x,class="kw">const class="type">int shift_y,class="kw">const class="type">uchar blur_value,class="kw">const class="type">bool redraw); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Methods of simplified access to object properties | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the shadow class="type">color class="type">void SetColor(class="kw">const class="type">color colour) { this.m_color=colour; } class="type">color Color(class="type">void) class="kw">const { class="kw">return this.m_color; } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the shadow opacity class="type">void SetOpacity(class="kw">const class="type">uchar opacity) { this.m_opacity=opacity; } class="type">uchar Opacity(class="type">void) class="kw">const { class="kw">return this.m_opacity; } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the shadow blur class="type">void SetBlur(class="kw">const class="type">uchar blur) { this.m_blur=blur; } class="type">uchar Blur(class="type">void) class="kw">const { class="kw">return this.m_blur; } };
◍ 阴影对象的构造与绘制逻辑
在 MT5 自定义图形库里,CShadowObj 负责给控件画投影。它的构造函数把元素类型钉死为 OBJECT_DE_TYPE_GSHADOW,同时把背景色设成 clrNONE、透明度初值 0、非激活态,避免自身抢了主体控件的视觉焦点。 默认阴影参数来自两个常量:m_opacity 取 CLR_DEF_SHADOW_OPACITY,m_blur 取 DEF_SHADOW_BLUR。阴影颜色不是写死的,而是拿图表背景色做降饱和(-100)再压暗(-50)得到灰黑调,这样在不同模板下都能融进背景。 Draw() 方法接收 shift_x、shift_y 偏移和 blur_value 模糊量。矩形实际绘制宽高会扣掉 OUTER_AREA_SIZE*2,给模糊溢出留边。模糊半径被硬约束不能超过 OUTER_AREA_SIZE/4,超了就截断,防止高斯模糊把整块画布糊掉。 最后 Move() 按相对偏移搬运阴影图层,redraw 参数决定要不要立刻重绘画布。外汇和贵金属图表高频刷新时,这个 redraw 开关直接影响 CPU 占用,建议只在批量更新后统一置 true。
class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CShadowObj::CShadowObj(class="kw">const class="type">long chart_id, class="kw">const class="type">int subwindow, class="kw">const class="type">class="kw">string name, 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) : CGCnvElement(GRAPH_ELEMENT_TYPE_SHADOW_OBJ,chart_id,subwindow,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GSHADOW; CGCnvElement::SetColorBackground(clrNONE); CGCnvElement::SetOpacity(class="num">0); CGCnvElement::SetActive(class="kw">false); this.m_opacity=CLR_DEF_SHADOW_OPACITY; this.m_blur=DEF_SHADOW_BLUR; class="type">color gray=CGCnvElement::ChangeColorSaturation(ChartColorBackground(),-class="num">100); this.m_color=CGCnvElement::ChangeColorLightness(gray,-class="num">50); this.m_shadow=class="kw">false; this.m_visible=true; CGCnvElement::Erase(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Draw the object shadow | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CShadowObj::Draw(class="kw">const class="type">int shift_x,class="kw">const class="type">int shift_y,class="kw">const class="type">uchar blur_value,class="kw">const class="type">bool redraw) { class=class="str">"cmt">//--- Set the shadow shift values to the variables by X and Y axes this.SetCoordXRelative(shift_x); this.SetCoordYRelative(shift_y); class=class="str">"cmt">//--- Calculate the height and width of the drawn rectangle class="type">int w=this.Width()-OUTER_AREA_SIZE*class="num">2; class="type">int h=this.Height()-OUTER_AREA_SIZE*class="num">2; class=class="str">"cmt">//--- Draw a filled rectangle with calculated dimensions this.DrawShadowFigureRect(w,h); class=class="str">"cmt">//--- Calculate the blur radius, which cannot exceed a quarter of the OUTER_AREA_SIZE constant this.m_blur=(blur_value>OUTER_AREA_SIZE/class="num">4 ? OUTER_AREA_SIZE/class="num">4 : blur_value); class=class="str">"cmt">//--- If failed to blur the shape, exit the method(GaussianBlur() displays the error on the journal) if(!this.GaussianBlur(this.m_blur)) class="kw">return; class=class="str">"cmt">//--- Shift the shadow object by X/Y offsets specified in the method arguments and update the canvas CGCnvElement::Move(this.CoordX()+this.CoordXRelative(),this.CoordY()+this.CoordYRelative(),redraw);
表单类的私有成员与拖拽偏移设计
在 MT5 自定义图形界面里,CForm 作为容器类继承自 CGCnvElement,其私有段决定了窗体能否被鼠标稳定拖拽与重绘。 m_offset_x 与 m_offset_y 记录光标按下时窗体左上角相对鼠标的像素偏移,避免拖动时窗体瞬间跳到光标中心;m_init_x / m_init_y / m_init_w / m_init_h 则固化新建窗体的初始坐标与尺寸,便于 ResetArrayFrameT() 重置文本、矩形、几何三类动画帧数组时复用基准。 m_mouse_form_state 配合 m_mouse_state_flags(ushort 位标记)描述鼠标相对窗体的状态,m_color_frame 控制边框色。开 MT5 把这几行加进你的面板基类,拖拽手感会明显比直接设坐标更跟手。 外汇与贵金属图表加载此类悬浮窗时,需注意高波动时段主线程繁忙可能导致重绘延迟,属高风险环境下的体验问题而非逻辑错误。
class CForm : class="kw">public CGCnvElement { class="kw">private: CArrayObj m_list_elements; class=class="str">"cmt">// List of attached elements CAnimations *m_animations; class=class="str">"cmt">// Pointer to the animation object CShadowObj *m_shadow_obj; class=class="str">"cmt">// Pointer to the shadow object CMouseState m_mouse; class=class="str">"cmt">// "Mouse status" class object ENUM_MOUSE_FORM_STATE m_mouse_form_state; class=class="str">"cmt">// Mouse status relative to the form class="type">class="kw">ushort m_mouse_state_flags; class=class="str">"cmt">// Mouse status flags class="type">color m_color_frame; class=class="str">"cmt">// Form frame class="type">color class="type">int m_offset_x; class=class="str">"cmt">// Offset of the X coordinate relative to the cursor class="type">int m_offset_y; class=class="str">"cmt">// Offset of the Y coordinate relative to the cursor class="type">int m_init_x; class=class="str">"cmt">// Newly created form X coordinate class="type">int m_init_y; class=class="str">"cmt">// Newly created form Y coordinate class="type">int m_init_w; class=class="str">"cmt">// Newly created form width class="type">int m_init_h; class=class="str">"cmt">// Newly created form height class=class="str">"cmt">//--- Reset the array size of(class="num">1) text, (class="num">2) rectangular and(class="num">3) geometric animation frames class="type">void ResetArrayFrameT(class="type">void);
「图形容器类的私有骨架与坐标绑定」
在 MT5 自定义图形库里,一个画框容器类往往先把重置和析构相关的两个公开方法挂在头里:ResetArrayFrameQ 与 ResetArrayFrameG,分别负责清空行情帧队列与图形帧队列,避免上一次绘制残留指针。 真正决定边框行为的,是四个 protected 整型成员:m_frame_width_left / right / top / bottom,单位都是像素。实测在 1920×1080 的图表上,若四边各留 4 像素,主图对象实际可绘制区会比裸坐标少 8 像素宽、8 像素高,缩放时容易切边。 依赖对象的命名用了一段拼接逻辑:取当前对象名,跳过 EA 名长度 +1 的字符,再接下划线和 base_name。这意味着同一 EA 下多个实例不会重名,但手动在对象列表里改名会破坏绑定。 CreateAndAddNewElement 是绑定子元素的入口,传入元素类型、主指针、x/y/w/h 后入列。它返回 CGCnvElement*,调用方必须判空——若坐标越界,MT5 可能返回 NULL 而不抛错,这是图形对象泄漏的高发点。
class="type">void ResetArrayFrameQ(class="type">void); class="type">void ResetArrayFrameG(class="type">void); class=class="str">"cmt">//--- Create a new graphical object class="kw">protected: CArrayObj m_list_tmp; class=class="str">"cmt">// List for storing the pointers class="type">int m_frame_width_left; class=class="str">"cmt">// Form frame width to the left class="type">int m_frame_width_right; class=class="str">"cmt">// Form frame width to the right class="type">int m_frame_width_top; class=class="str">"cmt">// Form frame width at the top class="type">int m_frame_width_bottom; class=class="str">"cmt">// Form frame width at the bottom class=class="str">"cmt">//--- Initialize the variables class="type">void Initialize(class="type">void); class="type">void Deinitialize(class="type">void); class=class="str">"cmt">//--- Create a shadow object class="type">void CreateShadowObj(class="kw">const class="type">color colour,class="kw">const class="type">uchar opacity); class=class="str">"cmt">//--- Return the name of the dependent object class="type">class="kw">string CreateNameDependentObject(class="kw">const class="type">class="kw">string base_name) class="kw">const { class="kw">return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+class="num">1)+"_"+base_name; } class=class="str">"cmt">//--- Update coordinates of bound objects class="kw">virtual class="type">bool MoveDependentObj(class="kw">const class="type">int x,class="kw">const class="type">int y,class="kw">const class="type">bool redraw=class="kw">false); class=class="str">"cmt">//--- Create a new bound element and add it to the list of bound objects CGCnvElement *CreateAndAddNewElement(class="kw">const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, 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,
◍ 图形表单的初始坐标与鼠标状态接口
在 MT5 自定义控件类里,表单的初始几何参数必须独立缓存,否则 ChartRedraw 后坐标会漂。下面这组 getter/setter 把创建时的 X、Y、宽、高锁进私有成员 m_init_x / m_init_y / m_init_w / m_init_h,外部只走接口改值。 MouseFormState 接收 id、lparam、dparam、sparam 四个事件参数,返回 ENUM_MOUSE_FORM_STATE 枚举,用来判断光标在表单内、外还是压在边框。配合 MouseCursorX 直接读 m_mouse.CoordX(),能在不重算矩形的前提下拿到光标 X 坐标。 实盘面板若要在鼠标悬停时高亮按钮,靠这两个方法就够了:先判 MouseFormState 再取 MouseCursorX,省去每次遍历所有子控件。外汇与贵金属图表多品种同开时,这种局部坐标查询能明显降低 OnChartEvent 里的 CPU 占用,但高频刷新仍属高风险操作,参数需自测。
class="kw">const class="type">color colour, class="kw">const class="type">uchar opacity, class="kw">const class="type">bool activity); class="kw">public: class=class="str">"cmt">//--- Return the initial(class="num">1) X and(class="num">2) Y coordinate, (class="num">3) form width and(class="num">4) height class="type">int GetCoordXInit(class="type">void) class="kw">const { class="kw">return this.m_init_x; } class="type">int GetCoordYInit(class="type">void) class="kw">const { class="kw">return this.m_init_y; } class="type">int GetWidthInit(class="type">void) class="kw">const { class="kw">return this.m_init_w; } class="type">int GetHeightInit(class="type">void) class="kw">const { class="kw">return this.m_init_h; } class=class="str">"cmt">//--- Set the initial(class="num">1) X and(class="num">2) Y coordinate, (class="num">3) form width and(class="num">4) height class="type">void SetCoordXInit(class="kw">const class="type">int value) { this.m_init_x=value; } class="type">void SetCoordYInit(class="kw">const class="type">int value) { this.m_init_y=value; } class="type">void SetWidthInit(class="kw">const class="type">int value) { this.m_init_w=value; } class="type">void SetHeightInit(class="kw">const class="type">int value) { this.m_init_h=value; } class=class="str">"cmt">//--- Return(class="num">1) the mouse status relative to the form, as well as(class="num">2) X and(class="num">3) Y coordinate of the cursor ENUM_MOUSE_FORM_STATE MouseFormState(class="kw">const class="type">int id,class="kw">const class="type">long lparam,class="kw">const class="type">class="kw">double dparam,class="kw">const class="type">class="kw">string sparam); class="type">int MouseCursorX(class="type">void) class="kw">const { class="kw">return this.m_mouse.CoordX(); }
图形容器里挂元素的几个接口
在 MT5 自定义图形容器类里,鼠标纵坐标直接由 MouseCursorY() 透传内部 m_mouse.CoordY(),返回的是画布坐标系下的整数像素值,方便你做悬浮提示或拖拽锚定。 CreateNewElement() 是个虚函数,参数列得很全:元素类型、主对象指针、x/y/w/h 四个几何整数、colour 与 opacity(0~255 的 uchar)、还有 activity 与 redraw 两个布尔开关。redraw 若为 true,新建完会立刻触发重绘,省去你手动调 ChartRedraw()。 AddNewElement() 比前者轻量,只接对象指针和偏移 x/y,适合把已经构造好的 CGCnvElement 挂到容器里。 属性访问走的是一对对 setter/getter:SetColorFrame() 直接赋值 m_color_frame,ColorFrame() 原样返回;阴影色和阴影透明度则拆成 SetColorShadow()/ColorShadow() 与 SetOpacityShadow(),后两者声明在头文件、实现挪到了别处。 把这些接口抄进你的 EA 面板工程,能在不碰底层 OnChartEvent 的情况下,用十几行代码堆出可交互的浮层。外汇与贵金属图表叠加自定义 UI 属高风险操作环境,参数误用可能导致图表卡顿或逻辑错乱。
class="type">int MouseCursorY(class="type">void) class="kw">const { class="kw">return this.m_mouse.CoordY(); } class=class="str">"cmt">//--- Create a new attached element class="kw">virtual class="type">bool CreateNewElement(class="kw">const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, 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 activity, class="kw">const class="type">bool redraw); class=class="str">"cmt">//--- Add a new attached element class="type">bool AddNewElement(CGCnvElement *obj,class="kw">const class="type">int x,class="kw">const class="type">int y); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Methods of simplified access to object properties | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) get the form frame class="type">color class="type">void SetColorFrame(class="kw">const class="type">color colour) { this.m_color_frame=colour; } class="type">color ColorFrame(class="type">void) class="kw">const { class="kw">return this.m_color_frame; } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the form shadow class="type">color class="type">void SetColorShadow(class="kw">const class="type">color colour); class="type">color ColorShadow(class="type">void) class="kw">const; class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the form shadow opacity class="type">void SetOpacityShadow(class="kw">const class="type">uchar opacity);