Русский 中文 Español Deutsch 日本語 Português
Graphical Interfaces V: The List View Element (Chapter 2)

Graphical Interfaces V: The List View Element (Chapter 2)

MetaTrader 5Examples | 17 May 2016, 10:50
8 764 5
Anatoli Kazharski
Anatoli Kazharski

Contents

 


Introduction

The first article Graphical Interfaces I: Preparation of the Library Structure (Chapter 1) explains in detail what this library is for. You will find a list of articles with links at the end of each chapter. There, you can also download a complete version of the library at the current stage of development. The files must be placed in the same directories as they are located in the archive.   

In the previous chapter, we wrote classes for creating vertical and horizontal scrollbars. In this chapter, we will implement them. We will write a class for creating the list view element, a compound part of which will be a vertical scrollbar. We will also demonstrate the implementation of the mechanism for automatic scrolling of the list view when holding the scrollbar button down. At the end, we will test everything using a real example of an MQL application.

 


The List View Element

The element of the graphical interface list view gives the user a choice of several options. The total number of the list view items and the number of items in its visible part can differ when the total number is too big and does not fit the highlighted working part of the interface. In such cases a scrollbar is used. 

We will compose a list view of several primitive objects and an include element. They are:

  1. List view background.
  2. Array of the list view items.
  3. The vertical scrollbar control.



Fig. 1. Compound parts of the list view element.

 

Below, we are going to consider the development of the class for creating the list view element.

 


Developing the Class for Creating the Element

To create the element and embed this into the library under development, we need to create a file with the CListView class of the element. In our case it is ListView.mqh. Then we need to include it in the WndContainer.mqh file:

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "ListView.mqh"

Similar to the classes of controls that have been considered in the previous articles of this series, the CListView class has a standard set of methods. To use a scrollbar in this element, the Scrolls.mqh file must be included in the ListView.mqh file. 

