繁簡切換您正在訪問的是FX168財經網,本網站所提供的內容及信息均遵守中華人民共和國香港特別行政區當地法律法規。

FX168财经网>人物频道>帖子

图形界面 X: 渲染表格的新功能 (集成编译 9)

作者/dsdkasd 2019-04-17 15:33 0 来源: FX168财经网人物频道


内容

  • 概论

  • 格式化斑马样式

  • 选择和弃选表格行

  • 列标题

  • 相对于列宽调整字符串长度

  • 事件处理

  • 结论


概论

首篇文章 图形界面 I: 函数库结构准备 (第 1 章) 详细研究了函数的目的。您将在每章结尾处找到含有链接的文章列表。从那里, 您还可以下载当前开发阶段的函数库完整版。文件必须位于与存档相同的目录中。

时至今日, CTable 是函数库中所含的最先进类型表格。表格由 OBJ_EDIT 类型的编辑框汇集而成, 但其进一步开发成为问题。例如, 它难以实现通过手动拖动标题边框调整列的大小, 且不能管理表格的各个图形对象的可见区域。这已达到极限。

因此, 在函数库开发的当前阶段, 进而开发 CCanvasTable 类型的表格更为合理。有关渲染表格以前版本和更新的信息, 请参见此处:

  • 图形界面 VII: 表格控件 (第 1 章)

  • 图形界面 X: 函数库的轻松快捷更新 (集成编译 3)


屏幕截图展示了最新版渲染表格的外观。如您所见, 此刻它毫无生气。它只是一个带有数据的表格单元格。可以为单元格指定对齐方法。除了滚动条和窗体大小的自动调整之外, 该表格还没有其它交互性。

 图例. 1. 渲染表格的以前版本。

图例. 1. 渲染表格的以前版本。

为了纠正这种状况, 我们来用新功能补充渲染表格。在当前更新中将包含以下功能:

  • 格式化斑马样式

  • 选择表格行, 再次单击时弃选

  • 添加了列标题, 可在鼠标悬浮和点击时改变颜色

  • 当单元格空间不足时, 则依照文本自动调整列宽

  • 能够通过拖动其边框来改变每列的标题宽度


格式化斑马样式

在最近的一篇文章里已将格式化斑马样式添加到 CTable 表格里。如果表格包含很多单元格, 它有助于更好地表格导航。让我们在渲染表格中实现这种模式。

使用 CCanvasTable::IsZebraFormatRows() 方法启用此模式。它将传递第二个颜色用于样式, 而通常的单元格颜色将使用第一个颜色。

//++//| 创建渲染表格的类                                                    |//++class CCanvasTable : public CElement
  {private:
   //- 格式化斑马样式模式   color             m_is_zebra_format_rows;
   //-public:
   //- 格式化斑马样式   void              IsZebraFormatRows(const color clr)   { m_is_zebra_format_rows=clr;      }
      };

在不同类型的表格中这种风格的可视化方法有所不同。在 CCanvasTable 的情况下, 正常模式中 表格背景 (绘图画板) 完全用普通单元格颜色填充。当斑马样式被激活时, 开始一个循环。每次迭代中计算每行的坐标, 且用两种颜色交替为区域着色。这是利用绘制填充矩形的 FillRectangle() 方法完成的。 