//+------------------------------------------------------------------+
//|                                                     ListView.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
#include "Scrolls.mqh"
//+------------------------------------------------------------------+
//| Class for creating a list view                                   |
//+------------------------------------------------------------------+
class CListView : public CElement
  {
private:
   //--- Pointer to the form to which the element is attached
   CWindow          *m_wnd;
   //---
public:
                     CListView(void);
                    ~CListView(void);
   //--- (1) Stores the form pointer
   void              WindowPointer(CWindow &object)                      { m_wnd=::GetPointer(object);       }
   //---
public:
   //--- Chart event handler
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Timer
   virtual void      OnEventTimer(void);
   //--- Moving the element
   virtual void      Moving(const int x,const int y);
   //--- (1) Show, (2) hide, (3) reset, (4) delete
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) Set, (2) reset of priorities for the left mouse button
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CListView::CListView(void)
  {
//--- Store the name of the element class in the base class
   CElement::ClassName(CLASS_NAME);
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CListView::~CListView(void)
  {
  }

Methods for setting properties of primitive objects (compound parts of the list view) will be required before their creation.

  • Height of the list view items.
  • Color of the frame of the list view background.
  • Color of the item background in different states.
  • Color of the item text in different states.

Values of the above properties are set in the class constructor. 

class CListView : public CElement
  {
private:
   //--- Properties of the list view background
   int               m_area_zorder;
   color             m_area_border_color;
   //--- Properties of the list view items
   int               m_item_zorder;
   int               m_item_y_size;
   color             m_item_color;
   color             m_item_color_hover;
   color             m_item_color_selected;
   color             m_item_text_color;
   color             m_item_text_color_hover;
   color             m_item_text_color_selected;
   //---
public:
   //--- Height of the item
   void              ItemYSize(const int y_size)                         { m_item_y_size=y_size;             }
   //--- Color of the background frame
   void              AreaBorderColor(const color clr)                    { m_area_border_color=clr;          }
   //--- Color of the list view items in different states
   void              ItemColor(const color clr)                          { m_item_color=clr;                 }
   void              ItemColorHover(const color clr)                     { m_item_color_hover=clr;           }
   void              ItemColorSelected(const color clr)                  { m_item_color_selected=clr;        }
   void              ItemTextColor(const color clr)                      { m_item_text_color=clr;            }
   void              ItemTextColorHover(const color clr)                 { m_item_text_color_hover=clr;      }
   void              ItemTextColorSelected(const color clr)              { m_item_text_color_selected=clr;   }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CListView::CListView(void) : m_item_y_size(18),
                             m_area_border_color(C'235,235,235'),
                             m_item_color(clrWhite),
                             m_item_color_hover(C'240,240,240'),
                             m_item_color_selected(C'51,153,255'),
                             m_item_text_color(clrBlack),
                             m_item_text_color_hover(clrBlack),
                             m_item_text_color_selected(clrWhite)
  {
//--- Set priorities of the left mouse button click
   m_area_zorder =1;
   m_item_zorder =2;
  }

There are three private and one public methods for creating the objects of the list view. An array of class instances of the CEdit type has to be declared for the creating list view items. This array can be used for creating graphical objects of the OBJ_EDIT type (entry field). 

class CListView : public CElement
  {
private:
   //--- Objects for creating the list view
   CRectLabel        m_area;
   CEdit             m_items[];
   CScrollV          m_scrollv;
   //---
public:
   //--- Methods for creating the list view
   bool              CreateListView(const long chart_id,const int window,const int x,const int y);
   //---
private:
   bool              CreateArea(void);
   bool              CreateList(void);
   bool              CreateScrollV(void);
  };

The default size of the list view and its visible part is equal to two elements as there is no point in creating a list view containing only one item. To set the size of the list view and its visible part, let us create the CListView::ListSize() and CListView::VisibleListSize() methods, checking that the number of items is not less than two.  

class CListView : public CElement
  {
private:
   //--- Array of the list view values
   string            m_value_items[];
   //--- Size of the list view and its visible part
   int               m_items_total;
   int               m_visible_items_total;
   //---
public:
   //--- Returns the size of (1) the list view and (2) its visible part
   int               ItemsTotal(void)                              const { return(m_items_total);            }
   int               VisibleItemsTotal(void)                       const { return(m_visible_items_total);    }
   //--- Set the size of (1) the list view and (2) its visible part
   void              ListSize(const int items_total);
   void              VisibleListSize(const int visible_items_total);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CListView::CListView(void) : m_items_total(2),
                             m_visible_items_total(2)
  {
//--- Set the size of the list view and its visible part
   ListSize(m_items_total);
   VisibleListSize(m_visible_items_total);
  }
//+------------------------------------------------------------------+
//| Sets the size of the list view                                   |
//+------------------------------------------------------------------+
void CListView::ListSize(const int items_total)
  {
//--- No point to make a list view shorter than two items
   m_items_total=(items_total<2) ? 2 : items_total;
   ::ArrayResize(m_value_items,m_items_total);
  }
//+------------------------------------------------------------------+
//| Sets the size of the visible part of the list view               |
//+------------------------------------------------------------------+
void CListView::VisibleListSize(const int visible_items_total)
  {
//--- No point to make a list view shorter than two items
   m_visible_items_total=(visible_items_total<2) ? 2 : visible_items_total;
   ::ArrayResize(m_items,m_visible_items_total);
  }

Relevant methods are required for storing and getting the index and the text of the highlighted item in the list view. The first item of the list view will be highlighted by default. If another item has to be highlighted after the list view has been created, use the CListView::SelectedItemIndex() method. You will need to specify the item index before the creation of the list view and after the number of the elements has been defined.

class CListView : public CElement
  {
private:
   //--- (1) Index and (2) the text of the highlighted item
   int               m_selected_item_index;
   string            m_selected_item_text;
   //---
public:
   //--- Returns/stores (1) the index and (2) the text in the highlighted item in the list view
   void              SelectedItemIndex(const int index);
   int               SelectedItemIndex(void)                       const { return(m_selected_item_index);    }
   void              SelectedItemText(const string text)                 { m_selected_item_text=text;        }
   string            SelectedItemText(void)                        const { return(m_selected_item_text);     }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CListView::CListView(void) : m_selected_item_index(0),
                             m_selected_item_text("")
  {
//--- ...
  }
//+------------------------------------------------------------------+
//| Storing the index                                                |
//+------------------------------------------------------------------+
void CListView::SelectedItemIndex(const int index)
  {
//--- Adjustment in case the range has been exceeded
   m_selected_item_index=(index>=m_items_total)? m_items_total-1 : (index<0)? 0 : index;
  }

After the list view is created and also at the moment of selecting an item, the item must be highlighted with a different color. Let us write the CListView::HighlightSelectedItem() method for that. At the beginning of this method, there is a check for the current state of the scrollbar. If it is active and the slider can move, then the program leaves the method. If the check has been passed, then get the current position of the slider in the list view. The obtained value will be the starting one for the counter in the loop. With the help of that loop we can identify what element to highlight.

class CListView : public CElement
  {
public:
   //--- Highlighting of the selected item
   void              HighlightSelectedItem(void);
  };
//+------------------------------------------------------------------+
//| Highlights selected item                                         |
//+------------------------------------------------------------------+
void CListView::HighlightSelectedItem(void)
  {
//--- Leave, if the scrollbar is active
   if(m_scrollv.ScrollState())
      return;
//--- Get the current position of the scrollbar slider
   int v=m_scrollv.CurrentPos();
//--- Iterate over the visible part of the list view
   for(int r=0; r<m_visible_items_total; r++)
     {
      //--- If inside the range of the list view
      if(v>=0 && v<m_items_total)
        {
         //--- Changing the background color and the text color
         m_items[r].BackColor((m_selected_item_index==v) ? m_item_color_selected : m_item_color);
         m_items[r].Color((m_selected_item_index==v) ? m_item_text_color_selected : m_item_text_color);
         //--- Increase the counter
         v++;
        }
     }
  }

When the list items are created in the CListView::CreateList() method, coordinates and width are calculated so that they do not obstruct the frame of the list view background. The width of the items is calculated considering whether the list view will have a scrollbar. All the items below the first one in the list view are set one on top of the other with the overlapping of one pixel. This is necessary to exclude gaps in two pixels which will be visible when the items are highlighted when the mouse cursor is hovering over them. We need to take it into consideration when calculating the height of the list view background and the scrollbar. After the creation of all the elements, at the end of the method the selected item is highlighted and its text is stored

//+------------------------------------------------------------------+
//| Creates the list view items                                      |
//+------------------------------------------------------------------+
bool CListView::CreateList(void)
  {
//--- Coordinates
   int x =CElement::X()+1;
   int y =0;
//--- Calculating the width of the list view items
   int w=(m_items_total>m_visible_items_total) ? CElement::XSize()-m_scrollv.ScrollWidth() : CElement::XSize()-2;
//---
   for(int i=0; i<m_visible_items_total; i++)
     {
      //--- Forming the object name
      string name=CElement::ProgramName()+"_listview_edit_"+(string)i+"__"+(string)CElement::Id();
      //--- Calculating the Y coordinate
      y=(i>0) ? y+m_item_y_size-1 : CElement::Y()+1;
      //--- Creating the object
      if(!m_items[i].Create(m_chart_id,name,m_subwin,x,y,w,m_item_y_size))
         return(false);
      //--- Setting the properties
      m_items[i].Description(m_value_items[i]);
      m_items[i].TextAlign(m_align_mode);
      m_items[i].Font(FONT);
      m_items[i].FontSize(FONT_SIZE);
      m_items[i].Color(m_item_text_color);
      m_items[i].BackColor(m_item_color);
      m_items[i].BorderColor(m_item_color);
      m_items[i].Corner(m_corner);
      m_items[i].Anchor(m_anchor);
      m_items[i].Selectable(false);
      m_items[i].Z_Order(m_item_zorder);
      m_items[i].ReadOnly(true);
      m_items[i].Tooltip("\n");
      //--- Coordinates
      m_items[i].X(x);
      m_items[i].Y(y);
      //--- Size
      m_items[i].XSize(w);
      m_items[i].YSize(m_item_y_size);
      //--- Margins from the edge of the panel
      m_items[i].XGap(x-m_wnd.X());
      m_items[i].YGap(y-m_wnd.Y());
      //--- Store the object pointer
      CElement::AddToArray(m_items[i]);
     }
//--- Highlighting the selected item
   HighlightSelectedItem();
//--- Store the text of the selected item
   m_selected_item_text=m_value_items[m_selected_item_index];
   return(true);
  }

When creating a scrollbar in the CListView::CreateScrollV() method, at the very beginning a ratio of the number of items of the whole list view to its visible part is checked. If the total number of items is less or equal to the number of items in its visible part, then there is no point in continuing and the program leaves the method. Then (1) the form pointer is stored, (2) coordinates are calculated and (3) properties are set. The element identifier for the scrollbar must be the same as its parent element. The modes of the drop-down element must be the same too.

//+------------------------------------------------------------------+
//| Creates the vertical scrollbar                                   |
//+------------------------------------------------------------------+
bool CListView::CreateScrollV(void)
  {
//--- If the number of items is greater that the size of the list then
//    set the vertical scrollbar
   if(m_items_total<=m_visible_items_total)
      return(true);
//--- Store the form pointer
   m_scrollv.WindowPointer(m_wnd);
//--- Coordinates
   int x=CElement::X()+m_area.X_Size()-m_scrollv.ScrollWidth();
   int y=CElement::Y();
//--- Set properties
   m_scrollv.Id(CElement::Id());
   m_scrollv.XSize(m_scrollv.ScrollWidth());
   m_scrollv.YSize(CElement::YSize());
   m_scrollv.AreaBorderColor(m_area_border_color);
   m_scrollv.IsDropdown(CElement::IsDropdown());
//--- Creating the scrollbar
   if(!m_scrollv.CreateScroll(m_chart_id,m_subwin,x,y,m_items_total,m_visible_items_total))
      return(false);
//---
   return(true);
  }

The size along the Y axis is calculated in the CListView::CreateListView() main method for creating the list view. As mentioned above, when the size is calculated, the one pixel overlap of the list view items must be considered. Please also bear in mind that the array of items must be located strictly inside the list view background so that it does not obstruct its frame. 

//+------------------------------------------------------------------+
//| Creates the list view                                            |
//+------------------------------------------------------------------+
bool CListView::CreateListView(const long chart_id,const int window,const int x,const int y)
  {
//--- Leave, if there is not a form pointer
   if(::CheckPointer(m_wnd)==POINTER_INVALID)
     {
      ::Print(__FUNCTION__," > Before creating the list view, the class must be passed "
              "the form pointer: CListView::WindowPointer(CWindow &object)");
      return(false);
     }
//--- Initialization of variables
   m_id       =m_wnd.LastId()+1;
   m_chart_id =chart_id;
   m_subwin   =window;
   m_x        =x;
   m_y        =y;
   m_y_size   =m_item_y_size*m_visible_items_total-(m_visible_items_total-1)+2;
//--- Margins from the edge
   CElement::XGap(m_x-m_wnd.X());
   CElement::YGap(m_y-m_wnd.Y());
//--- Creating a button
   if(!CreateArea())
      return(false);
   if(!CreateList())
      return(false);
   if(!CreateScrollV())
      return(false);
//--- Hide the element if it is a dialog window or it is minimized
   if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized())
      Hide();
//---
   return(true);
  }

The library user will decide whether he or she needs the items to be highlighted when the mouse cursor is hovering over them. The highlighting will be disabled by default. As an additional adjustable property, let us create a method for setting text alignment to (1) the left margin, (2) to the right margin or (3) to the center. By default the text will be aligned to the left margin. 

class CListView : public CElement
  {
private:
   //--- Mode of highlighting when the cursor is hovering over
   bool              m_lights_hover;
   //--- Mode of alignment of the text in the list view
   ENUM_ALIGN_MODE   m_align_mode;
   //---
public:
   //--- (1) Mode of highlighting items when hovering the cursor, (2) text alignment
   void              LightsHover(const bool state)                       { m_lights_hover=state;             }
   void              TextAlign(const ENUM_ALIGN_MODE align_mode)         { m_align_mode=align_mode;          }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CListView::CListView(void) : m_lights_hover(false),
                             m_align_mode(ALIGN_LEFT)
  {
//--- ...
  }

Before creating the list view, the data array must be initialized. For that, create the CListView::ValueToList() method where the check of the array size and adjustment of the index in case of exceeding the array size will take place.  

class CListView : public CElement
  {
public:
   //--- Setting the value in the list view by the specified index of the row
   void              ValueToList(const int item_index,const string value);
  };
//+------------------------------------------------------------------+
//| Stores the passed value in the list view by the specified index  |
//+------------------------------------------------------------------+
void CListView::ValueToList(const int item_index,const string value)
  {
   int array_size=::ArraySize(m_value_items);
//--- If there are no items in the context menu, report
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > This method is to be called, "
              "when the list view contains at least one item!");
     }
//--- Adjustment in case the range has been exceeded
   int i=(item_index>=array_size)? array_size-1 : (item_index <0)? 0 : item_index;
//--- Store the value in the list view
   m_value_items[i]=value;
  }

Now, we are going to test the setting of the list view with a vertical scrollbar and then will gradually add all required methods for its management. 



Testing the Setup of the List View

As the ListView.mqh file is already included in the library, the class of the list view element (CListView) is already available to the user in the custom class. Before we test the embedding of the list view, we need to introduce some additions in the CWndContainer class. A list view is a compound element and therefore the addition of the scrollbar pointer to the element base must be ensured. 

For that, let us write the CWndContainer::AddListViewElements() method. A check for the class name will be at the beginning of the method. It the element turns out to be not a list view, the program will leave the method. Then, having increased the size of the common array of pointers and obtained the pointer with the right element type, store it.

class CWndContainer
  {
private:
   //--- Stores the pointers to the list view objects in the base
   bool              AddListViewElements(const int window_index,CElement &object);
  };
//+------------------------------------------------------------------+
//| Stores the pointers to the list view objects in the base         |
//+------------------------------------------------------------------+
bool CWndContainer::AddListViewElements(const int window_index,CElement &object)
  {
//--- Leave, if this is not a list view
   if(object.ClassName()!="CListView")
      return(false);
//--- Get the list view pointer
   CListView *lv=::GetPointer(object);
//--- Increasing the element array
   int size=::ArraySize(m_wnd[window_index].m_elements);
   ::ArrayResize(m_wnd[window_index].m_elements,size+1);
//--- Get the scrollbar pointer
   CScrollV *sv=lv.GetScrollVPointer();
//--- Store the element in the base
   m_wnd[window_index].m_elements[size]=sv;
   return(true);
  }

The CWndContainer::AddListViewElements() method is called in the main public method for adding elements to the base: 

//+------------------------------------------------------------------+
//| Adds the pointer to the element array                            |
//+------------------------------------------------------------------+
void CWndContainer::AddToElementsArray(const int window_index,CElement &object)
  {
//--- If the base does not contain forms for controls
//--- If the request is for a non-existent form
//--- Add to the common array of elements
//--- Add element objects to the common array of objects
//--- Store the id of the last element in all forms
//--- Increase the counter of element identifiers

//--- Stores the pointers to the context menu objects in the base
//--- Stores the pointers to the main menu objects in the base
//--- Stores the pointers to the split button objects in the base
//--- Stores the pointers to the tooltip objects in the base
//--- Stores the pointers to the list view objects in the base
   if(AddListViewElements(window_index,object))
      return;
  }

Let us use the EA from the previous part of the series for testing. We will leave only its main menu together with the context menus and the status bar, other elements have to be deleted. Create a class instance in the custom class, declare the method for creating an element and define the margins from the edge of the form: 

class CProgram : public CWndEvents
  {
private:
   //--- List view
   CListView         m_listview1;
   //--- 
private:
   //--- List view
#define LISTVIEW1_GAP_X       (2)
#define LISTVIEW1_GAP_Y       (43)
   bool              CreateListView1(void);
  };

Below is the code of the method for creating a list view. The list view will contain twenty items. Only ten items will be visible. We are going to enable the highlighting of the items when the mouse cursor is hovering over them. Select the sixth (5) item of the list view. As this is only an example, fill the list with the text «SYMBOL» with the item number

//+------------------------------------------------------------------+
//| Creates list view 1                                              |
//+------------------------------------------------------------------+
bool CProgram::CreateListView1(void)
  {
//--- Size of the list view
#define ITEMS_TOTAL1 20
//--- Store the window pointer
   m_listview1.WindowPointer(m_window1);
//--- Coordinates
   int x=m_window1.X()+LISTVIEW1_GAP_X;
   int y=m_window1.Y()+LISTVIEW1_GAP_Y;
//--- Set properties before creation
   m_listview1.XSize(100);
   m_listview1.LightsHover(true);
   m_listview1.ListSize(ITEMS_TOTAL1);
   m_listview1.VisibleListSize(10);
   m_listview1.AreaBorderColor(clrDarkGray);
   m_listview1.SelectedItemIndex(5);
//--- Get the scrollbar pointer
   CScrollV *sv=m_listview1.GetScrollVPointer();
//--- Properties of the scrollbar
   sv.ThumbBorderColor(C'190,190,190');
   sv.ThumbBorderColorHover(C'180,180,180');
   sv.ThumbBorderColorPressed(C'160,160,160');
//--- Filling the list view with data
   for(int r=0; r<ITEMS_TOTAL1; r++)
      m_listview1.ValueToList(r,"SYMBOL "+string(r));
//--- Create the list view
   if(!m_listview1.CreateListView(m_chart_id,m_subwin,x,y))
      return(false);
//--- Add the element pointer to the base
   CWndContainer::AddToElementsArray(0,m_listview1);
   return(true);
  }

The method for creating a list view must be called in the main method for creating the graphical interface: 

//+------------------------------------------------------------------+
//| Creates the trading panel                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- Creating form 1 for controls
//--- Creating controls:
//    Main menu
//--- Context menus
//--- Creating the status bar
//--- List view
   if(!CreateListView1())
      return(false);
//--- Redrawing the chart
   m_chart.Redraw();
   return(true);
  }

Now, the code can be compiled and the program can be loaded on to the chart. If everything is done correctly, you will see the result as in the screenshot below:

 Fig. 2. Test of setting up the list view element.

Fig. 2. Test of setting up the list view element.

 

It looks good but currently this list view cannot be managed and its objects will not react the mouse cursor. In the next part of the article, we will write methods that will allow us to manage the list view.

 


Methods for Managing the Element

At first, let us create the method that will allow the items to change color when the mouse cursor is hovering over them. Then, we will need a method that will facilitate restoring the default colors.

class CListView : public CElement
  {
public:
   //--- (1) Resetting the color of the list view items, (2) changing the color of the list view items when the cursor is hovering over them
   void              ResetItemsColor(void);
   void              ChangeItemsColor(const int x,const int y);
  };

Resetting the colors of all items except the selected one is carried out in the CListView::ResetItemsColor() method. At the beginning of the method, position of the slider is identified. This value is received in the variable which will be used further in the loop as a counter for identifying the selected item.

//+------------------------------------------------------------------+
//| Resetting the color of the list view items                       |
//+------------------------------------------------------------------+
void CListView::ResetItemsColor(void)
  {
//--- Get the current position of the scrollbar slider
   int v=m_scrollv.CurrentPos();
//--- Iterate over the visible part of the list view
   for(int i=0; i<m_visible_items_total; i++)
     {
      //--- Increase the counter if the list view range has not been exceeded
      if(v>=0 && v<m_items_total)
         v++;
      //--- Skip the selected item
      if(m_selected_item_index==v-1)
         continue;
      //--- Setting the color (background, text)
      m_items[i].BackColor(m_item_color);
      m_items[i].Color(m_item_text_color);
     }
  }

Several checks have to be passed at the beginning of the CListView::ChangeItemsColor() method. The program will leave the method in the following cases:

  • if highlighting the items when the cursor is hovering over them is not enabled;
  • if the scrollbar is under management;
  • if it is not a drop-down element and the form is blocked.

Then, similar to many methods of this class, we get the current position of the scrollbar in the variable. This variable will be used for identifying the selected item in the loop so it is skipped as its color does not need changing. The cursor coordinates will be passed to the method. These coordinates will allow to identify in a loop over which item the mouse is placed. More detailed code of the CListView::ChangeItemsColor() method is presented below. 

//+------------------------------------------------------------------+
//| Changing color of the list view item when the cursor is hovering |
//+------------------------------------------------------------------+
void CListView::ChangeItemsColor(const int x,const int y)
  {
//--- Leave, if the highlighting of the item when the cursor is hovering over it is disabled or the scrollbar is active
   if(!m_lights_hover || m_scrollv.ScrollState())
      return;
//--- Leave, if it is not a drop-down element and the form is blocked
   if(!CElement::IsDropdown() && m_wnd.IsLocked())
      return;
//--- Get the current position of the scrollbar slider
   int v=m_scrollv.CurrentPos();
//--- Identify over which item the cursor is over and highlight it
   for(int i=0; i<m_visible_items_total; i++)
     {
      //--- Increase the counter if the list view range has not been exceeded
      if(v>=0 && v<m_items_total)
         v++;
      //--- Skip the selected item
      if(m_selected_item_index==v-1)
         continue;
      //--- If the cursor is over this item, highlight it
      if(x>m_items[i].X() && x<m_items[i].X2() && y>m_items[i].Y() && y<m_items[i].Y2())
        {
         m_items[i].BackColor(m_item_color_hover);
         m_items[i].Color(m_item_text_color_hover);
        }
      //--- If the cursor is not over this item, assign the color appropriate to its state
      else
        {
         m_items[i].BackColor(m_item_color);
         m_items[i].Color(m_item_text_color);
        }
     }
  }

Now, we need to use the CListView::ChangeItemsColor() method in the event handler of the CListView class:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CListView::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- List hidden
      if(!CElement::IsVisible())
         return;
      //--- Coordinates
      int x=(int)lparam;
      int y=(int)dparam;
      //--- Changes the color of the list view items when the cursor is hovering over it
      ChangeItemsColor(x,y);
      return;
     }
  }

If we compile the program for tests, then when we hover the mouse cursor over the list view items, their color will be changed as shown in the screenshot below:

Fig. 3. Test of changing the color of the list view items when the mouse cursor is hovering over them. 

Fig. 3. Test of changing the color of the list view items when the mouse cursor is hovering over them.

To allow the list view to move together with the slider of the scrollbar, let us write the CListView::ShiftList() method. This method also begins with the storing the current position of the slider in the variable. Similar to the previous method, it will be used as a counter in the loop for storing the selected item in the list view and also for moving the data. See the code below. 

class CListView : public CElement
  {
public:
   //--- Scrolling the list view
   void              ShiftList(void);
  };
//+------------------------------------------------------------------+
//| Moves the list view along the scrollbar                          |
//+------------------------------------------------------------------+
void CListView::ShiftList(void)
  {
//--- Get the current position of the scrollbar slider
   int v=m_scrollv.CurrentPos();
//--- Iterate over the visible part of the list view
   for(int i=0; i<m_visible_items_total; i++)
     {
      //--- If inside the range of the list view
      if(v>=0 && v<m_items_total)
        {
         //--- Moving the text, the background color and the text color
         m_items[i].Description(m_value_items[v]);
         m_items[i].BackColor((m_selected_item_index==v) ? m_item_color_selected : m_item_color);
         m_items[i].Color((m_selected_item_index==v) ? m_item_text_color_selected : m_item_text_color);
         //--- Increase the counter
         v++;
        }
     }
  }