class CCanvasTable : public CElement
  {public:
   //- 绘制表格行的背景   void              DrawRows(void);
  };//++//| 绘制表格行的背景                                                    |//++void CCanvasTable::DrawRows(void)
  {//- 如果禁用格式化斑马样式模式   if(m_is_zebra_format_rows==clrNONE)
     {
      //- 用一种颜色填充画板      m_table.Erase(::ColorToARGB(m_cell_color));      return;
     }//- 标题坐标   int x1=0,x2=m_table_x_size;
   int y1=0,y2=0;//- 格式化斑马样式   for(int r=0; r<m_rows_total; r++)
     {
      //- 计算坐标      y1=(r*m_cell_y_size)-r;
      y2=y1+m_cell_y_size;
      //- 行颜色      uint clr=::ColorToARGB((r%2!=0)? m_is_zebra_format_rows : m_cell_color);
      //- 绘制行背景      m_table.FillRectangle(x1,y1,x2,y2,clr);     }
      }

行颜色可根据您的喜好设置。结果就是, 斑马模式下的渲染表格将如下所示:

 图例. 2. 格式化斑马样式模式下的渲染表格。

图例. 2. 格式化斑马样式模式下的渲染表格。 

 


选择和弃选表格行

行选择将需要其它字段和方法进行存储和设置:

  • 所选行的背景和文本的颜色

  • 索引和文本

class CCanvasTable : public CElement
  {private:
   //- 颜色用于 (1) 背景 (2) 所选行文本   color             m_selected_row_color;
   color             m_selected_row_text_color;
   //- (1) 索引 (2) 所选行文本   int               m_selected_item;
   string            m_selected_item_text;
   //-public:
   //- 返回 (1) 索引 (2) 表格中所选行文本   int               SelectedItem(void)             const { return(m_selected_item);         }
   string            SelectedItemText(void)         const { return(m_selected_item_text);    }
   //-private:
   //- 绘制表格行的背景   void              DrawRows(void);
      };

可以通过 CCanvasTable::SelectableRow() 方法启用/禁用可选行模式:

class CCanvasTable : public CElement
  {private:
   //- 可选行模式   bool              m_selectable_row;
   //-public:
   //- 行选择   void              SelectableRow(const bool flag)       { m_selectable_row=flag;           }
      };

为了选择一行, 需要一个用于绘制用户定义区域的单独方法。下面给出了 CCanvasTable::DrawSelectedRow() 方法的代码。它计算画板上所选区域的坐标, 然后用于 绘制填充矩形。 

class CCanvasTable : public CElement
  {private:
   //- 绘制所选行   void              DrawSelectedRow(void);
  };//++//| 绘制所选行                                                         |//++void CCanvasTable::DrawSelectedRow(void)
  {//- 设置初始坐标以便检查条件   int y_offset=(m_selected_item*m_cell_y_size)-m_selected_item;//- 坐标   int x1=0,x2=0,y1=0,y2=0;//-   x1=0;
   y1=y_offset;
   x2=m_table_x_size;
   y2=y_offset+m_cell_y_size-1;//- 绘制填充矩形   m_table.FillRectangle(x1,y1,x2,y2,::ColorToARGB(m_selected_row_color));  }

一个辅助的 CCanvasTable::TextColor() 方法用来重绘文本, 它会判断单元格中的文本颜色: 

class CCanvasTable : public CElement
  {private:
   //- 返回单元格文本的颜色   uint              TextColor(const int row_index);
  };//++//| 返回单元格文本的颜色                                                 |//++uint CCanvasTable::TextColor(const int row_index)
  {
   uint clr=::ColorToARGB((row_index==m_selected_item)? m_selected_row_text_color : m_cell_text_color);//- 返回标题颜色   return(clr);
      }

若要选择表格行, 需要双击它。这需要 CCanvasTable::OnClickTable() 方法, 它通过 CHARTEVENT_OBJECT_CLICK 标识符在控件的事件处理器中调用。

在方法开始时应通过几个检查。程序会离开方法, 如果:

  • 行选择模式被禁用;

  • 滚动条已激活;

  • 未在表格上点击。 

如果检查通过, 则为了计算点击坐标, 需要获取 距画板边缘的当前偏移量鼠标光标的 Y 坐标。在这之后, 在循环里 判断所点击的行。一旦找到该行, 需要检查当前是否已被选中, 如果是 — 取消选择。如果已选择该行, 则需要存储其索引和第一列中的文本。在循环中完成行搜索后重新绘制表格。发送一条消息, 其中包含:

  • ON_CLICK_LIST_ITEM 事件的标识符

  • 控件的标识符

  • 所选行的索引

  • 所选行的文本。 

class CCanvasTable : public CElement
  {private:
   //- 在元素上点按处理   bool              OnClickTable(const string clicked_object);
  };//++//| 点击控件处理                                                       |//++bool CCanvasTable::OnClickTable(const string clicked_object)
  {//- 如果禁用选择行模式, 离开   if(!m_selectable_row)
      return(false);//- 如果滚动条激活, 离开   if(m_scrollv.ScrollState() || m_scrollh.ScrollState())
      return(false);//- 如果对象名不同, 离开   if(m_table.Name()!=clicked_object)
      return(false);//- 获取 X 和 Y 轴的偏移量   int xoffset=(int)m_table.GetInteger(OBJPROP_XOFFSET);   int yoffset=(int)m_table.GetInteger(OBJPROP_YOFFSET);//- 确定鼠标光标下方的文本编辑框坐标   int y=m_mouse.Y()-m_table.Y()+yoffset;//- 确定点击的行   for(int r=0; r<m_rows_total; r++)
     {      //- 设置初始坐标以便检查条件      int y_offset=(r*m_cell_y_size)-r;
      //- 沿 Y 轴检查状态      bool y_pos_check=(y>=y_offset && y<y_offset+m_cell_y_size);
      //- 如果点击并非此行, 则转至下一个      if(!y_pos_check)
         continue;
      //- 如果点击已选行, 取消选择      if(r==m_selected_item)
        {
         m_selected_item      =WRONG_VALUE;
         m_selected_item_text ="";
         break;
        }
      //- 保存行索引       m_selected_item      =r;
      m_selected_item_text =m_vcolumns[0].m_vrows[r];
      break;
     }//- 表格重绘   DrawTable();//- 发送有关消息   ::EventChartCustom(m_chart_id,ON_CLICK_LIST_ITEM,CElementBase::Id(),m_selected_item,m_selected_item_text);
   return(true);
      }

带有选定行的渲染表格如下方式显示:

图例. 3. 选择和弃选渲染表格一行的演示。

图例. 3. 选择和弃选渲染表格一行的演示。 


 

列标题

任何没有标题的表格都是空白的。标题也将在这种类型的表中绘制, 但会在单独的画板上。为了做到这一点, 在 CCanvasTable 类中包含其它 CRectCanvas 类的实例, 并 创建一个单独的画板创建方法。此方法的代码不会在这里提供: 它几乎与创建表格相同。仅有的区别是预设对象的大小和位置。

class CCanvasTable : public CElement
  {private:
   //- 创建表格的对象   CRectCanvas       m_headers;   //-private:
   bool              CreateHeaders(void);  };

现在研究与列标题相关的属性。它们可在创建表之前进行配置。

  • 表头的显示模式。

  • 标题的大小 (高度)。

  • 不同状态下的标题背景颜色。

  • 标题文本颜色。


与这些属性相关的字段和方法: 

class CCanvasTable : public CElement
  {private:
   //- 表头的显示模式   bool              m_show_headers;
   //- 标题的大小 (高度)   int               m_header_y_size;
   //- 不同状态下的标题 (背景) 颜色   color             m_headers_color;
   color             m_headers_color_hover;
   color             m_headers_color_pressed;
   //- 标题文本颜色   color             m_headers_text_color;
   //-public:
   //- (1) 标题显示模式, (2) 标题高度   void              ShowHeaders(const bool flag)         { m_show_headers=flag;             }
   void              HeaderYSize(const int y_size)        { m_header_y_size=y_size;          }
   //- (1) 背景 (2) 标题文本颜色   void              HeadersColor(const color clr)        { m_headers_color=clr;             }
   void              HeadersColorHover(const color clr)   { m_headers_color_hover=clr;       }
   void              HeadersColorPressed(const color clr) { m_headers_color_pressed=clr;     }
   void              HeadersTextColor(const color clr)    { m_headers_text_color=clr;        }
      };

方法需要 设置标题名称。此外, 还需要一个 数组来保存这些数值。数组的大小等于列数, 并在设置表格大小时使用相同的 CCanvasTable::TableSize() 方法中设置。 

class CCanvasTable : public CElement
  {private:
   //- 标题文本   string            m_header_text[];   //-public:
   //- 设置指定标题的文本   void              SetHeaderText(const int column_index,const string value);  };//++//| 在指定索引处填充标题数组                                              |//++void CCanvasTable::SetHeaderText(const uint column_index,const string value)  {//- 检查列超界   uint csize=::ArraySize(m_vcolumns);
   if(csize<1 || column_index>=csize)
      return;//- 将数值保存到数组中   m_header_text[column_index]=value;
      }

在单元格和标题中的文本对齐将使用通用的 CCanvasTable::TextAlign() 方法进行。在表格单元中沿 X 轴的对齐与标题相匹配, 而 沿 Y 轴的对齐通过传递的数值定义。在此版本里, 标题文本在 Y 轴上将位于中间 — TA_VCENTER, 且单元格的便宜自单元格的顶部边缘进行调整 — TA_TOP。 

class CCanvasTable : public CElement
  {private:
   //- 返回指定列中的文本对齐模式   uint              TextAlign(const int column_index,const uint anchor);
  };//++//| 返回指定列中的文本对齐模式                                            |//++uint CCanvasTable::TextAlign(const int column_index,const uint anchor)
  {
   uint text_align=0;//- 当前列的文本对齐   switch(m_vcolumns[column_index].m_text_align)
     {
      case ALIGN_CENTER :
         text_align=TA_CENTER|anchor;
         break;
      case ALIGN_RIGHT :
         text_align=TA_RIGHT|anchor;
         break;
      case ALIGN_LEFT :
         text_align=TA_LEFT|anchor;
         break;
     }//- 返回对齐类型   return(text_align);
      }

在许多表格以及操作系统环境中, 当光标悬浮在两个标题之间的边界时, 指针会改变。以下截图通过 MetaTrader 5 交易终端工具箱窗口中表格的示例描述了这种情形。如果点击这个新出现的指针, 它将切换更改列宽度的模式。此列的背景颜色也会改变。

图例. 4. 鼠标指针悬浮在标题边界关联处。

图例. 4. 鼠标指针悬浮在标题边界关联处。

 

我们来为所开发的函数库准备相同的图像。文章末尾的附件包含一个文件夹, 内有函数库控件的所有图像。在 Enums.mqh 文件里添加 新的指针标识符至 ENUM_MOUSE_POINTER 枚举, 用于沿 XY 轴的大小改变: 

//++//| 指针类型的枚举                                                      |//++enum ENUM_MOUSE_POINTER
  {
   MP_CUSTOM            =0,
   MP_X_RESIZE          =1,
   MP_Y_RESIZE          =2,
   MP_XY1_RESIZE        =3,
   MP_XY2_RESIZE        =4,   MP_X_RESIZE_RELATIVE =5,   MP_Y_RESIZE_RELATIVE =6,
   MP_X_SCROLL          =7,
   MP_Y_SCROLL          =8,
   MP_TEXT_SELECT       =9  };

需要在 CPointer 类中相应进行添加, 以使该指针类型可在控件的类中使用。 

//++//|                                                      Pointer.mqh |//|                                 版权所有 2015, MetaQuotes 软件公司  |//|                                              https://www.mql5.com |//++//- 资源#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_rs_rel.bmp"#resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_y_rs_rel.bmp"//++//| 创建鼠标光标的类                                                    |//++class CPointer : public CElement
  {private:
   //- 设置鼠标光标的图像   void              SetPointerBmp(void);
  };//++//| 基于光标类型设置光标图标                                              |//++void CPointer::SetPointerBmp(void)
  {
   switch(m_type)
     {
      ...      case MP_X_RESIZE_RELATIVE :         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs_rel.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_x_rs_rel.bmp";
         break;
      case MP_Y_RESIZE_RELATIVE :
         m_file_on  ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs_rel.bmp";
         m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_y_rs_rel.bmp";
         break;
      ...
     }//- 如果指定为自定义类型 (MP_CUSTOM)   if(m_file_on=="" || m_file_off=="")
      ::Print(__FUNCTION__," > 必须为光标设置两个图像");
      }

此处需要附加的字段:

  • 确定拖动标题边框的时刻

  • 为了确定鼠标光标从一个标题区域移动到另一个标题区域的时刻。这需要节省资源, 因此只有当相邻区域的边界交错时才会重新绘制标题。

class CCanvasTable : public CElement
  {private:
   //- 确定鼠标光标从一个标题转换到另一个标题的时刻   int               m_prev_header_index_focus;   //- 拖动标题边框以改变列宽的状态   int               m_column_resize_control;  };

CCanvasTable::HeaderColorCurrent() 方法可以根据当前模式、鼠标光标位置和鼠标左键状态获取标题的当前颜色。焦点覆盖标题 将在 CCanvasTable::DrawHeaders() 方法里判断, 该方法设计用于绘制标题背景, 并将被传递到此作为检查结果。

class CCanvasTable : public CElement
  {private:
   //- 返回当前的标题背景色   uint              HeaderColorCurrent(const bool is_header_focus);
  };//++//| 返回当前的标题背景色                                                 |//++uint CCanvasTable::HeaderColorCurrent(const bool is_header_focus)
  {
   uint clr=clrNONE;//- 如果无焦点   if(!is_header_focus || !m_headers.MouseFocus())
      clr=m_headers_color;
   else     {
      //- 如果鼠标左键按下且未处于列宽改变进程      bool condition=(m_mouse.LeftButtonState() && m_column_resize_control==WRONG_VALUE);
      clr=(condition)? m_headers_color_pressed : m_headers_color_hover;
     }//- 返回标题颜色   return(::ColorToARGB(clr));
      }

CCanvasTable::DrawHeaders() 方法的代码表述如下。此处, 如果鼠标光标不在标题区域, 则 整个画板将以指定的颜色填充。如果焦点在标题上, 那么有必要确定它们当中的哪一个拥有焦点。为此, 需要确定鼠标光标的相对坐标, 并在计算标题坐标的循环中检查每个标题是否处于焦点。另外, 在此还要考虑改变列宽模式。在计算此模式中使用 附加偏移量如果找到焦点, 则必须保存列索引。 

class CCanvasTable : public CElement
  {private:
   //- 自分隔线边界的偏移, 按照更改列宽模式显示鼠标指针   int               m_sep_x_offset;
   //-private:
   //- 绘制标题   void              DrawHeaders(void);
  };//++//| 绘制标题背景                                                       |//++void CCanvasTable::DrawHeaders(void)
  {//- 如果非焦点, 重置标题颜色   if(!m_headers.MouseFocus())
     {
      m_headers.Erase(::ColorToARGB(m_headers_color));      return;
     }//- 检查焦点是否在标题上   bool is_header_focus=false;//- 鼠标光标坐标   int x=0;//- 坐标   int x1=0,x2=0,y1=0,y2=m_header_y_size;//- 得到鼠标光标的相对坐标   if(::CheckPointer(m_mouse)!=POINTER_INVALID)
     {
      //- 获取 X 轴偏移量      int xoffset=(int)m_headers.GetInteger(OBJPROP_XOFFSET);
      //- 确定鼠标光标坐标      x=m_mouse.X()-m_headers.X()+xoffset;
     }//- 清除标题背景   m_headers.Erase(::ColorToARGB(clrNONE,0));//- 考虑到更改列宽模式偏移   int sep_x_offset=(m_column_resize_mode)? m_sep_x_offset : 0;//- 绘制标题背景   for(int i=0; i<m_columns_total; i++)
     {
      //- 计算坐标      x2+=m_vcolumns[i].m_width;
      //- 检查焦点      if(is_header_focus=x>x1+((i!=0)? sep_x_offset : 0) && x<=x2+sep_x_offset)         m_prev_header_index_focus=i;
      //- 绘制标题背景      m_headers.FillRectangle(x1,y1,x2,y2,HeaderColorCurrent(is_header_focus));
      //- 计算下一标题偏移      x1+=m_vcolumns[i].m_width;
     }
      }

一旦绘制了标题背景, 就需要绘制网格 (标题框)。CCanvasTable::DrawHeadersGrid() 方法用于此目的。首先, 绘制通用框, 之后在循环里画 分割线

class CCanvasTable : public CElement
  {private:
   //- 绘制表格标题网格   void              DrawHeadersGrid(void);
  };//++//| 绘制表格标题网格                                                    |//++void CCanvasTable::DrawHeadersGrid(void)
  {//- 网格颜色   uint clr=::ColorToARGB(m_grid_color);//- 坐标   int x1=0,x2=0,y1=0,y2=0;
   x2=m_table_x_size-1;
   y2=m_header_y_size-1;//- 绘制边框   m_headers.Rectangle(x1,y1,x2,y2,clr);//- 分割线   x2=x1=m_vcolumns[0].m_width;
   for(int i=1; i<m_columns_total; i++)
     {
      m_headers.Line(x1,y1,x2,y2,clr);      x2=x1+=m_vcolumns[i].m_width;
     }
      }

最后, 绘制标题文本。此任务用 CCanvasTable::DrawHeadersText() 方法执行。在此, 需要循环查看每个标题, 在每次迭代时确定 文本坐标对齐方式。标题名称作为循环的最后一个操作。相对于列宽调整文本也在此进行。CCanvasTable::CorrectingText() 方法 即用于此目的。在本文的下一节将对其进行更详细的描述。 

class CCanvasTable : public CElement
  {private:
   //- 绘制表格标题文本   void              DrawHeadersText(void);
  };//++//| 绘制表格标题文本                                                    |//++void CCanvasTable::DrawHeadersText(void)
  {//- 计算坐标和偏移   int x=0,y=m_header_y_size/2;
   int column_offset =0;
   uint text_align   =0;//- 文本颜色   uint clr=::ColorToARGB(m_headers_text_color);//- 字体属性   m_headers.FontSet(CElementBase::Font(),-CElementBase::FontSize()*10,FW_NORMAL);//-绘制文本   for(int c=0; c<m_columns_total; c++)
     {
      //- 获取文本的 X 坐标      x=TextX(c,column_offset);      //- 获取文本的对齐模式      text_align=TextAlign(c,TA_VCENTER);      //- 绘制列名称      m_headers.TextOut(x,y,CorrectingText(c,0,true),clr,text_align);
     }
      }

所有列出的绘制标题的方法均在通用的 CCanvasTable::DrawTableHeaders() 方法里调用。如果标题显示模式被禁用, 则禁止进入该方法。 

class CCanvasTable : public CElement
  {private:
   //- 绘制表格标题   void              DrawTableHeaders(void);
  };//++//| 绘制表格标题                                                       |//++void CCanvasTable::DrawTableHeaders(void)
  {//- 如果禁用标题, 离开   if(!m_show_headers)      return;//- 绘制标题   DrawHeaders();//- 绘制网格   DrawHeadersGrid();//- 绘制标题文本   DrawHeadersText();
      }

利用 CCanvasTable::CheckHeaderFocus() 方法检查焦点是否在标题上。程序在两种情况下会离开方法:

  • 如果标题显示模式被禁用

  • 或者如果更改列宽的过程已经开始。

此后, 获取光标在画板上的相对坐标。循环 搜索任何标题上的焦点 以及 检查自最后一次调用该方法后是否有变化。如果注册了新焦点 (标题边界交错的时刻), 则 需要重置之前保存的标题索引 并停止循环。

class CCanvasTable : public CElement
  {private:
   //- 检查标题上的焦点   void              CheckHeaderFocus(void);
  };//++//| 检查标题上的焦点                                                    |//++void CCanvasTable::CheckHeaderFocus(void)
  {//- 如果 (1) 标题被禁用 (2) 列宽变更已开始, 离开   if(!m_show_headers || m_column_resize_control!=WRONG_VALUE)
      return;//- 标题坐标   int x1=0,x2=0;//- 获取 X 轴偏移量   int xoffset=(int)m_headers.GetInteger(OBJPROP_XOFFSET);//- 得到鼠标光标的相对坐标   int x=m_mouse.X()-m_headers.X()+xoffset;//- 考虑到更改列宽模式偏移   int sep_x_offset=(m_column_resize_mode)? m_sep_x_offset : 0;//- 搜索焦点   for(int i=0; i<m_columns_total; i++)
     {
      //- 计算右坐标      x2+=m_vcolumns[i].m_width;
      //- 如果标题焦点改变      if((x>x1+sep_x_offset && x<=x2+sep_x_offset) && m_prev_header_index_focus!=i)
        {
         m_prev_header_index_focus=WRONG_VALUE;         break;
        }
      //- 计算左坐标      x1+=m_vcolumns[i].m_width;
     }
      }

与其相对, 只有当边界交错时, 标题才会重绘。这样可以节省 CPU 资源。CCanvasTable::ChangeHeadersColor() 方法设计用于此任务。在此, 如果标题显示模式被禁用, 或正在改变其宽度的过程, 程序将离开该方法。如果方法开头的检查通过, 则检查标题的焦点, 并重绘它们。 

class CCanvasTable : public CElement
  {private:
   //- 改变标题颜色   void              ChangeHeadersColor(void);
  };//++//| 改变标题颜色                                                       |//++void CCanvasTable::ChangeHeadersColor(void)
  {//- 如果禁用标题, 离开   if(!m_show_headers)
      return;//- 如果光标已激活   if(m_column_resize.IsVisible() && m_mouse.LeftButtonState())
     {
      //- 保存拖拽列的索引      if(m_column_resize_control==WRONG_VALUE)
         m_column_resize_control=m_prev_header_index_focus;
      //-      return;
     }//- 非焦点   if(!m_headers.MouseFocus())
     {
      //- 如果尚未指出不在焦点      if(m_prev_header_index_focus!=WRONG_VALUE)
        {
         //- 重置焦点         m_prev_header_index_focus=WRONG_VALUE;
         //- 改变颜色         DrawTableHeaders();
         m_headers.Update();
        }
     }//- 若在焦点   else     {
      //- 检查标题的焦点      CheckHeaderFocus();
      //- 如果无焦点      if(m_prev_header_index_focus==WRONG_VALUE)
        {
         //- 改变颜色         DrawTableHeaders();
         m_headers.Update();
        }
     }
      }

以下是 CCanvasTable::CheckColumnResizeFocus() 方法的代码。需要确定标题间边界的焦点, 并负责在更改列宽时显示/隐藏光标。在方法开始时有两次检查。如果列宽改变模式被禁用, 程序将离开该方法。如果启用并正在更改列宽, 则需要更新鼠标光标坐标并离开该方法。

如果更改列宽度的过程尚未开始, 那么如果光标位于标题区域中, 则 尝试在循环中确定其中一个 的边框上的焦点。如果发现焦点, 更新鼠标光标坐标, 令其可见 并离开方法。如果焦点未发现, 则 指针将被隐藏。 

class CCanvasTable : public CElement
  {private:
   //- 检查标题边框上的焦点以更改其宽度   void              CheckColumnResizeFocus(void);
  };//++//| 检查标题边框上的焦点以更改其宽度                                       |//++void CCanvasTable::CheckColumnResizeFocus(void)
  {//- 如果禁用更改列宽模式, 离开   if(!m_column_resize_mode)
      return;//- 如果开始更改列宽, 离开   if(m_column_resize_control!=WRONG_VALUE)
     {
      //- 更新光标坐标并令其可见      m_column_resize.Moving(m_mouse.X(),m_mouse.Y());
      return;
     }//- 检查标题边框上的焦点   bool is_focus=false;//- 如果鼠标光标位于标题区域   if(m_headers.MouseFocus())
     {
      //- 标题坐标      int x1=0,x2=0;
      //- 获取 X 轴偏移量      int xoffset=(int)m_headers.GetInteger(OBJPROP_XOFFSET);
      //- 得到鼠标光标的相对坐标      int x=m_mouse.X()-m_headers.X()+xoffset;
      //- 搜索焦点      for(int i=0; i<m_columns_total; i++)
        {
         //- 计算坐标         x1=x2+=m_vcolumns[i].m_width;
         //- 验证焦点         if(is_focus=x>x1-m_sep_x_offset && x<=x2+m_sep_x_offset)            break;
        }
      //- 如果这是焦点      if(is_focus)
        {         //- 更新光标坐标并令其可见         m_column_resize.Moving(m_mouse.X(),m_mouse.Y());
         //- 显示光标         m_column_resize.Show();
         return;
        }
     }//- 如果非焦点, 隐藏指针   if(!m_headers.MouseFocus() || !is_focus)
      m_column_resize.Hide();  }

最终结果如下:

 图例. 5. 列标题。

图例. 5. 列标题。

 

 


相对于列宽调整字符串长度

此前, 若要使文本不与相邻单元格重叠, 必须手动选择列宽并重新编译文件来查看结果。很自然, 这极不方便。

我们让字符串长度自动调整, 如果它不适合表格单元格。重新绘制表格时, 以前调整过的字符串将不会再次调整。将另一个数组添加到表格属性的结构中 以便保存这些字符串。

class CCanvasTable : public CElement
  {private:
   //- 表格数值和属性的数组   struct CTOptions
     {
      string            m_vrows[];
      string            m_text[];      int               m_width;
      ENUM_ALIGN_MODE   m_text_align;
     };
   CTOptions         m_vcolumns[];
      };

作为结果, m_vrows[] 将保存全部文本, 而 m_text[] 数组将保存调整版本的文本。

CCanvasTable::CorrectingText() 方法将负责调整 标题和表格单元两者内的 字符串长度。在识别操作的文本之后, 获取其宽度。接下来, 检查字符串的全文是否适合单元格, 同时考虑单元格边缘的所有偏移量。如果它适合, 将之保存在 m_text[] 数组里并离开方法。在当前版本中, 仅保存调整过的单元格文本, 但不针对标题。

如果文本不合适, 则 应修剪多余的字符, 并添加省略号 ('…')。省略号将表明所显示的文本已被裁剪。这个过程很容易实现:

1)获取字符串长度。

2)之后在循环里从最后一个字符开始遍历所有字符, 删除最后一个字符并将修剪后的文本保存在临时变量中。

如果没有字符剩下, 返回一个空字符串。

4)只要有字符剩下, 获取包含省略号在内的结果字符串宽度。

5)检查依次形成的字符串是否适合表格单元, 同时考虑自单元格边缘指定的偏移。

6)如果字符串合适, 则将其保存到方法的局部变量中并停止循环。

7)此后, 将调整后的字符串保存到 m_text[] 数组中, 并从方法返回。 

class CCanvasTable : public CElement
  {private:
   //- 返回依据列宽调整的文本   string            CorrectingText(const int column_index,const int row_index,const bool headers=false);
  };//++//| 返回依据列宽调整的文本                                               |//++string CCanvasTable::CorrectingText(const int column_index,const int row_index,const bool headers=false)
  {//- 获取当前文本   string corrected_text=(headers)? m_header_text[column_index]: m_vcolumns[column_index].m_vrows[row_index];//- 自单元格边缘在 X 轴上的偏移   int x_offset=m_text_x_offset*2;//- 获取画板对象的指针   CRectCanvas *obj=(headers)? ::GetPointer(m_headers) : ::GetPointer(m_table);//- 获取文本宽度   int full_text_width=obj.TextWidth(corrected_text);//- 如果它适合单元格, 将调整后的文本保存在单独的数组中并返回   if(full_text_width<=m_vcolumns[column_index].m_width-x_offset)
     {
      //- 如果这些不是标题, 保存调整后的文本      if(!headers)
         m_vcolumns[column_index].m_text[row_index]=corrected_text;
      //-      return(corrected_text);
     }//- 如果文本不适合单元格, 则需要调整文本 (修剪过多字符并添加省略号)   else     {      //- 用来操纵字符串      string temp_text="";
      //- 获取字符串长度      int total=::StringLen(corrected_text);
      //- 从字符串中逐个删除字符, 直到所需文本宽度      for(int i=total-1; i>=0; i)
        {
         //- 删除一个字符         temp_text=::StringSubstr(corrected_text,0,i);
         //- 如果什么都没剩下, 保留空字符串         if(temp_text=="")
           {
            corrected_text="";
            break;
           }
         //- 在检查之前添加省略号         int text_width=obj.TextWidth(temp_text+"...");
         //- 如果适合单元格         if(text_width<m_vcolumns[column_index].m_width-x_offset)
           {
            //- 保存文本并停止循环            corrected_text=temp_text+"...";
            break;
           }
        }
     }//- 如果这些不是标题, 保存调整后的文本   if(!headers)
      m_vcolumns[column_index].m_text[row_index]=corrected_text;//- 返回调整后的文字   return(corrected_text);
      }