The CListView::ShiftList() method must be called in the CListView::OnEvent() handler provided that the CScrollV::ScrollBarControl() method of the scrollbar will return true. That will mean that the management of the slider is enabled.

class CListView : public CElement
  {
private:
   //--- State of the left mouse button (pressed down/released)
   bool              m_mouse_state;
  };
//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CListView::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- List hidden
      if(!CElement::IsVisible())
         return;
      //--- Coordinates and state of the left mouse button
      int x=(int)lparam;
      int y=(int)dparam;
      m_mouse_state=(bool)int(sparam);
      //--- Checking the focus over the list view
      CElement::MouseFocus(x>CElement::X() && x<CElement::X2() && 
                           y>CElement::Y() && y<CElement::Y2());
      //--- Move the list if the management of the slider is enabled
      if(m_scrollv.ScrollBarControl(x,y,m_mouse_state))
         ShiftList();
      //--- Changes the color of the list view items when the cursor is hovering over it
      ChangeItemsColor(x,y);
      return;
     }
  }

After the compilation of the program, there list view can be managed by the slider of the scrollbar as shown in the screenshot below. Everything is implemented in such a way that even if the cursor leaves the boundaries of the slider after the left mouse button was pressed down over it, the management will be passed to the scrollbar and the slider will be moving.

 Fig. 4. Managing the list view using the slider of the scrollbar.