当重绘表格时使用调整后的字符串, 在更改列宽过程中尤为重要。不必在所有表格单元格中反复替换调整的文本, 只需在列单元格中进行此操作, 宽度将会被改变。这样可以节省 CPU 资源。

CCanvasTable::Text() 方法将确定是否需要调整指定列的文本, 或者发送以前调整的版本即可足矣。其代码如下所见: 

class CCanvasTable : public CElement
  {private:
   //- 返回文本   string            Text(const int column_index,const int row_index);
  };//++//| 返回文本                                                           |//++string CCanvasTable::Text(const int column_index,const int row_index)
  {
   string text="";//- 如果不是更改列宽模式, 调整文本   if(m_column_resize_control==WRONG_VALUE)
      text=CorrectingText(column_index,row_index);//- 如果处于更改列宽度模式下, 则...   else     {
      //- ...仅宽度改变的列需要调整文本      if(column_index==m_column_resize_control)
         text=CorrectingText(column_index,row_index);
      //- 对于所有其它, 使用以前调整的文字      else         text=m_vcolumns[column_index].m_text[row_index];
     }//- 返回文本   return(text);
      }

以下是 CCanvasTable::ChangeColumnWidth() 方法的代码, 设计用于改变列宽。
 

最小列宽设为 30 像素。如果标题显示被禁用, 程序将离开方法。如果检查通过, 则在标题的边界处检查焦点。如果检查确定进程尚未开始/完成, 辅助变量为零, 程序离开方法。如果进程正在运行, 则获取光标的相对 X 坐标。如果 进程恰好刚刚开始, 则必须保存当前光标的 X 坐标 (x_fixed 变量) 和所拖拽列的宽度 (prev_width 变量)。涉及此目的的局部变量是静态的。因此, 每次进入此方法时, 它们的值将会保存, 直到进程完成为止。 

现在, 计算列的新宽度。如果结果达到 最小列宽度, 程序将离开方法。否则, 指定列的新宽度保存在表格属性的结构中。此后, 重新计算并重新应用表格维度, 并在方法结束时重新绘制表格。 

class CCanvasTable : public CElement
  {private:
   //- 列的最小宽度   int               m_min_column_width;   //-private:
   //- 更改拖拽列的宽度   void              ChangeColumnWidth(void);
  };//++//| 构造器                                                            |//++CCanvasTable::CCanvasTable(void) : m_min_column_width(30)  {
   ...
  }//++//| 更改拖拽列的宽度                                                    |//++void CCanvasTable::ChangeColumnWidth(void)
  {//- 如果禁用标题, 离开   if(!m_show_headers)
      return;//- 检查标题边界的焦点   CheckColumnResizeFocus();//- 辅助变量   static int x_fixed    =0;
   static int prev_width =0;//- 如果完成, 重置该值   if(m_column_resize_control==WRONG_VALUE)     {
      x_fixed    =0;
      prev_width =0;
      return;
     }//- 获取 X 轴偏移量   int xoffset=(int)m_headers.GetInteger(OBJPROP_XOFFSET);//- 得到鼠标光标的相对坐标   int x=m_mouse.X()-m_headers.X()+xoffset;//- 如果更改列宽的过程刚刚开始   if(x_fixed<1)     {
      //- 保存当前列的 X 坐标和宽度      x_fixed    =x;
      prev_width =m_vcolumns[m_column_resize_control].m_width;
     }//- 计算列的新宽度   int new_width=prev_width+(x-x_fixed);//- 如果小于指定的极限, 保持不变   if(new_width<m_min_column_width)
      return;//- 保存列的新宽度   m_vcolumns[m_column_resize_control].m_width=new_width;//- 计算表格大小   CalculateTableSize();//- 调整表的大小   ChangeTableSize();//- 表格重绘   DrawTable();
      }