Fig. 4. Managing the list view using the slider of the scrollbar.

 

Now, we need a method for identifying the pressing on one of the list view items. Let us create a private method and call it CListView::OnClickListItem(). Also, the CListView::IdFromObjectName() private method is required for extracting the element identifier from the object name which has been shown in classes of other elements of our library.

Besides, we will need a unique identifier of clicking on the list view item (ON_CLICK_LIST_ITEM). Add it to the Defines.mqh file: 

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#define ON_CLICK_LIST_ITEM        (16) // Selecting the list view item

At the beginning of the CListView::OnClickListItem() method, there are checks for the name and the identifier of the object that was clicked on. Then, the local variable is assigned the current position of the slider. This variable is used as a counter in the loop to identify the index and the text of the item. At the end of the method, a message is sent containing (1) the identifier of the ON_CLICK_LIST_ITEM event, (2) the element identifier and (3) the text of current selected item. 

class CListView : public CElement
  {
private:
   //--- Handling the pressing on the list view item
   bool              OnClickListItem(const string clicked_object);
   //--- Getting the identifier from the name of the list view item
   int               IdFromObjectName(const string object_name);
  };
//+------------------------------------------------------------------+
//| Handling the pressing on the list view item                      |
//+------------------------------------------------------------------+
bool CListView::OnClickListItem(const string clicked_object)
  {
//--- Leave, if the pressing was not on the menu item
   if(::StringFind(clicked_object,CElement::ProgramName()+"_listview_edit_",0)<0)
      return(false);
//--- Get the identifier and index from the object name
   int id=IdFromObjectName(clicked_object);
//--- Leave, if the identifier does not match
   if(id!=CElement::Id())
      return(false);
//--- Get the current position of the scrollbar slider
   int v=m_scrollv.CurrentPos();
//--- Go over the visible part of the list view
   for(int i=0; i<m_visible_items_total; i++)
     {
      //--- If this list view item was selected
      if(m_items[i].Name()==clicked_object)
        {
         m_selected_item_index =v;
         m_selected_item_text  =m_value_items[v];
        }
      //--- If inside the range of the list view
      if(v>=0 && v<m_items_total)
         //--- Increase the counter
         v++;
     }
//--- Send a message about it
   ::EventChartCustom(m_chart_id,ON_CLICK_LIST_ITEM,CElement::Id(),0,m_selected_item_text);
   return(true);
  }