结果如下:

 图例. 5. 相对于列的可变宽度调整字符串长度。

图例. 5. 相对于列的可变宽度调整字符串长度。 

 


事件处理

表格对象的颜色管理更变列宽度 是通过控件的鼠标移动事件 (CHARTEVENT_MOUSE_MOVE) 进行处理。 

//++//| 事件处理器                                                         |//++void CCanvasTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {//- 处理光标移动事件   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //- 如果控件被隐藏, 离开      if(!CElementBase::IsVisible())
         return;
      //- 如果子窗口的数字不匹配, 离开      if(!CElementBase::CheckSubwindowNumber())
         return;
      //- 检查焦点覆盖在元素上      CElementBase::CheckMouseFocus();
      m_headers.MouseFocus(m_mouse.X()>m_headers.X() && m_mouse.X()<m_headers.X2() &&
                           m_mouse.Y()>m_headers.Y() && m_mouse.Y()<m_headers.Y2());
      //- 如果滚动条处于激活状态      if(m_scrollv.ScrollBarControl() || m_scrollh.ScrollBarControl())
        {
         ShiftTable();
         return;
        }
      //- 更改对象颜色      ChangeObjectsColor();      //- 更改拖拽列的宽度      ChangeColumnWidth();      return;
     }
   ...
      }

需要另一个新的事件标识符, 以便确定鼠标左键状态的变更时刻。需要在事件处理器程序代码的多个模块中摆脱重复检查和并发处理。将 ON_CHANGE_MOUSE_LEFT_BUTTON 标识符添加到 Define.mqh 文件: 

//++//|                                                      Defines.mqh |//|                                 版权所有 2015, MetaQuotes 软件公司|//|                                              https://www.mql5.com |//++
  #define ON_CHANGE_MOUSE_LEFT_BUTTON (33) // 改变鼠标左键的状态

此外, CMouse::CheckChangeLeftButtonState() 方法已经添加到类中以获取鼠标的当前参数 (CMouse)。它可以确定鼠标左键状态的变化时刻。该方法在类的处理器中调用。如果鼠标左键的状态发生变化, 方法发送消息 ON_CHANGE_MOUSE_LEFT_BUTTON 标识符。消息随后可在任何控件中被接收并处理。 

//++//| 获取鼠标参数的类                                                    |//++class CMouse
  {private:
   //- 检查鼠标左键的状态变化   bool              CheckChangeLeftButtonState(const string mouse_state);
  };//++//| 处理移动鼠标光标的事件                                               |//++void CMouse::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {//- 处理光标移动事件   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //- 坐标和鼠标左键的状态      m_x                 =(int)lparam;
      m_y                 =(int)dparam;
      m_left_button_state =CheckChangeLeftButtonState(sparam);      ...
     }
  }//++//| 检查鼠标左键的状态变化                                               |//++bool CMouse::CheckChangeLeftButtonState(const string mouse_state)
  {
   bool left_button_state=(bool)int(mouse_state);//- 发送关于鼠标左键状态更改的消息   if(m_left_button_state!=left_button_state)
      ::EventChartCustom(m_chart.ChartId(),ON_CHANGE_MOUSE_LEFT_BUTTON,0,0.0,"");//-   return(left_button_state);
      }