Now, when we have all required methods for managing the list view, we only have to place the code in the CListView::OnEvent() list view event handler as shown in the code below. Pressing on one of the list view items calls the CListView::HighlightSelectedItem() method. In this method pressing on the buttons of the scrollbar is tracked. If there was pressing on one of its buttons, then the list view is moved in relation to the current position of the slider. 

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CListView::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Handling the pressing on objects
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- If the pressing was on the list view elements
      if(OnClickListItem(sparam))
        {
         //--- Highlighting the item
         HighlightSelectedItem();
         return;
        }
      //--- If the pressing was on the buttons of the scrollbar
      if(m_scrollv.OnClickScrollInc(sparam) || m_scrollv.OnClickScrollDec(sparam))
        {
         //--- Moves the list along the scrollbar
         ShiftList();
         return;
        }
     }
  }


 

Fast Forward of the List View

We have completed the development of the required minimum for managing the list view. We can extend the capacity. Let us create one more private method for a fast forward of the list view by pressing a mouse button and holding it down on one of the buttons of the scrollbar.

We need to ensure that when the left mouse button is held down over the buttons of the scrollbar there is a little delay before the fast forward of the list view. If this is not done, the fast forward will be carried out immediately, which is not suitable for the time when the button is clicked only once for the list view to be moved only by one item. The SPIN_DELAY_MSC identifier has to be added in the Defines.mqh file with the value of -450. That means that the delay will be 450 milliseconds. 

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//--- Delay before enabling the fast forward of the counter (milliseconds)
#define SPIN_DELAY_MSC (-450)

Below is the code of the CListView::FastSwitching() method. We also need to declare the m_timer_counter field which will be used as the timer counter. At the beginning of the CListView::FastSwitching() method, we check the focus on the list view. If there is no focus, the program leaves the method. Then, if the mouse button is released, the counter is assigned the value of the delay (in our case it is -450 ms). If the mouse button is held down, the value of the counter is increased by the step of the timer set in the library (in this case it is 16 ms). Then there is a condition that prevents the program to work further until the value of the counter is not equal or greater than zero. As soon as this condition is met, the state of the scrollbar buttons is checked. Depending on what button is pressed down, the method for imitating pressing on the button is called. Then the list view is moved according to the current position of the slider of the scrollbar. 

class CListView : public CElement
  {
private:
   //--- Timer counter for fast forwarding the list view
   int               m_timer_counter;
private:
   //--- Fast forward of the list view
   void              FastSwitching(void);
  };
//+------------------------------------------------------------------+
//| Fast forward of the scrollbar                                    |
//+------------------------------------------------------------------+
void CListView::FastSwitching(void)
  {
//--- Leave, if there is no focus on the list view
   if(!CElement::MouseFocus())
      return;
//--- Return the counter to the initial value if the mouse button is released
   if(!m_mouse_state)
      m_timer_counter=SPIN_DELAY_MSC;
//--- If the mouse button is pressed down
   else
     {
      //--- Increase the counter by the set step
      m_timer_counter+=TIMER_STEP_MSC;
      //--- Leave, if less than zero
      if(m_timer_counter<0)
         return;
      //--- If scrolling up
      if(m_scrollv.ScrollIncState())
         m_scrollv.OnClickScrollInc(m_scrollv.ScrollIncName());
      //--- If scrolling down
      else if(m_scrollv.ScrollDecState())
         m_scrollv.OnClickScrollDec(m_scrollv.ScrollDecName());
      //--- Moves the list
      ShiftList();
     }
  }

The CListView::FastSwitching() method has to be carried out in the CListView::OnEventTimer() timer as shown below. If the list view is a drop-down element, no additional checks are required. Otherwise, we need to check if the form is currently blocked. 

//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CListView::OnEventTimer(void)
  {
//--- If this is a drop-down element
   if(CElement::IsDropdown())
      //--- Fast forward of the list view
      FastSwitching();
//--- If this is not a drop-down element, take current availability of the form into consideration
   else
     {
      //--- Track the fast forward of the list view only if the form is not blocked
      if(!m_wnd.IsLocked())
         FastSwitching();
     }
  }

Now, all the methods for managing the list view are ready. We can test them. Let us add two more list views to what we have created earlier:

Fig. 5. Testing three list views in the graphical interface. 

Fig. 5. Testing three list views in the graphical interface.