所需的 ON_CHANGE_MOUSE_LEFT_BUTTON 标识符的事件处理在 CCanvasTable 类之中:

  •  将类中的某些字段归零;

  •  调整滚动条;

  •  重绘表格

//++//| 事件处理器                                                         |//++void CCanvasTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {//- 更改鼠标左键的状态   if(id==CHARTEVENT_CUSTOM+ON_CHANGE_MOUSE_LEFT_BUTTON)
     {
      //- 如果禁用标题, 离开      if(!m_show_headers)
         return;
      //- 如果鼠标左键释放      if(!m_mouse.LeftButtonState())
        {
         //- 重置宽度更改模式         m_column_resize_control=WRONG_VALUE;
         //- 隐藏光标         m_column_resize.Hide();
         //- 考虑最近变化来调整滚动条         HorizontalScrolling(m_scrollh.CurrentPos());        }
      //- 重置标题上最后一个焦点的索引      m_prev_header_index_focus=WRONG_VALUE;
      //- 更改对象颜色      ChangeObjectsColor();     }
      }

本文的动画截图展示了 MQL 应用程序的运行结果, 可从下面的链接下载, 以便进一步学习。

 

结论

函数库的当前更新改进了 CCanvasTable 类型的渲染表格。这不是表格的最终版本。它将进一步开发, 并将添加新的功能。

创建图形界面函数库的当前规划图如下所示。

 图例. 6. 函数库结构的当前开发状态。

图例. 6. 函数库结构的当前开发状态。

 

您可从下面下载最新版本的函数库和文件进行测试。

如果您在使用这些文件中提供的材料时有任何疑问, 可以参考函数库开发系列文章之一的详细描述, 或在本文的评论中提出您的问题。  


分享到:
举报财经168客户端下载

全部回复

0/140

投稿 您想发表你的观点和看法?

更多人气分析师

  • 张亦巧

    人气2184文章4145粉丝45

    暂无个人简介信息

  • 梁孟梵

    人气2176文章3177粉丝39

    qq:2294906466 了解群指导添加微信mfmacd

  • 指导老师

    人气1864文章4423粉丝52

    暂无个人简介信息

  • 李冉晴

    人气2320文章3821粉丝34

    李冉晴,专业现贷实盘分析师。

  • 王启蒙现货黄金

    人气296文章3135粉丝8

    本人做分析师以来,并专注于贵金属投资市场,尤其是在现货黄金...

  • 张迎妤

    人气1896文章3305粉丝34

    个人专注于行情技术分析,消息面解读剖析,给予您第一时间方向...

  • 金泰铬J

    人气2328文章3925粉丝51

    投资问答解咨询金泰铬V/信tgtg67即可获取每日的实时资讯、行情...

  • 金算盘

    人气2696文章7761粉丝125

    高级分析师,混过名校,厮杀于股市和期货、证券市场多年,专注...

  • 金帝财神

    人气4760文章8329粉丝119

    本文由资深分析师金帝财神微信:934295330,指导黄金,白银,...

FX168财经

FX168财经学院

FX168财经

FX168北美