We will receive the messages from the list views in the event handler of the custom class (CProgram): 

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Event of pressing on the list view item
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM)
     {
      if(lparam==m_listview1.Id())
         ::Print(__FUNCTION__," > This message is from the first list view > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
      else if(lparam==m_listview2.Id())
         ::Print(__FUNCTION__," > This message is from the second list view > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
      else if(lparam==m_listview3.Id())
         ::Print(__FUNCTION__," > This message is from the third list view > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
     }
  }

When list view items are pressed on, the messages will be printed in the journal as shown below: 

2016.01.16 13:02:00.085 TestLibrary (GBPUSD,D1) CProgram::OnEvent > This message is from the first list view > id: 1016; lparam: 7; dparam: 0.0; sparam: SYMBOL 11
2016.01.16 13:01:59.056 TestLibrary (GBPUSD,D1) CProgram::OnEvent > This message is from the third list view > id: 1016; lparam: 9; dparam: 0.0; sparam: SYMBOL 12
2016.01.16 13:01:58.479 TestLibrary (GBPUSD,D1) CProgram::OnEvent > This message is from the second list view > id: 1016; lparam: 8; dparam: 0.0; sparam: SYMBOL 9
2016.01.16 13:01:57.868 TestLibrary (GBPUSD,D1) CProgram::OnEvent > This message is from the third list view > id: 1016; lparam: 9; dparam: 0.0; sparam: SYMBOL 19
2016.01.16 13:01:56.854 TestLibrary (GBPUSD,D1) CProgram::OnEvent > This message is from the second list view > id: 1016; lparam: 8; dparam: 0.0; sparam: SYMBOL 4
2016.01.16 13:01:56.136 TestLibrary (GBPUSD,D1) CProgram::OnEvent > This message is from the first list view> id: 1016; lparam: 7; dparam: 0.0; sparam: SYMBOL 9
2016.01.16 13:01:55.433 TestLibrary (GBPUSD,D1) CProgram::OnEvent > This message is from the first list view > id: 1016; lparam: 7; dparam: 0.0; sparam: SYMBOL 14

 


Conclusion

In this article, we have considered the compound control list view. We also demonstrated how a vertical scrollbar can be used. In the next article, we will talk about another compound control - the combobox

You can download the material of Part V and test how it works. If you have questions on using material from those files, you can refer to the detailed description of the library development in one of the articles from the list below or ask your question in the comments of this article.

List of articles of the fifth part:

Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/2380

Attached files |
Last comments | Go to discussion (5)
marquez
marquez | 23 May 2016 at 15:13
MetaQuotes Software Corp.:

New article Graphical Interfaces V: The List View Element (Chapter 2) has been published:

Author: Anatoli Kazharski

Hi,


compiling it with current MT5 Version: 5.00 build 1340 causes compiler Errors:

'return' - cannot convert from const pointer to nonconst pointer    SplitButton.mqh    90    65
'return' - cannot convert from const pointer to nonconst pointer    ListView.mqh    67    76

Anatoli Kazharski
Anatoli Kazharski | 24 May 2016 at 17:05
marquez:

Hi,

compiling it with current MT5 Version: 5.00 build 1340 causes compiler Errors:

'return' - cannot convert from const pointer to nonconst pointer    SplitButton.mqh    90    65
'return' - cannot convert from const pointer to nonconst pointer    ListView.mqh    67    76

Thank.

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Обсуждение статьи "Графические интерфейсы III: Группы простых и многофункциональных кнопок (Глава 2)"

Anatoli Kazharski, 2016.05.14 18:58

Да, после последнего обновления терминала появилась такая ошибка. Правила "игры" немного изменились. Исправить можно просто удалив спецификатор const.

Перейдите к строке с ошибкой и замените эту строку:

CContextMenu     *GetContextMenuPointer(void)        const { return(::GetPointer(m_drop_menu)); }

На эту: 

CContextMenu     *GetContextMenuPointer(void)              { return(::GetPointer(m_drop_menu)); }

//---

Подобные исправления нужно будет внести во всех файлах, где будет встречаться такая ошибка. В следующих статьях серии ошибка будет устранена.

Спасибо за сообщение. 


Amir Yacoby
Amir Yacoby | 24 May 2016 at 22:49
The comments in the source are in Gibberish (previous times it was in English)
Laszlo Tormasi
Laszlo Tormasi | 29 Apr 2017 at 11:53

Hello,

I am playing around with the standard library, and I have a really simple question:

I have created a List with CListView. For example, i add elements to it every second. How can i update the list to show the most recent elements automatically without scrolling down with the mouse?

Eric Sebestyen Ferreira
Eric Sebestyen Ferreira | 15 Jul 2018 at 09:40
Anatoli Kazharski:

Thank.



Hi Still have problem, after delet the "const", now I get the error:  array out of range in 'Program.mqh' (753,32)

Universal Expert Advisor: Trading in a Group and Managing a Portfolio of Strategies (Part 4) Universal Expert Advisor: Trading in a Group and Managing a Portfolio of Strategies (Part 4)
In the last part of the series of articles about the CStrategy trading engine, we will consider simultaneous operation of multiple trading algorithms, will learn to load strategies from XML files, and will present a simple panel for selecting Expert Advisors from a single executable module, and managing their trading modes.
Graphical Interfaces V: The Vertical and Horizontal Scrollbar (Chapter 1) Graphical Interfaces V: The Vertical and Horizontal Scrollbar (Chapter 1)
We are still discussing the development of the library for creating graphical interfaces in the MetaTrader environment. In the first article of the fifth part of the series, we will write classes for creating vertical and horizontal scrollbars.
Self-organizing feature maps (Kohonen maps) - revisiting the subject Self-organizing feature maps (Kohonen maps) - revisiting the subject
This article describes techniques of operating with Kohonen maps. The subject will be of interest to both market researchers with basic level of programing in MQL4 and MQL5 and experienced programmers that face difficulties with connecting Kohonen maps to their projects.
Evaluating the effectiveness of trading systems by analyzing their components Evaluating the effectiveness of trading systems by analyzing their components
This article explores the effectiveness of complex trading systems by analyzing the efficiency of its individual components. Any analysis, whether it is graphic, based on indicators, or any other, is one of the key components of successful trading in financial markets. This article is to some extent a research of few simple and independent trading systems for analyzing their effectiveness and usefulness of the joint application.