
DoEasy. Steuerung (Teil 19): Verschieben der Registerkarten in TabControl, Ereignisse im WinForms-Objekt
Inhalt
Konzept
Im vorigen Artikel habe ich getestet, ob sich die Kopfzeile der Registerkarte verschieben lässt, wenn eine teilweise ausgeblendete Kopfzeile ausgewählt wird — sie verschob sich nach links und wurde vollständig sichtbar. Hier werde ich die Methoden für das Verschieben der Kopfleiste nach links-rechts und oben-unten basierend auf der erstellten Funktionalität implementieren. Beim Verschieben von Überschriften nach oben und unten spielt es eine Rolle, wo sich die Überschriftenzeile befindet — auf der linken oder rechten Seite. Alle diese Methoden werden aufgerufen, wenn eine teilweise ausgeblendete Kopfzeile ausgewählt wird, um sie vollständig anzuzeigen, und wenn die Bildlauftasten — nach oben, unten, links und rechts — gedrückt werden. Um zu verstehen, welche Schaltfläche und welches Steuerelement gedrückt wurde, werden wir das Ereignismodell verwenden — wenn die Schaltfläche gedrückt wird, wird ein Ereignis gesendet, das die Bibliothek abfängt und verarbeitet, indem sie es an den Ereignishandler des Elements sendet, an das die gedrückte Schaltfläche angehängt ist, um es innerhalb des Steuerelements weiter zu verarbeiten. Ich werde dieses Modell für die Arbeit mit anderen Kontrollen verwenden.
Zurzeit werden die Namen der grafischen Haupt- und Basiselemente im Parameter „sparam“ an die Ereignisbehandlung gesendet. Das Hauptsteuerelement ist dasjenige, das ein Objekt enthält, bei dem ein Ereignis eingetreten ist. Es gilt hier als Basiskontrolle. Wenn es sich bei diesem Basissteuerelement jedoch um ein zusammengesetztes Steuerelement handelt, an das weitere Steuerelemente angehängt sind (z. B. Schaltflächen) und in denen bereits ein Ereignis eingetreten ist, können wir das Basiselement nicht finden, da es nicht direkt an das Hauptobjekt angehängt ist. Um dies zu vermeiden, übergeben wir im Parameter „sparam“ die Namen des Haupt- und des Basisobjekts sowie den Namen des Objekts, in dem das Ereignis aufgetreten ist.
Auf diese Weise erhalten wir Eingabedaten für das Hauptobjekt, für das Basisobjekt, in dem ein Ereignis in einem der mit ihm verbundenen Elemente aufgetreten ist, und den Namen des Elements, in dem das Ereignis aufgetreten ist. Um das Steuerelement zu definieren, in dem wir die Schaltfläche angeklickt haben (ein Spezialfall), senden wir den Typ dieses Basisobjekts im Parameter 'dparam'. Wenn wir also den Typ des Basisobjekts kennen, erhalten wir eine Liste aller Steuerelemente im Hauptobjekt mit dem in 'dparam' aufgezeichneten Typ. Dann suchen wir in einer Schleife über all diese Objekte nach einem Objekt mit dem zuletzt in „sparam“ übergebenen Namen, bei dem ein Ereignis (Klicken auf das Steuerelement) aufgetreten ist.
Im Moment sieht eine solche Struktur im Hinblick auf ihre Vielseitigkeit bei der Handhabung komplexerer Objekte nicht sehr zuverlässig aus. Für die Entwicklung der Bibliothek ist sie jedoch in diesem Stadium völlig ausreichend. Bei der Erstellung komplexerer Steuerelemente mit einer ausgefeilteren Verschachtelung von Objekten untereinander erhalten wir ein anschauliches praktisches Beispiel dafür, wie es notwendig ist, Ereignisse in solchen Objekten in der Bibliothek korrekt und universell zu identifizieren (denken Sie an das Prinzip „einfach-komplex“).
Derzeit ermöglicht die Bibliothek das Ausblenden und Anzeigen von Steuerelementen. Um Elemente ein- oder auszublenden, genügt es, nur das Haupt- oder Basiselement auszublenden. Alle mit ihr verbundenen Elemente werden entsprechend ein- oder ausgeblendet. Wenn wir aber bedenken, dass das Steuerelement die angezeigten Objekte unabhängig vom Hauptobjekt enthalten sollte, d.h. ihre Sichtbarkeit wird im Steuerelement selbst eingestellt, und wenn ein solches Objekt ausgeblendet ist und sein übergeordnetes Element angezeigt wird, sollte ein solches Objekt nicht angezeigt werden. In solchen Situationen müssen wir die Sichtbarkeit dieser Objekte von ihrem Basissteuerelement aus verwalten. Um diese Möglichkeit zu verwirklichen, müssen wir eine weitere Eigenschaft des grafischen Elements einführen — sein Anzeigeflag. Wenn das Hauptobjekt ausgeblendet und dann eingeblendet wurde, bleibt das Steuerelement, für das das Anzeigeflag gelöscht wurde, so lange ausgeblendet, bis es von seinem Basissteuerelement aus explizit angezeigt wird.
Aber genug der Theorie, machen wir uns an die Arbeit...
Verbesserung der Bibliotheksklassen
Da ich alles im Hinblick auf die Zukunft tue, werde ich keine eigenen Ereignis-IDs für die Schaltflächen der Bildlaufsteuerung erstellen. Ich werde sie mit Blick auf weitere neue Steuerelemente erstellen, die ebenfalls Schaltflächen verwenden werden (Bildlaufleiste, Dropdown-Listen usw.). Mit anderen Worten, lassen Sie uns einige generische Ereignisse für das Klicken auf das Scroll- oder Pop-up-Steuerelement erstellen.
In \MQL5\Include\DoEasy\Defines.mqh, und zwar in der Liste der möglichen WinForms-Steuerungsereignisse, werden wir neue Enumerationskonstanten hinzufügen:
//+------------------------------------------------------------------+ //| List of possible WinForms control events | //+------------------------------------------------------------------+ enum ENUM_WF_CONTROL_EVENT { WF_CONTROL_EVENT_NO_EVENT = GRAPH_OBJ_EVENTS_NEXT_CODE,// No event WF_CONTROL_EVENT_CLICK, // "Click on the control" event WF_CONTROL_EVENT_CLICK_CANCEL, // "Canceling the click on the control" event WF_CONTROL_EVENT_TAB_SELECT, // "TabControl tab selection" event WF_CONTROL_EVENT_CLICK_SCROLL_LEFT, // "Clicking the control left button" event WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT, // "Clicking the control right button" event WF_CONTROL_EVENT_CLICK_SCROLL_UP, // "Clicking the control up button" event WF_CONTROL_EVENT_CLICK_SCROLL_DOWN, // "Clicking the control down button" event }; #define WF_CONTROL_EVENTS_NEXT_CODE (WF_CONTROL_EVENT_TAB_SELECT+1) // The code of the next event after the last graphical element event code //+------------------------------------------------------------------+
Hier habe ich vorsichtshalber eine Ereignis-ID für das Abbrechen des Klicks auf das Steuerelement erstellt (in einigen Fällen ist es möglich, ein solches Ereignis zu behandeln) und allgemeine Ereignisse für Steuerelemente hinzugefügt, die über Schaltflächen verfügen, die das Erscheinungsbild des Steuerelements verwalten oder eine Interaktion mit ihm ermöglichen.
In der Enumeration der ganzzahligen Eigenschaften von grafischen Elementen fügen wir eine neue Eigenschaft hinzu und erhöhen die Anzahl der ganzzahligen Objekteigenschaften von 96 auf 97:
//+------------------------------------------------------------------+ //| Integer properties of the graphical element on the canvas | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_PROP_INTEGER { CANV_ELEMENT_PROP_ID = 0, // Element ID CANV_ELEMENT_PROP_TYPE, // Graphical element type //---... //---... CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH, // Visibility scope width CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT, // Visibility scope height CANV_ELEMENT_PROP_DISPLAYED, // Non-hidden control display flag CANV_ELEMENT_PROP_GROUP, // Group the graphical element belongs to CANV_ELEMENT_PROP_ZORDER, // Priority of a graphical object for receiving the event of clicking on a chart //---... //---... CANV_ELEMENT_PROP_TAB_PAGE_COLUMN, // Tab column index CANV_ELEMENT_PROP_ALIGNMENT, // Location of an object inside the control }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (97) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting //+------------------------------------------------------------------+
Das Flag für die Anzeige eines nicht ausgeblendeten Steuerelements bedeutet, dass das Steuerelement nicht angezeigt wird, aber wenn das Flag gelöscht ist (false), wird das Element nicht gezeigt. Mit anderen Worten: Wenn das Hauptsteuerelement angezeigt wird, bleiben die untergeordneten Steuerelemente, für die dieses Flag gelöscht wurde, so lange verborgen, bis sie durch Setzen dieses Flags auf true und dem Aufruf der Methode Show() zur Anzeige gezwungen werden.
Wir fügen neue Eigenschaften zur Liste der möglichen Kriterien für die Sortierung von grafischen Elementen auf der Leinwand hinzu:
//+------------------------------------------------------------------+ //| Possible sorting criteria of graphical elements on the canvas | //+------------------------------------------------------------------+ #define FIRST_CANV_ELEMENT_DBL_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP) #define FIRST_CANV_ELEMENT_STR_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP) enum ENUM_SORT_CANV_ELEMENT_MODE { //--- Sort by integer properties SORT_BY_CANV_ELEMENT_ID = 0, // Sort by element ID SORT_BY_CANV_ELEMENT_TYPE, // Sort by graphical element type //---... //---... SORT_BY_CANV_ELEMENT_VISIBLE_AREA_WIDTH, // Sort by visibility scope width SORT_BY_CANV_ELEMENT_VISIBLE_AREA_HEIGHT, // Sort by visibility scope height SORT_BY_CANV_ELEMENT_DISPLAYED, // Sort by non-hidden control display flag SORT_BY_CANV_ELEMENT_GROUP, // Sort by a group the graphical element belongs to SORT_BY_CANV_ELEMENT_ZORDER, // Sort by the priority of a graphical object for receiving the event of clicking on a chart //---... //---... SORT_BY_CANV_ELEMENT_TAB_PAGE_COLUMN, // Sort by tab column index SORT_BY_CANV_ELEMENT_ALIGNMENT, // Sort by the location of the object inside the control //--- Sort by real properties //--- Sort by string properties SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Sort by an element object name SORT_BY_CANV_ELEMENT_NAME_RES, // Sort by the graphical resource name SORT_BY_CANV_ELEMENT_TEXT, // Sort by graphical element text SORT_BY_CANV_ELEMENT_DESCRIPTION, // Sort by graphical element description }; //+------------------------------------------------------------------+
Jetzt können wir die Listen der Grafikelemente nach dieser neuen Eigenschaft auswählen und sortieren.
In \MQL5\Include\DoEasy\Data.mqh wurden neuen Nachrichtenindizes der Bibliothek hinzugefügt:
MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY, // Request outside the array MSG_LIB_SYS_FAILED_CONV_GRAPH_OBJ_COORDS_TO_XY, // Failed to convert graphical object coordinates to screen ones MSG_LIB_SYS_FAILED_CONV_TIMEPRICE_COORDS_TO_XY, // Failed to convert time/price coordinates to screen ones MSG_LIB_SYS_FAILED_ENQUEUE_EVENT, // Failed to put the event in the chart event queue
...
MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH, // Visibility scope width MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT, // Visibility scope height MSG_CANV_ELEMENT_PROP_DISPLAYED, // Non-hidden control display flag MSG_CANV_ELEMENT_PROP_ENABLED, // Element availability flag MSG_CANV_ELEMENT_PROP_FORE_COLOR, // Default text color for all control objects
und die Textnachrichten, die den neu hinzugefügten Indizes entsprechen:
{"Запрос за пределами массива","Data requested outside the array"}, {"Не удалось преобразовать координаты графического объекта в экранные","Failed to convert graphics object coordinates to screen coordinates"}, {"Не удалось преобразовать координаты время/цена в экранные","Failed to convert time/price coordinates to screen coordinates"}, {"Не удалось поставить событие в очередь событий графика","Failed to put event in chart event queue"},
...
{"Ширина области видимости","Width of object visibility area"}, {"Высота области видимости","Height of object visibility area"}, {"Флаг отображения не скрытого элемента управления","Flag that sets the display of a non-hidden control"}, {"Флаг доступности элемента","Element Availability Flag"}, {"Цвет текста по умолчанию для всех объектов элемента управления","Default text color for all objects in the control"},
Verbessern wir noch die Objektklasse der grafischen Elemente in \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh.
In der Objektstruktur fügen wir eine neu hinzugefügte Eigenschaft hinzu:
private: int m_shift_coord_x; // Offset of the X coordinate relative to the base object int m_shift_coord_y; // Offset of the Y coordinate relative to the base object struct SData { //--- Object integer properties int id; // Element ID int type; // Graphical element type //---... //---... int visible_area_w; // Visibility scope width int visible_area_h; // Visibility scope height bool displayed; // Non-hidden control display flag //--- Object real properties //--- Object string properties uchar name_obj[64]; // Graphical element object name uchar name_res[64]; // Graphical resource name uchar text[256]; // Graphical element text uchar descript[256]; // Graphical element description }; SData m_struct_obj; // Object structure uchar m_uchar_array[]; // uchar array of the object structure
Alle Eigenschaften des Objekts werden in der Struktur des Objekts festgelegt, damit das Objekt in der Datei gespeichert und wiederhergestellt werden kann.
Wir fügen zwei neue Methoden zum Setzen und Abrufen der Objektanzeigeeigenschaft zum Methodenblock für den vereinfachten Zugriff auf Objekteigenschaften hinzu:
//+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ //--- Set the (1) X, (2) Y coordinates, (3) element width, (4) height, (5) right (6) and bottom edge, virtual bool SetCoordX(const int coord_x); virtual bool SetCoordY(const int coord_y); virtual bool SetWidth(const int width); virtual bool SetHeight(const int height); void SetRightEdge(void) { this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge()); } void SetBottomEdge(void) { this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge()); } //--- Set the shift of the (1) left, (2) top, (3) right, (4) bottom edge of the active area relative to the element, //--- (5) all shifts of the active area edges relative to the element, (6) opacity void SetActiveAreaLeftShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,fabs(value)); } void SetActiveAreaRightShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,fabs(value)); } void SetActiveAreaTopShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,fabs(value)); } void SetActiveAreaBottomShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,fabs(value)); } void SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift); void SetOpacity(const uchar value,const bool redraw=false); //--- (1) Set and (2) return the flag for displaying a non-hidden control void SetDisplayed(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,flag); } bool Displayed(void) { return (bool)this.GetProperty(CANV_ELEMENT_PROP_DISPLAYED); } //--- (1) Set and (2) return the graphical element type void SetTypeElement(const ENUM_GRAPH_ELEMENT_TYPE type) { CGBaseObj::SetTypeElement(type); this.SetProperty(CANV_ELEMENT_PROP_TYPE,type); } ENUM_GRAPH_ELEMENT_TYPE TypeGraphElement(void) const { return (ENUM_GRAPH_ELEMENT_TYPE)this.GetProperty(CANV_ELEMENT_PROP_TYPE); }
Die Methoden schreiben einfach das übergebene Flag in die Objekteigenschaft und geben den dort eingestellten Wert zurück.
In beiden Klassenkonstruktoren fügen wir den Standardwert der neuen Eigenschaft hinzu:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int element_id, const int element_num, const long chart_id, const int wnd_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable=true, const bool activity=true, const bool redraw=false) : m_shadow(false) { this.SetTypeElement(element_type); this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_main=NULL; this.m_element_base=NULL; this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND); this.m_name=this.CreateNameGraphElement(element_type); this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id); this.m_subwindow=wnd_num; this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.m_text_anchor=0; this.m_text_x=0; this.m_text_y=0; this.SetBackgroundColor(colour,true); this.SetOpacity(opacity); this.m_shift_coord_x=0; this.m_shift_coord_y=0; if(::ArrayResize(this.m_array_colors_bg,1)==1) this.m_array_colors_bg[0]=this.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1) this.m_array_colors_bg_dwn[0]=this.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1) this.m_array_colors_bg_ovr[0]=this.BackgroundColor(); if(this.Create(chart_id,wnd_num,x,y,w,h,redraw)) { this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID()); // Chart ID //---... //---... this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,w); // Visibility scope width this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h); // Visibility scope height this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,true); // Non-hidden control display flag //--- this.SetProperty(CANV_ELEMENT_PROP_BELONG,ENUM_GRAPH_OBJ_BELONG::GRAPH_OBJ_BELONG_PROGRAM); // Graphical element affiliation this.SetProperty(CANV_ELEMENT_PROP_ZORDER,0); // Priority of a graphical object for receiving the event of clicking on a chart this.SetProperty(CANV_ELEMENT_PROP_BOLD_TYPE,FW_NORMAL); // Font width type //---... //---... this.SetProperty(CANV_ELEMENT_PROP_TEXT,""); // Graphical element text this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descript); // Graphical element description this.SetVisibleFlag(false,false); } else { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj()); } } //+------------------------------------------------------------------+ //| Protected constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const long chart_id, const int wnd_num, const string descript, const int x, const int y, const int w, const int h) : m_shadow(false) { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_main=NULL; this.m_element_base=NULL; this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND); this.m_name=this.CreateNameGraphElement(element_type); this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id); this.m_subwindow=wnd_num; this.m_type_element=element_type; this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.m_text_anchor=0; this.m_text_x=0; this.m_text_y=0; this.SetBackgroundColor(CLR_CANV_NULL,true); this.SetOpacity(0); this.m_shift_coord_x=0; this.m_shift_coord_y=0; if(::ArrayResize(this.m_array_colors_bg,1)==1) this.m_array_colors_bg[0]=this.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1) this.m_array_colors_bg_dwn[0]=this.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1) this.m_array_colors_bg_ovr[0]=this.BackgroundColor(); if(this.Create(chart_id,wnd_num,x,y,w,h,false)) { this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID()); // Chart ID //---... //---... this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,w); // Visibility scope width this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h); // Visibility scope height this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,true); // Non-hidden control display flag //--- this.SetProperty(CANV_ELEMENT_PROP_BELONG,ENUM_GRAPH_OBJ_BELONG::GRAPH_OBJ_BELONG_PROGRAM); // Graphical element affiliation this.SetProperty(CANV_ELEMENT_PROP_ZORDER,0); // Priority of a graphical object for receiving the event of clicking on a chart this.SetProperty(CANV_ELEMENT_PROP_BOLD_TYPE,FW_NORMAL); // Font width type //---... //---... this.SetProperty(CANV_ELEMENT_PROP_TEXT,""); // Graphical element text this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descript); // Graphical element description this.SetVisibleFlag(false,false); } else { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj()); } } //+------------------------------------------------------------------+
Standardmäßig ist ein Objekt so eingestellt, dass es sichtbar ist und angezeigt wird, wenn die Sichtbarkeit des Basis- oder Hauptobjekts eingeschaltet ist. Um den Modus der manuellen Steuerung der Objektsichtbarkeit einzustellen, muss der Wert des Flags auf false gesetzt werden. Wenn in diesem Fall das Basis- oder Hauptobjekt ausgeblendet und dann angezeigt wurde, bleibt das aktuelle Objekt weiterhin ausgeblendet, und um es anzuzeigen, müssen wir die Eigenschaft Angezeigt (Displayed) auf true setzen und die Methode Show() des Objekts aufrufen.
In der Methode zum Anlegen einer Objektstruktur setzen wir den Wert der Objekteigenschaft auf das entsprechende Feld der Struktur:
//+------------------------------------------------------------------+ //| Create the object structure | //+------------------------------------------------------------------+ bool CGCnvElement::ObjectToStruct(void) { //--- Save integer properties this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID); // Element ID this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE); // Graphical element type //---... //---... this.m_struct_obj.belong=(int)this.GetProperty(CANV_ELEMENT_PROP_BELONG); // Graphical element affiliation this.m_struct_obj.number=(int)this.GetProperty(CANV_ELEMENT_PROP_NUM); // Element ID in the list //---... //---... this.m_struct_obj.visible_area_x=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X); // Visibility scope X coordinate this.m_struct_obj.visible_area_y=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y); // Visibility scope Y coordinate //---... //---... this.m_struct_obj.visible_area_w=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH); // Visibility scope width this.m_struct_obj.visible_area_h=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT); // Visibility scope height this.m_struct_obj.displayed=(bool)this.GetProperty(CANV_ELEMENT_PROP_DISPLAYED); // Flag for displaying a non-hidden control this.m_struct_obj.zorder=this.GetProperty(CANV_ELEMENT_PROP_ZORDER); // Priority of a graphical object for receiving the on-chart mouse click event this.m_struct_obj.enabled=(bool)this.GetProperty(CANV_ELEMENT_PROP_ENABLED); // Element availability flag this.m_struct_obj.fore_color=(color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR); // Default text color for all control objects //---... //---... this.m_struct_obj.fore_color_opacity=(uchar)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY); // Opacity of the default text color for all control objects this.m_struct_obj.background_color=(color)this.GetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR); // Element background color this.m_struct_obj.tab_alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT); // Location of tabs inside the control this.m_struct_obj.alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_ALIGNMENT); // Location of an object inside the control //--- Save real properties //--- Save string properties ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj); // Graphical element object name ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res); // Graphical resource name ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_TEXT),this.m_struct_obj.text); // Graphical element text ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_DESCRIPTION),this.m_struct_obj.descript);// Graphical element description //--- Save the structure to the uchar array ::ResetLastError(); if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,true); return false; } return true; } //+------------------------------------------------------------------+
In der Methode, die ein Objekt aus einer Struktur erzeugt, setzen wir den Wert der neuen Objekteigenschaft aus dem entsprechenden Strukturfeld:
//+------------------------------------------------------------------+ //| Create the object from the structure | //+------------------------------------------------------------------+ void CGCnvElement::StructToObject(void) { //--- Save integer properties this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id); // Element ID this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type); // Graphical element type //---... //---... this.SetProperty(CANV_ELEMENT_PROP_BELONG,this.m_struct_obj.belong); // Graphical element affiliation this.SetProperty(CANV_ELEMENT_PROP_NUM,this.m_struct_obj.number); // Element index in the list //---... //---... this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,this.m_struct_obj.visible_area_h); // Visibility scope height this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,this.m_struct_obj.displayed); // Non-hidden control display flag this.SetProperty(CANV_ELEMENT_PROP_ZORDER,this.m_struct_obj.zorder); // Priority of a graphical object for receiving the event of clicking on a chart this.SetProperty(CANV_ELEMENT_PROP_ENABLED,this.m_struct_obj.enabled); // Element availability flag //---... //---... this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR,this.m_struct_obj.fore_color); // Default text color for all control objects this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY,this.m_struct_obj.fore_color_opacity); // Opacity of the default text color for all control objects this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,this.m_struct_obj.tab_alignment); // Location of tabs inside the control this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,this.m_struct_obj.alignment); // Location of an object inside the control //--- Save real properties //--- Save string properties this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj)); // Graphical element object name this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res)); // Graphical resource name this.SetProperty(CANV_ELEMENT_PROP_TEXT,::CharArrayToString(this.m_struct_obj.text)); // Graphical element text this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,::CharArrayToString(this.m_struct_obj.descript));// Graphical element description } //+------------------------------------------------------------------+
In der Klasse des WinForms-Basisobjekts in \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh, und zwar in der Methode, die die Beschreibung der Integer-Element-Eigenschaft zurückgibt, fügen wir den Codeblock für die Rückgabe der Beschreibung der neuen Objekteigenschaft hinzu:
//+------------------------------------------------------------------+ //| Return the description of the control integer property | //+------------------------------------------------------------------+ string CWinFormBase::GetPropertyDescription(ENUM_CANV_ELEMENT_PROP_INTEGER property,bool only_prop=false) { return ( property==CANV_ELEMENT_PROP_ID ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ID)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : //---... //---... property==CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_DISPLAYED ? CMessage::Text(MSG_CANV_ELEMENT_PROP_DISPLAYED)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_GROUP ? CMessage::Text(MSG_GRAPH_OBJ_PROP_GROUP)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : //---... //---... property==CANV_ELEMENT_PROP_ALIGNMENT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ALIGNMENT)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(property)) ) : "" ); } //+------------------------------------------------------------------+
Wenn die Eigenschaft an die Methode übergeben wird, wird je nach dem Flag only_prop eine entsprechende Textnachricht erstellt — entweder ein einfacher Eigenschaftsname (only_prop = true) oder zusammen mit dem für die Eigenschaft festgelegten Wert (only_prop = false).
Alle Steuerelemente verwenden die Ereignisfunktionalität auf die eine oder andere Weise — sowohl für ihre interne Verwendung als auch für die Benachrichtigung des Programms über Ereignisse, die in der grafischen Nutzeroberfläche auftreten. Die Hauptklasse für die Nutzerinteraktion ist die Formularobjektklasse — sie implementiert die Mausinteraktionsfunktionen, und die Basisklasse aller WinForms-Bibliotheksobjekte wird von ihr abgeleitet. Lassen Sie uns eine Methode zum Senden von Nachrichten an grafische Elemente in derselben Klasse erstellen.
In der Formularobjektklassendatei \MQL5\Include\DoEasy\Objects\Graph\Form.mqh, und zwar in ihrem geschützten Abschnitt, deklarieren wir die Methode zum Senden von Nachrichten.
Die Methode wird virtuell sein, falls sie für ein abgeleitetes Objekt überschrieben werden soll:
//--- 'The cursor is inside the window scrolling area, a mouse button is clicked (any)' event handler virtual void MouseScrollAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); //--- 'The cursor is inside the window scrolling area, the mouse wheel is being scrolled' event handler virtual void MouseScrollAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Send a message about the event virtual bool SendEvent(const long chart_id,const ushort event_id); public:
In der Methode, die das Formular anzeigt, legen wir die Überprüfung des Objektanzeigeflags fest (Überprüfung einer neuen Grafikelement-Eigenschaft):
//+------------------------------------------------------------------+ //| Show the form | //+------------------------------------------------------------------+ void CForm::Show(void) { //--- If the element should not be displayed (hidden inside another control), leave if(!this.Displayed()) return; //--- If the object has a shadow, display it if(this.m_shadow_obj!=NULL) this.m_shadow_obj.Show(); //--- Display the main form CGCnvElement::Show(); //--- In the loop by all bound graphical objects, for(int i=0;i<this.m_list_elements.Total();i++) { //--- get the next graphical element CGCnvElement *element=this.m_list_elements.At(i); if(element==NULL) continue; //--- and display it element.Show(); } //--- Update the form CGCnvElement::Update(); } //+------------------------------------------------------------------+
Wenn diese Methode für ein beliebiges Objekt vom Typ CForm oder höher aufgerufen wird, wird zunächst das Anzeigeflag des Objekts geprüft. Wenn das Flag nicht gesetzt ist (manuelle Sichtbarkeit des Elements ist aktiviert), wird die Methode sofort verlassen.
Implementieren wir die Methode, die die Ereignismeldung außerhalb des Klassenkörpers sendet:
//+------------------------------------------------------------------+ //| Send a message about the event | //+------------------------------------------------------------------+ bool CForm::SendEvent(const long chart_id,const ushort event_id) { //--- Create the event: //--- Get the base and main objects CGCnvElement *base=this.GetBase(); CGCnvElement *main=this.GetMain(); //--- find the names of the main and base objects string name_main=(main!=NULL ? main.Name() : this.IsMain() ? this.Name() : "Lost name of object"); string name_base=(base!=NULL ? base.Name() : "Lost name of object"); ENUM_GRAPH_ELEMENT_TYPE base_base_type=(base!=NULL ? base.GetBase().TypeGraphElement() : this.TypeGraphElement()); //--- pass the object ID in the event 'long' parameter //--- pass the object type in the event 'double' parameter //--- in the event 'string' parameter, pass the names of the main, base and current objects separated by ";" long lp=this.ID(); double dp=base_base_type; string sp=::StringSubstr(name_main,::StringLen(this.NamePrefix()))+";"+ ::StringSubstr(name_base,::StringLen(this.NamePrefix()))+";"+ ::StringSubstr(this.Name(),::StringLen(this.NamePrefix())); //--- Send the event of clicking on the control to the control program chart bool res=true; ::ResetLastError(); res=::EventChartCustom(chart_id,event_id,lp,dp,sp); if(res) return true; ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_ENQUEUE_EVENT),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(::GetLastError())); return false; } //+------------------------------------------------------------------+
Hier erhalten wir die Zeiger auf die Objekte main und base und ihre Namen. Wenn der Zeiger auf das Hauptobjekt NULL ist, dann ist dies höchstwahrscheinlich das Hauptobjekt. Dazu prüfen wir, ob dies zutrifft. Wenn ja, wird der Name des aktuellen Objekts verwendet. Wenn der Zeiger aus irgendeinem Grund nicht empfangen wird, verwenden Sie die Zeichenfolge „Lost name of object“, um ihn zu verwalten.
Dann müssen wir den Typ des Basisobjekts herausfinden, an das das Basisobjekt des aktuellen Objekts gebunden ist (d.h. wir holen uns sein Basisobjekt vom Basisobjekt, gefolgt von seinem Typ) und schreiben alle empfangenen Daten in die Variablen, die in der Ereignismeldung gesendet wurden. In lparam senden wir die ID des aktuellen Objekts, in dparam den Typ des Basisobjekts, an das das Basisobjekt des aktuellen Objekts gebunden ist, und in „sparam“ übergeben wir die Zeichenkette mit den Namen der drei Objekte (main, base und current), getrennt durch „;“. Wenn wir ein Ereignis empfangen, können wir anhand dieser Daten genau feststellen, von welchem Objekt die Ereignismeldung stammt.
Im Moment reicht diese Logik aus, um das Objekt zu bestimmen, das das Ereignis erzeugt hat, aber ich werde sie später ändern, da sie es nicht erlaubt, die gesamte Verschachtelung von Objekten ineinander zu verfolgen, wenn man komplexere Steuerelemente mit einer tieferen Hierarchie der Verschachtelung ineinander erstellt.
Fügen wir nun das Senden von Ereignismeldungen zu den Ereignishandlern der WinForms-Objekte hinzu.
In der Klassendatei des Schaltflächenobjekts \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh fügen wir die Ereignisbehandlung von „Der Cursor befindet sich im aktiven Bereich, die linke Maustaste wurde angeklickt“ hinzu, um Ereignismeldungen zu senden:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| left mouse button released | //+------------------------------------------------------------------+ void CButton::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- The mouse button released outside the element means refusal to interact with the element if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { //--- If this is a simple button, set the initial background and text color if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorInit(),false); this.SetForeColor(this.ForeColorInit(),false); } //--- If this is the toggle button, set the initial background and text color depending on whether the button is pressed or not else { this.SetBackgroundColor(!this.State() ? this.BackgroundColorInit() : this.BackgroundStateOnColorInit(),false); this.SetForeColor(!this.State() ? this.ForeColorInit() : this.ForeStateOnColorInit(),false); } //--- Set the initial frame color this.SetBorderColor(this.BorderColorInit(),false); //--- Send the event: this.SendEvent(::ChartID(),WF_CONTROL_EVENT_CLICK_CANCEL); //--- Send the test message to the journal Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel")); } //--- The mouse button released within the element means a click on the control else { //--- If this is a simple button, set the background and text color for "The cursor is over the active area" status if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorMouseOver(),false); this.SetForeColor(this.ForeColorMouseOver(),false); } //--- If this is the toggle button, else { //--- if the button does not work in the group, set its state to the opposite, if(!this.GroupButtonFlag()) this.SetState(!this.State()); //--- if the button is not pressed yet, set it to the pressed state else if(!this.State()) this.SetState(true); //--- set the background and text color for "The cursor is over the active area" status depending on whether the button is clicked or not this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseOver() : this.BackgroundColorMouseOver(),false); this.SetForeColor(this.State() ? this.ForeStateOnColorMouseOver() : this.ForeColorMouseOver(),false); } //--- Send the event: this.SendEvent(::ChartID(),WF_CONTROL_EVENT_CLICK); //--- Send the test message to the journal Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.State()=",this.State(),", ID=",this.ID(),", Group=",this.Group()); //--- Set the frame color for "The cursor is over the active area" status this.SetBorderColor(this.BorderColorMouseOver(),false); } //--- Redraw the object this.Redraw(false); } //+------------------------------------------------------------------+
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\RadioButton.mqh,
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| left mouse button released | //+------------------------------------------------------------------+ void CRadioButton::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- The mouse button released outside the element means refusal to interact with the element if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { this.SetCheckBackgroundColor(this.BackgroundColorInit(),false); this.SetCheckBorderColor(this.CheckBorderColorInit(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); //--- Send the event: this.SendEvent(::ChartID(),WF_CONTROL_EVENT_CLICK_CANCEL); //--- Send a test entry to the journal Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel")); } //--- The mouse button released within the element means a click on the control else { this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseOver(),false); this.SetCheckBorderColor(this.CheckBorderColorMouseOver(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); if(!this.Checked()) this.SetChecked(true); //--- Send the event: this.SendEvent(::ChartID(),WF_CONTROL_EVENT_CLICK); //--- Send a test entry to the journal Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.Checked()=",this.Checked(),", ID=",this.ID(),", Group=",this.Group()); } this.Redraw(false); } //+------------------------------------------------------------------+
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh,
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| left mouse button released | //+------------------------------------------------------------------+ void CCheckBox::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- The mouse button released outside the element means refusal to interact with the element if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { this.SetCheckBackgroundColor(this.CheckBackgroundColorInit(),false); this.SetCheckBorderColor(this.CheckBorderColorInit(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); this.SetBackgroundColor(this.BackgroundColorInit(),false); //--- Send the event: this.SendEvent(::ChartID(),WF_CONTROL_EVENT_CLICK_CANCEL); //--- Send a test entry to the journal Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel")); } //--- The mouse button released within the element means a click on the control else { this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseOver(),false); this.SetCheckBorderColor(this.CheckBorderColorMouseOver(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); this.SetBackgroundColor(this.BackgroundColorMouseOver(),false); this.SetChecked(!this.Checked()); //--- Send the event: this.SendEvent(::ChartID(),WF_CONTROL_EVENT_CLICK); //--- Send a test entry to the journal Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.Checked()=",this.Checked(),", ID=",this.ID()); } this.Redraw(false); } //+------------------------------------------------------------------+
Nach der Veröffentlichung des vorigen Artikels habe ich festgestellt, dass zwei Klassendateien von Schaltflächenobjekten mit Links-Rechts- und Oben-Unten-Pfeilen nicht mehr selbständig kompiliert werden. Sie können nur als Teil der Bibliothek kompiliert werden — beim Kompilieren der Hauptdatei der Engine.mqh-Bibliothek, aber nicht allein, was nicht korrekt ist. Um dies zu beheben, müssen wir die Liste der eingebundenen Dateien in den Dateien dieser Objekte ändern.
Zuvor hatte ich die Objektdatei des Panels eingebunden:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Containers\Panel.mqh" //+------------------------------------------------------------------+
Jetzt werde ich nur die Dateien einfügen, die von diesen Klassen verwendet werden sollen.
Für die Schaltflächenobjektdatei mit Auf-/Ab-Pfeilen \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ArrowUpDownBox.mqh:
//+------------------------------------------------------------------+ //| ArrowUpDownBox.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Containers\Container.mqh" #include "..\Helpers\ArrowUpButton.mqh" #include "..\Helpers\ArrowDownButton.mqh" //+------------------------------------------------------------------+
Für die Schaltflächenobjektdatei mit Links-Rechts-Pfeilen \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ArrowLeftRightBox.mqh:
//+------------------------------------------------------------------+ //| ArrowLeftRightBox.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Containers\Container.mqh" #include "..\Helpers\ArrowLeftButton.mqh" #include "..\Helpers\ArrowRightButton.mqh" //+------------------------------------------------------------------+
Jetzt werden beide Dateien normal kompiliert, sowohl unabhängig als auch als Teil der Bibliothek.
Finalisieren wir die Klasse des Objekts der Registerkartentitels von TabControl in \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\TabHeader.mqh.
Aus der Ereignisbehandlung von „Der Cursor befindet sich im aktiven Bereich, die linke Maustaste wurde geklickt“ entfernen wir den Codeblock für die Erstellung eines Ereignisses:
//--- Send the test message to the journal Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.State()=",this.State(),", ID=",this.ID(),", Group=",this.Group()); //--- Create the event: //--- Get the base and main objects CWinFormBase *base=this.GetBase(); CWinFormBase *main=this.GetMain(); //--- in the 'long' event parameter, pass a string, while in the 'double' parameter, the tab header location column long lp=this.Row(); double dp=this.Column(); //--- in the 'string' parameter of the event, pass the names of the main and base objects separated by ";" string name_main=(main!=NULL ? main.Name() : ""); string name_base=(base!=NULL ? base.Name() : ""); string sp=name_main+";"+name_base; //--- Send the tab selection event to the chart of the control program ::EventChartCustom(::ChartID(),WF_CONTROL_EVENT_TAB_SELECT,lp,dp,sp); //--- Set the frame color for "The cursor is over the active area" status this.SetBorderColor(this.BorderColorMouseOver(),false); } } //+------------------------------------------------------------------+
Jetzt haben wir die Methode zum Erstellen und Senden eines Ereignisses. Nutzen wir sie also:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| left mouse button released | //+------------------------------------------------------------------+ void CTabHeader::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- The mouse button released outside the element means refusal to interact with the element if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { //--- If this is a simple button, set the initial background and text color if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorInit(),false); this.SetForeColor(this.ForeColorInit(),false); } //--- If this is the toggle button, set the initial background and text color depending on whether the button is pressed or not else { this.SetBackgroundColor(!this.State() ? this.BackgroundColorInit() : this.BackgroundStateOnColorInit(),false); this.SetForeColor(!this.State() ? this.ForeColorInit() : this.ForeStateOnColorInit(),false); } //--- Set the initial frame color this.SetBorderColor(this.BorderColorInit(),false); //--- Send the event: this.SendEvent(::ChartID(),WF_CONTROL_EVENT_CLICK_CANCEL); //--- Send the test message to the journal Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel")); } //--- The mouse button released within the element means a click on the control else { //--- If this is a simple button, set the background and text color for "The cursor is over the active area" status if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorMouseOver(),false); this.SetForeColor(this.ForeColorMouseOver(),false); } //--- If this is the toggle button, else { //--- if the button does not work in the group, set its state to the opposite, if(!this.GroupButtonFlag()) this.SetState(!this.State()); //--- if the button is not pressed yet, set it to the pressed state else if(!this.State()) this.SetState(true); //--- set the background and text color for "The cursor is over the active area" status depending on whether the button is clicked or not this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseOver() : this.BackgroundColorMouseOver(),false); this.SetForeColor(this.State() ? this.ForeStateOnColorMouseOver() : this.ForeColorMouseOver(),false); //--- Get the field object corresponding to the header CWinFormBase *field=this.GetFieldObj(); if(field!=NULL) { //--- Display the field, bring it to the front, draw a frame and crop the excess field.Show(); field.BringToTop(); field.DrawFrame(); field.Crop(); } } //--- Send the event: this.SendEvent(::ChartID(),WF_CONTROL_EVENT_TAB_SELECT); //--- Send the test message to the journal Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.State()=",this.State(),", ID=",this.ID(),", Group=",this.Group()); //--- Set the frame color for "The cursor is over the active area" status this.SetBorderColor(this.BorderColorMouseOver(),false); } //--- Redraw an object and a chart this.Redraw(true); } //+------------------------------------------------------------------+
Wenn wir z. B. die Kopfleiste nach links verschieben, wird die linke Kopfzeile aus dem Steuerelement entfernt, und die Kopfzeile rechts davon nimmt ihren Platz ein. Da die Anfangskoordinate der Kopfzeilen um zwei Pixel nach rechts vom linken Rand des Containers verschoben wird, bleibt die Kopfzeile, die über den linken Rand hinausgeht, in eben diesem Bereich von zwei Pixeln sichtbar — sie wird an den Rändern des Containerbereichs abgeschnitten, innerhalb dessen die Elemente sichtbar sein sollten.
Um diesen dünnen sichtbaren Teil der Kopfzeile, der über den linken Rand hinausgeht, auszublenden, müssen wir die Größe des Containerbereichs, in dem die angehängten Objekte angezeigt werden, leicht anpassen. Außerdem muss berücksichtigt werden, ob die Kopfzeile ausgewählt ist oder nicht, da sich die Größe der ausgewählten Kopfzeile auf jeder Seite um zwei Pixel erhöht. Das bedeutet, dass wir die Größe des Bereichs des Containers, in dem die Objekte sichtbar sind, dynamisch anpassen müssen, je nachdem, welches Objekt sich am Rand befindet. Wenn diese Option gewählt wird, bleibt die Größe unverändert. Andernfalls wird es um zwei Pixel verkleinert.
Bei der Methode, die das durch den berechneten rechteckigen Sichtbarkeitsbereich umrissene Bild beschneidet, wird zusätzlich die Größe des Container-Sichtbarkeitsbereichs angepasst und der erhaltene Wert auf die Kanten des sichtbaren Bereichs angewendet:
//+------------------------------------------------------------------+ //| Crop the image outlined by the calculated | //| rectangular visibility scope | //+------------------------------------------------------------------+ void CTabHeader::Crop(void) { //--- Get the pointer to the base object CGCnvElement *base=this.GetBase(); //--- If the object does not have a base object it is attached to, then there is no need to crop the hidden areas - leave if(base==NULL) return; //--- Set the initial coordinates and size of the visibility scope to the entire object int vis_x=0; int vis_y=0; int vis_w=this.Width(); int vis_h=this.Height(); //--- Set the size of the top, bottom, left and right areas that go beyond the container int crop_top=0; int crop_bottom=0; int crop_left=0; int crop_right=0; //--- Get the additional size, by which to crop the titles when the arrow buttons are visible int add_size_lr=(this.IsVisibleLeftRightBox() ? this.m_arr_butt_lr_size : 0); int add_size_ud=(this.IsVisibleUpDownBox() ? this.m_arr_butt_ud_size : 0); int dec_size_vis=(this.State() ? 0 : 2); //--- Calculate the boundaries of the container area, inside which the object is fully visible int top=fmax(base.CoordY()+(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_TOP),base.CoordYVisibleArea())+dec_size_vis+(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT ? add_size_ud : 0); int bottom=fmin(base.BottomEdge()-(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_BOTTOM),base.BottomEdgeVisibleArea()+1)-dec_size_vis-(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT ? add_size_ud : 0); int left=fmax(base.CoordX()+(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_LEFT),base.CoordXVisibleArea())+dec_size_vis; int right=fmin(base.RightEdge()-(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_RIGHT),base.RightEdgeVisibleArea()+1)-add_size_lr; //--- Calculate the values of the top, bottom, left and right areas, at which the object goes beyond //--- the boundaries of the container area, inside which the object is fully visible crop_top=this.CoordY()-top; if(crop_top<0) vis_y=-crop_top; crop_bottom=bottom-this.BottomEdge()-1; if(crop_bottom<0) vis_h=this.Height()+crop_bottom-vis_y; crop_left=this.CoordX()-left; if(crop_left<0) vis_x=-crop_left; crop_right=right-this.RightEdge()-1; if(crop_right<0) vis_w=this.Width()+crop_right-vis_x; //--- If there are areas that need to be hidden, call the cropping method with the calculated size of the object visibility scope if(crop_top<0 || crop_bottom<0 || crop_left<0 || crop_right<0) this.Crop(vis_x,vis_y,vis_w,vis_h); } //+------------------------------------------------------------------+
Wenn Kopfzeilen über den Container hinausgehen, wird ihr schmaler, zwei Pixel großer Bereich nicht angezeigt.
In der WinForms-Objektklassendatei TabControl \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqh, und zwar in ihrem privaten Abschnitt, deklarieren wir neue Methoden:
//--- Return the list of (1) headers, (2) tab fields, the pointer to the (3) up-down and (4) left-right button objects CArrayObj *GetListHeaders(void) { return this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); } CArrayObj *GetListFields(void) { return this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD); } CArrowUpDownBox *GetArrUpDownBox(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,0); } CArrowLeftRightBox *GetArrLeftRightBox(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,0); } //--- Return the pointer to the (1) last and (2) first visible tab header CTabHeader *GetLastHeader(void) { return this.GetTabHeader(this.TabPages()-1); } CTabHeader *GetFirstVisibleHeader(void); //--- Set the tab as selected void SetSelected(const int index); //--- Set the tab as released void SetUnselected(const int index); //--- Set the number of a selected tab void SetSelectedTabPageNum(const int value) { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,value); } //--- Arrange the tab headers according to the set modes void ArrangeTabHeaders(void); //--- Arrange the tab headers at the (1) top, (2) bottom, (3) left and (4) right void ArrangeTabHeadersTop(void); void ArrangeTabHeadersBottom(void); void ArrangeTabHeadersLeft(void); void ArrangeTabHeadersRight(void); //--- Stretch tab headers by control size void StretchHeaders(void); //--- Stretch tab headers by (1) control width and height when positioned on the (2) left and (3) right void StretchHeadersByWidth(void); void StretchHeadersByHeightLeft(void); void StretchHeadersByHeightRight(void); //--- Scroll the header row (1) to the left, (2) to the right, (3) up when headers are on the left, (4) down, (3) up, (4) down void ScrollHeadersRowToLeft(void); void ScrollHeadersRowToRight(void); //--- Scroll the row of headers when they are located on the left (1) up, (2) down void ScrollHeadersRowLeftToUp(void); void ScrollHeadersRowLeftToDown(void); //--- Scroll the row of headers when they are located on the right (1) up, (2) down void ScrollHeadersRowRightToUp(void); void ScrollHeadersRowRightToDown(void); public:
und entfernen die öffentliche Methode
//--- Show the control virtual void Show(void); //--- Shift the header row void ShiftHeadersRow(const int selected); //--- Event handler virtual void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Constructor
Ich habe diese Methode im vorherigen Artikel im öffentlichen Bereich platziert. Die Kopfleiste wurde nach links verschoben, wenn eine teilweise ausgeblendete Kopfzeile angeklickt wurde. Dies wird nun durch die oben angegebenen Methoden geschehen. Außerdem können sie sowohl das Anklicken einer teilweise ausgeblendeten Kopfzeile als auch das Anklicken der Schaltfläche, die das Scrollen der Kopfleiste steuert, verarbeiten.
Jede der angegebenen Methoden dient dazu, die Kopfleiste in eine bestimmte Richtung zu verschieben:
- Wenn sich die Überschriften oben oder unten befinden — zwei Methoden für den Bildlauf nach links und rechts.
- Wenn sich die Überschriften auf der linken Seite befinden — zwei Methoden für den Bildlauf nach links und den Bildlauf nach rechts.
- Wenn sich die Überschriften auf der rechten Seite befinden — zwei Methoden für den Bildlauf nach links und den Bildlauf nach rechts.
Beim Erstellen von Schaltflächenobjekten mit Links-Rechts- und Oben-Unten-Pfeilen in der Methode, die die angegebene Anzahl von Registerkarten erstellt, müssen wir die Haupt- und Basisobjekte für diese Objekte und jedes Pfeilschaltflächenobjekt innerhalb dieser Objekte angeben, da wir sonst nicht in der Lage sind, diese Objekte zu finden, wenn wir eine Ereignismeldung erstellen, wenn die Schaltfläche angeklickt wird:
//+------------------------------------------------------------------+ //| Create the specified number of tabs | //+------------------------------------------------------------------+ bool CTabControl::CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="") { //--- Calculate the size and initial coordinates of the tab title int w=(tab_w==0 ? this.ItemWidth() : tab_w); int h=(tab_h==0 ? this.ItemHeight() : tab_h); //--- In the loop by the number of tabs //---... //---... //--- Create left-right and up-down button objects this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,this.Width()-32,0,15,15,clrNONE,255,this.Active(),false); this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,0,this.Height()-32,15,15,clrNONE,255,this.Active(),false); //--- CArrowLeftRightBox *box_lr=this.GetArrLeftRightBox(); if(box_lr!=NULL) { this.SetVisibleLeftRightBox(false); this.SetSizeLeftRightBox(box_lr.Width()); box_lr.SetMain(this.GetMain()); box_lr.SetBase(this.GetObject()); box_lr.SetBorderStyle(FRAME_STYLE_NONE); box_lr.SetBackgroundColor(CLR_CANV_NULL,true); box_lr.SetOpacity(0); box_lr.Hide(); CArrowLeftButton *lb=box_lr.GetArrowLeftButton(); if(lb!=NULL) { lb.SetMain(this.GetMain()); lb.SetBase(box_lr); } CArrowRightButton *rb=box_lr.GetArrowRightButton(); if(rb!=NULL) { rb.SetMain(this.GetMain()); rb.SetBase(box_lr); } } //--- CArrowUpDownBox *box_ud=this.GetArrUpDownBox(); if(box_ud!=NULL) { this.SetVisibleUpDownBox(false); this.SetSizeUpDownBox(box_ud.Height()); box_ud.SetMain(this.GetMain()); box_ud.SetBase(this.GetObject()); box_ud.SetBorderStyle(FRAME_STYLE_NONE); box_ud.SetBackgroundColor(CLR_CANV_NULL,true); box_ud.SetOpacity(0); box_ud.Hide(); CArrowDownButton *db=box_ud.GetArrowDownButton(); if(db!=NULL) { db.SetMain(this.GetMain()); db.SetBase(box_ud); } CArrowUpButton *ub=box_ud.GetArrowUpButton(); if(ub!=NULL) { ub.SetMain(this.GetMain()); ub.SetBase(box_ud); } } //--- Arrange all titles in accordance with the specified display modes and select the specified tab this.ArrangeTabHeaders(); this.Select(selected_page,true); return true; } //+------------------------------------------------------------------+
Nachdem wir Schaltflächenobjekte mit Links-Rechts- und Oben-Unten-Pfeilen erstellt haben, erhalten wir den Zeiger auf das erstellte Objekt und setzen die Haupt- und Basisobjekte dafür. Aus dem empfangenen Objekt holen wir uns seine Pfeilschaltflächenobjekte und geben für jedes von ihnen das Haupt- und Basisobjekt an.
In der Methode, die das Steuerelement anzeigt, fügen wir die Überprüfung des Objektanzeigeflags hinzu:
//+------------------------------------------------------------------+ //| Show the control | //+------------------------------------------------------------------+ void CTabControl::Show(void) { //--- If the element should not be displayed (hidden inside another control), leave if(!this.Displayed()) return; //--- Get the list of all tab headers CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; //--- If the object has a shadow, display it if(this.m_shadow_obj!=NULL) this.m_shadow_obj.Show(); //--- Display the container CGCnvElement::Show(); //--- Move all elements of the object to the foreground this.BringToTop(); } //+------------------------------------------------------------------+
Wenn der Modus der manuellen Anzeigeverwaltung für das Objekt aktiviert ist, dann verlassen wir die Methode.
Die Methode, die den Zeiger auf die erste sichtbare Kopfzeile zurückgibt:
//+------------------------------------------------------------------+ //| Return the pointer to the first visible header | //+------------------------------------------------------------------+ CTabHeader *CTabControl::GetFirstVisibleHeader(void) { for(int i=0;i<this.TabPages();i++) { CTabHeader *obj=this.GetTabHeader(i); if(obj==NULL) continue; switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : case CANV_ELEMENT_ALIGNMENT_BOTTOM : if(obj.CoordX()==this.CoordXWorkspace()+(obj.State() ? 0 : 2)) return obj; break; case CANV_ELEMENT_ALIGNMENT_LEFT : if(obj.BottomEdge()==this.BottomEdgeWorkspace()+(obj.State() ? 0 : -2)) return obj; break; case CANV_ELEMENT_ALIGNMENT_RIGHT : if(obj.CoordY()==this.CoordYWorkspace()+(obj.State() ? 0 : 2)) return obj; break; default: break; } } return NULL; } //+------------------------------------------------------------------+
Die erste sichtbare Kopfzeile ist diejenige auf der linken Seite, wenn sich die Kopfzeilen oben/unten befinden, oder unten, wenn sich die Kopfzeilen links befinden, oder oben, wenn sich die Kopfzeilen rechts auf dem Steuerelement befinden. Um diese extreme Kopfzeile zu finden, müssen wir eine Schleife durch alle Kopfzeilen des Objekts ziehen, um die Koordinaten ihrer Position in Übereinstimmung mit der Position der Kopfzeile zu überprüfen. Um ganz oben zu stehen, sollte die Kopfzeile an der ersten X-Koordinate des Containers platziert werden. In diesem Fall wird die Anfangskoordinate des Titels, wenn er nicht ausgewählt ist, um zwei Pixel nach rechts verschoben. Ähnlich verhält es sich mit einer anderen Position der Kopfleiste.
Die Methode in der Schleife sucht nach einer Übereinstimmung zwischen den Koordinaten des Objekts und den Koordinaten des Containers, abhängig von der Position der Kopfzeile, und gibt den Zeiger auf das gefundene Objekt zurück. Wird keiner der Header gefunden, gibt die Methode NULL zurück.
Die Methode, mit der die Kopfleiste nach links verschoben wird:
//+------------------------------------------------------------------+ //| Scroll the header bar to the left | //+------------------------------------------------------------------+ void CTabControl::ScrollHeadersRowToLeft(void) { //--- If there are multiline headers, leave if(this.Multiline()) return; //--- Declare the variables and get the index of the selected tab int shift=0; int correct_size=0; int selected=this.SelectedTabPageNum(); //--- Get the first visible header CTabHeader *first=this.GetFirstVisibleHeader(); if(first==NULL) return; //--- If the first visible header is selected, set the size adjustment value if(first.PageNumber()==selected) correct_size=4; //--- Get the pointer to the very last header CTabHeader *last=this.GetLastHeader(); if(last==NULL) return; //--- If the last heading is fully visible, leave since the shift of all headers to the left is completed if(last.RightEdge()<=this.RightEdgeWorkspace()) return; //--- Get the shift size shift=first.Width()-correct_size; //--- In the loop by all headers for(int i=0;i<this.TabPages();i++) { //--- get the next header CTabHeader *header=this.GetTabHeader(i); if(header==NULL) continue; //--- and, if the header is successfully shifted to the left by 'shift' value, if(header.Move(header.CoordX()-shift,header.CoordY())) { //--- save its new relative coordinates header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); //--- If the title has gone beyond the left edge, int x=(i==selected ? 0 : 2); if(header.CoordX()-x<this.CoordXWorkspace()) { //--- crop and hide it header.Crop(); header.Hide(); //--- Get the selected header CTabHeader *header_selected=this.GetTabHeader(selected); if(header_selected==NULL) continue; //--- Get the tab field corresponding to the selected header CTabField *field_selected=header_selected.GetFieldObj(); if(field_selected==NULL) continue; //--- Draw the field frame field_selected.DrawFrame(); field_selected.Update(); } //--- If the header fits the visible area of the control, else { //--- display and redraw it header.Show(); header.Redraw(false); //--- Get the tab field corresponding to the header CTabField *field=header.GetFieldObj(); if(field==NULL) continue; //--- If this is a selected header, if(i==selected) { //--- Draw the field frame field.DrawFrame(); field.Update(); } } } } //--- Get the selected header CTabHeader *obj=this.GetTabHeader(selected); //--- If the header is placed in the visible part of the control, bring it to the foreground if(obj!=NULL && obj.CoordX()>=this.CoordXWorkspace() && obj.RightEdge()<=this.RightEdgeWorkspace()) obj.BringToTop(); //--- Redraw the chart to display changes immediately ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
Die Methode, die Kopfleiste nach rechts zu verschiebt:
//+------------------------------------------------------------------+ //| Scroll the header bar to the right | //+------------------------------------------------------------------+ void CTabControl::ScrollHeadersRowToRight(void) { //--- If there are multiline headers, leave if(this.Multiline()) return; //--- Declare the variables and get the index of the selected tab int shift=0; int correct_size=0; int selected=this.SelectedTabPageNum(); //--- Get the first visible header CTabHeader *first=this.GetFirstVisibleHeader(); if(first==NULL) return; //--- Get the header located before the first visible one CTabHeader *prev=this.GetTabHeader(first.PageNumber()-1); //--- If there is no such header, leave since the shift of all headers to the right is completed if(prev==NULL) return; //--- If the header is selected, specify the size adjustment value if(prev.PageNumber()==selected) correct_size=4; //--- Get the shift size shift=prev.Width()-correct_size; //--- In the loop by all headers for(int i=0;i<this.TabPages();i++) { //--- get the next header CTabHeader *header=this.GetTabHeader(i); if(header==NULL) continue; //--- and, if the header is successfully shifted to the right by 'shift' value, if(header.Move(header.CoordX()+shift,header.CoordY())) { //--- save its new relative coordinates header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); //--- If the title goes beyond the left edge, int x=(i==selected ? 0 : 2); if(header.CoordX()-x<this.CoordXWorkspace()) { //--- crop and hide it header.Crop(); header.Hide(); } //--- If the header fits the visible area of the control, else { //--- display and redraw it header.Show(); header.Redraw(false); //--- Get the tab field corresponding to the header CTabField *field=header.GetFieldObj(); if(field==NULL) continue; //--- If this is a selected header, if(i==selected) { //--- Draw the field frame field.DrawFrame(); field.Update(); } } } } //--- Get the selected header CTabHeader *obj=this.GetTabHeader(selected); //--- If the header is placed in the visible part of the control, bring it to the foreground if(obj!=NULL && obj.CoordX()>=this.CoordXWorkspace() && obj.RightEdge()<=this.RightEdgeWorkspace()) obj.BringToTop(); //--- Redraw the chart to display changes immediately ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
Die Methode, die die Kopfzeile nach oben schiebt, wenn sich die Kopfzeilen auf der linken Seite befinden:
//+------------------------------------------------------------------+ //| Scroll the header row up when the headers are on the left | //+------------------------------------------------------------------+ void CTabControl::ScrollHeadersRowLeftToUp(void) { //--- If there are multiline headers, leave if(this.Multiline()) return; //--- Declare the variables and get the index of the selected tab int shift=0; int correct_size=0; int selected=this.SelectedTabPageNum(); //--- Get the first visible header CTabHeader *first=this.GetFirstVisibleHeader(); if(first==NULL) return; //--- Get the header located before the first visible one CTabHeader *prev=this.GetTabHeader(first.PageNumber()-1); //--- If there is no such header, leave since the shift of all headers upwards is completed if(prev==NULL) return; //--- If the header is selected, specify the size adjustment value if(prev.PageNumber()==selected) correct_size=4; //--- Get the shift size shift=prev.Height()-correct_size; //--- In the loop by all headers for(int i=0;i<this.TabPages();i++) { //--- get the next header CTabHeader *header=this.GetTabHeader(i); if(header==NULL) continue; //--- and, if the header is successfully shifted upwards by 'shift' value, if(header.Move(header.CoordX(),header.CoordY()-shift)) { //--- save its new relative coordinates header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); //--- If the header goes beyond the lower edge, int x=(i==selected ? 0 : 2); if(header.BottomEdge()+x>this.BottomEdgeWorkspace()) { //--- crop and hide it header.Crop(); header.Hide(); } //--- If the header fits the visible area of the control, else { //--- display and redraw it header.Show(); header.Redraw(false); //--- Get the tab field corresponding to the header CTabField *field=header.GetFieldObj(); if(field==NULL) continue; //--- If this is a selected header, if(i==selected) { //--- Draw the field frame field.DrawFrame(); field.Update(); } } } } //--- Get the selected header CTabHeader *obj=this.GetTabHeader(selected); //--- If the header is placed in the visible part of the control, bring it to the foreground if(obj!=NULL && obj.CoordY()>=this.CoordYWorkspace() && obj.BottomEdge()<=this.BottomEdgeWorkspace()) obj.BringToTop(); //--- Redraw the chart to display changes immediately ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
Die Methode, die die Kopfzeile nach unten schiebt, wenn die Kopfzeilen auf der linken Seite sind:
//+------------------------------------------------------------------+ //| Scroll the header row down when the headers are on the left | //+------------------------------------------------------------------+ void CTabControl::ScrollHeadersRowLeftToDown(void) { //--- If there are multiline headers, leave if(this.Multiline()) return; //--- Declare the variables and get the index of the selected tab int shift=0; int correct_size=0; int selected=this.SelectedTabPageNum(); //--- Get the first visible header CTabHeader *first=this.GetFirstVisibleHeader(); if(first==NULL) return; //--- If the first visible header is selected, set the size adjustment value if(first.PageNumber()==selected) correct_size=4; //--- Get the pointer to the very last header CTabHeader *last=this.GetLastHeader(); if(last==NULL) return; //--- If the last heading is fully visible, leave since the shift of all headers downwards is completed if(last.CoordY()>=this.CoordYWorkspace()) return; //--- Get the shift size shift=first.Height()-correct_size; //--- In the loop by all headers for(int i=0;i<this.TabPages();i++) { //--- get the next header CTabHeader *header=this.GetTabHeader(i); if(header==NULL) continue; //--- and, if the header is successfully shifted downwards by 'shift' value, if(header.Move(header.CoordX(),header.CoordY()+shift)) { //--- save its new relative coordinates header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); //--- If the header has gone beyond the lower edge, int x=(i==selected ? 0 : 2); if(header.BottomEdge()-x>this.BottomEdgeWorkspace()) { //--- crop and hide it header.Crop(); header.Hide(); //--- Get the selected header CTabHeader *header_selected=this.GetTabHeader(selected); if(header_selected==NULL) continue; //--- Get the tab field corresponding to the selected header CTabField *field_selected=header_selected.GetFieldObj(); if(field_selected==NULL) continue; //--- Draw the field frame field_selected.DrawFrame(); field_selected.Update(); } //--- If the header fits the visible area of the control, else { //--- display and redraw it header.Show(); header.Redraw(false); //--- Get the tab field corresponding to the header CTabField *field=header.GetFieldObj(); if(field==NULL) continue; //--- If this is a selected header, if(i==selected) { //--- Draw the field frame field.DrawFrame(); field.Update(); } } } } //--- Get the selected header CTabHeader *obj=this.GetTabHeader(selected); //--- If the header is placed in the visible part of the control, bring it to the foreground if(obj!=NULL && obj.CoordY()>=this.CoordYWorkspace() && obj.BottomEdge()<=this.BottomEdgeWorkspace()) obj.BringToTop(); //--- Redraw the chart to display changes immediately ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
Die Methode, die die Kopfzeile nach oben schiebt, wenn sich die Kopfzeilen auf der rechten Seite befinden:
//+------------------------------------------------------------------+ //| Scroll the header row up when the headers are on the right | //+------------------------------------------------------------------+ void CTabControl::ScrollHeadersRowRightToUp(void) { //--- If there are multiline headers, leave if(this.Multiline()) return; //--- Declare the variables and get the index of the selected tab int shift=0; int correct_size=0; int selected=this.SelectedTabPageNum(); //--- Get the first visible header CTabHeader *first=this.GetFirstVisibleHeader(); if(first==NULL) return; //--- If the first visible header is selected, set the size adjustment value if(first.PageNumber()==selected) correct_size=4; //--- Get the pointer to the very last header CTabHeader *last=this.GetLastHeader(); if(last==NULL) return; //--- If the last heading is fully visible, leave since the shift of all headers upwards is completed if(last.BottomEdge()<=this.BottomEdgeWorkspace()) return; //--- Get the shift size shift=first.Height()-correct_size; //--- In the loop by all headers for(int i=0;i<this.TabPages();i++) { //--- get the next header CTabHeader *header=this.GetTabHeader(i); if(header==NULL) continue; //--- and, if the header is successfully shifted upwards by 'shift' value, if(header.Move(header.CoordX(),header.CoordY()-shift)) { //--- save its new relative coordinates header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); //--- If the header has gone beyond the upper edge, int x=(i==selected ? 0 : 2); if(header.CoordY()-x<this.CoordYWorkspace()) { //--- crop and hide it header.Crop(); header.Hide(); //--- Get the selected header CTabHeader *header_selected=this.GetTabHeader(selected); if(header_selected==NULL) continue; //--- Get the tab field corresponding to the selected header CTabField *field_selected=header_selected.GetFieldObj(); if(field_selected==NULL) continue; //--- Draw the field frame field_selected.DrawFrame(); field_selected.Update(); } //--- If the header fits the visible area of the control, else { //--- display and redraw it header.Show(); header.Redraw(false); //--- Get the tab field corresponding to the header CTabField *field=header.GetFieldObj(); if(field==NULL) continue; //--- If this is a selected header, if(i==selected) { //--- Draw the field frame field.DrawFrame(); field.Update(); } } } } //--- Get the selected header CTabHeader *obj=this.GetTabHeader(selected); //--- If the header is placed in the visible part of the control, bring it to the foreground if(obj!=NULL && obj.CoordY()>=this.CoordYWorkspace() && obj.BottomEdge()<=this.BottomEdgeWorkspace()) obj.BringToTop(); //--- Redraw the chart to display changes immediately ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
Die Methode, die die Kopfzeile nach unten schiebt, wenn die Kopfzeilen auf der rechten Seite sind:
//+------------------------------------------------------------------+ //| Scroll the header row down when the headers are on the right | //+------------------------------------------------------------------+ void CTabControl::ScrollHeadersRowRightToDown(void) { //--- If there are multiline headers, leave if(this.Multiline()) return; //--- Declare the variables and get the index of the selected tab int shift=0; int correct_size=0; int selected=this.SelectedTabPageNum(); //--- Get the first visible header CTabHeader *first=this.GetFirstVisibleHeader(); if(first==NULL) return; //--- Get the header located before the first visible one CTabHeader *prev=this.GetTabHeader(first.PageNumber()-1); //--- If there is no such header, leave since the shift of all headers downwards is completed if(prev==NULL) return; //--- If the header is selected, specify the size adjustment value if(prev.PageNumber()==selected) correct_size=4; //--- Get the shift size shift=prev.Height()-correct_size; //--- In the loop by all headers for(int i=0;i<this.TabPages();i++) { //--- get the next header CTabHeader *header=this.GetTabHeader(i); if(header==NULL) continue; //--- and, if the header is successfully shifted downwards by 'shift' value, if(header.Move(header.CoordX(),header.CoordY()+shift)) { //--- save its new relative coordinates header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); //--- If the title goes beyond the upper edge int x=(i==selected ? 0 : 2); if(header.CoordY()-x<this.CoordYWorkspace()) { //--- crop and hide it header.Crop(); header.Hide(); } //--- If the header fits the visible area of the control, else { //--- display and redraw it header.Show(); header.Redraw(false); //--- Get the tab field corresponding to the header CTabField *field=header.GetFieldObj(); if(field==NULL) continue; //--- If this is a selected header, if(i==selected) { //--- Draw the field frame field.DrawFrame(); field.Update(); } } } } //--- Get the selected header CTabHeader *obj=this.GetTabHeader(selected); //--- If the header is placed in the visible part of the control, bring it to the foreground if(obj!=NULL && obj.CoordY()>=this.CoordYWorkspace() && obj.BottomEdge()<=this.BottomEdgeWorkspace()) obj.BringToTop(); //--- Redraw the chart to display changes immediately ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
Die Logik aller Methoden zum Verschieben von Kopfzeilen ist im Methodencode vollständig beschrieben. Sie sind alle identisch und unterscheiden sich nur geringfügig in Bezug auf die Berechnung der Verschiebung und den Sichtbarkeitsbereich. Ich hoffe, dass die Methoden keiner weiteren Erklärung bedürfen. Wenn Sie Fragen haben, können Sie diese gerne im Kommentarteil stellen.
Die Ereignisbehandlung:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CTabControl::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Adjust subwindow Y shift CGCnvElement::OnChartEvent(id,lparam,dparam,sparam); //--- If the tab is selected if(id==WF_CONTROL_EVENT_TAB_SELECT) { //--- Get the header of the selected tab CTabHeader *header=this.GetTabHeader(this.SelectedTabPageNum()); if(header==NULL) return; //--- Depending on the location of the header row switch(this.Alignment()) { //--- Headers at the top/bottom case CANV_ELEMENT_ALIGNMENT_TOP : case CANV_ELEMENT_ALIGNMENT_BOTTOM : //--- If the header is cropped, shift the header row to the left if(header.RightEdge()>this.RightEdgeWorkspace()) this.ScrollHeadersRowToLeft(); break; //--- Headers on the left case CANV_ELEMENT_ALIGNMENT_LEFT : //--- If the header is cropped, shift the header row downwards if(header.CoordY()<this.CoordYWorkspace()) this.ScrollHeadersRowLeftToDown(); break; //--- Headers on the right case CANV_ELEMENT_ALIGNMENT_RIGHT : //--- If the header is cropped, shift the header row upwards Print(DFUN,"header.BottomEdge=",header.BottomEdge(),", this.BottomEdgeWorkspace=",this.BottomEdgeWorkspace()); if(header.BottomEdge()>this.BottomEdgeWorkspace()) this.ScrollHeadersRowRightToUp(); break; default: break; } } //--- When clicking on any header row scroll button if(id>=WF_CONTROL_EVENT_CLICK_SCROLL_LEFT && id<=WF_CONTROL_EVENT_CLICK_SCROLL_DOWN) { //--- Get the header of the last tab CTabHeader *header=this.GetTabHeader(this.GetListHeaders().Total()-1); if(header==NULL) return; int hidden=0; //--- When clicking on the left arrow header row scroll button if(id==WF_CONTROL_EVENT_CLICK_SCROLL_LEFT) this.ScrollHeadersRowToRight(); //--- When clicking on the right arrow header row scroll button if(id==WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT) this.ScrollHeadersRowToLeft(); //--- When clicking on the down arrow header row scroll button if(id==WF_CONTROL_EVENT_CLICK_SCROLL_DOWN) { //--- Depending on the location of the header row switch(this.Alignment()) { //--- scroll the headings upwards using the appropriate method case CANV_ELEMENT_ALIGNMENT_LEFT : this.ScrollHeadersRowLeftToUp(); break; case CANV_ELEMENT_ALIGNMENT_RIGHT : this.ScrollHeadersRowRightToUp(); break; default: break; } } //--- When clicking on the up arrow header row scroll button if(id==WF_CONTROL_EVENT_CLICK_SCROLL_UP) { //--- Depending on the location of the header row switch(this.Alignment()) { //--- scroll the headings downwards using the appropriate method case CANV_ELEMENT_ALIGNMENT_LEFT : this.ScrollHeadersRowLeftToDown(); break; case CANV_ELEMENT_ALIGNMENT_RIGHT : this.ScrollHeadersRowRightToDown(); break; default: break; } } } } //+------------------------------------------------------------------+
Jetzt behandeln wir jedes Ereignis (entweder die Auswahl einer teilweise ausgeblendeten Registerkartenüberschrift oder das Klicken auf die Schaltfläche zum Scrollen der Kopfleiste) mit den oben genannten Methoden. Abhängig von der Position der Kopfzeile und dem Klick-Ereignis auf die Schaltfläche oder die Kopfzeile rufen wir im Allgemeinen die entsprechende Methode auf, um die Kopfzeile zu verschieben.
In der Kollektionsklasse der grafischen Elemente in \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh, und zwar in ihrer Ereignisbehandlung, müssen wir nun die von den WinForms-Objekten empfangenen Ereignisse korrekt behandeln. Um dies zu erreichen, müssen wir drei Namen aus dem String-Parameter „sparam“ abrufen, das Basisobjekt finden und das Objekt, das das Ereignis erzeugt hat, daraus abrufen. Wenn das gefundene Objekt zu TabControl gehört, rufen wir die Ereignisbehandlung von TabControl auf, indem wir die Ereignis-ID an ihn senden.
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { CGStdGraphObj *obj_std=NULL; // Pointer to the standard graphical object CGCnvElement *obj_cnv=NULL; // Pointer to the graphical element object on canvas ushort idx=ushort(id-CHARTEVENT_CUSTOM); //--- Processing WinForms control events if(idx>WF_CONTROL_EVENT_NO_EVENT && idx<WF_CONTROL_EVENTS_NEXT_CODE) { //--- Declare the array of names and enter the names of three objects, set in 'sparam' and separated by ";", into it string array[]; if(::StringSplit(sparam,::StringGetCharacter(";",0),array)!=3) { CMessage::ToLog(MSG_GRAPH_OBJ_FAILED_GET_OBJECT_NAMES); return; } //--- Get the main object by name CWinFormBase *main=this.GetCanvElement(array[0]); if(main==NULL) return; //--- Get the base object, inside which the event has occurred, from the main object by name CWinFormBase *base=main.GetElementByName(array[1]); CWinFormBase *base_elm=NULL; //--- If there is no element with the same name, then this is the base object of the event element bound to the base one - look for it in the list if(base==NULL) { //--- Get the list of all elements bound to the main object with the type set in the 'dparam' parameter CArrayObj *list_obj=CSelect::ByGraphCanvElementProperty(main.GetListElements(),CANV_ELEMENT_PROP_TYPE,(long)dparam,EQUAL); if(list_obj==NULL || list_obj.Total()==0) return; //--- In the loop by the obtained list for(int i=0;i<list_obj.Total();i++) { //--- get the next object base_elm=list_obj.At(i); if(base_elm==NULL) continue; //--- If the base object is found, get the bound object from it by name from array[1] base=base_elm.GetElementByName(array[1]); if(base!=NULL) break; } } //--- If failed to find the object here, exit if(base==NULL) return; //--- From the found base object, get the object the event occurred from by name CWinFormBase *object=base.GetElementByName(array[2]); if(object==NULL) return; //+------------------------------------------------------------------+ //| Clicking the control | //+------------------------------------------------------------------+ if(idx==WF_CONTROL_EVENT_CLICK) { //--- If TabControl type is set in dparam if((ENUM_GRAPH_ELEMENT_TYPE)dparam==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) { //--- Set the event type depending on the element type that generated the event int event_id= (object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT ? WF_CONTROL_EVENT_CLICK_SCROLL_LEFT : object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT ? WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT : object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP ? WF_CONTROL_EVENT_CLICK_SCROLL_UP : object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN ? WF_CONTROL_EVENT_CLICK_SCROLL_DOWN : WF_CONTROL_EVENT_NO_EVENT); //--- If the base control is received, call its event handler if(base_elm!=NULL) base_elm.OnChartEvent(event_id,lparam,dparam,sparam); } } //+------------------------------------------------------------------+ //| Selecting the TabControl tab | //+------------------------------------------------------------------+ if(idx==WF_CONTROL_EVENT_TAB_SELECT) { if(base!=NULL) base.OnChartEvent(idx,lparam,dparam,sparam); } } //--- Handle the events of renaming and clicking a standard graphical object if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG || id==CHARTEVENT_OBJECT_CLICK || idx==CHARTEVENT_OBJECT_CHANGE || idx==CHARTEVENT_OBJECT_DRAG || idx==CHARTEVENT_OBJECT_CLICK) { //--- Calculate the chart ID //---... //---...
Die gesamte Logik ist im Code beschrieben und bedarf keiner weiteren Erläuterungen.
Jetzt ist alles bereit für den Test.
Test
Um den Test durchzuführen, verwende ich den EA aus dem vorherigen Artikel und speichere ihn in \MQL5\Experts\TestDoEasy\Part119\ als TestDoEasy119.mq5.
Der einzige Unterschied zur vorherigen Version ist, dass ich 15 Registerkarten in TabControl implementiert habe:
//--- Create TabControl pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,InpTabControlX,InpTabControlY,pnl.Width()-30,pnl.Height()-40,clrNONE,255,true,false); CTabControl *tc=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0); if(tc!=NULL) { tc.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode); tc.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment); tc.SetMultiline(InpTabCtrlMultiline); tc.SetHeaderPadding(6,0); tc.CreateTabPages(15,0,56,20,TextByLanguage("Вкладка","TabPage")); //--- Create a text label with a tab description on each tab for(int j=0;j<tc.TabPages();j++) { tc.CreateNewElement(j,GRAPH_ELEMENT_TYPE_WF_LABEL,60,20,80,20,clrDodgerBlue,255,true,false); CLabel *label=tc.GetTabElement(j,0); if(label==NULL) continue; label.SetText("TabPage"+string(j+1)); } }
Es war möglich, 11 Registerkarten zu belassen, aber ich habe die Anzahl der Registerkarten erhöht, um die Leistung zu testen und nach einigen „Bugs“ zu suchen. Diese Zahl ist also nur das Ergebnis der Fehlersuche und -behebung beim Verschieben der ausgewählten Kopfzeile aus dem Container auf beiden Seiten.
Kompilieren Sie den EA und starten Sie ihn auf einem Chart:
Wie wir sehen können, funktioniert alles wie vorgesehen.
Es gibt jedoch zwei Unzulänglichkeiten: Wenn man mit dem Mauszeiger über den ausgeblendeten Kopfbereich der Registerkarte fahren, ändert die Kopfzeile ihre Farbe, als ob sie in diesem Bereich sichtbar wäre. Aus diesem Grund ändert sich die Größe des aktiven Bereichs des Steuerelements nicht, wenn die Größe des sichtbaren Bereichs geändert wird. Um dies zu beheben, muss ich den aktiven Bereich berechnen und in der Größe an den sichtbaren Bereich anpassen.
Die zweite Unzulänglichkeit besteht darin, dass zwei Pixel der ausgeblendeten Kopfzeile angezeigt werden, wenn man die ausgewählte Kopfzeile außerhalb des Containers bewegen und das Bedienfeld verschieben. Dies hat mit der Größenbestimmung der Registerkarte für die Bereichsberechnung zu tun, da die ausgewählte Kopfzeile auf jeder Seite um zwei Pixel größer wird. Um dies zu beheben, muss ich eine Möglichkeit finden, die Größe der angrenzenden Kopfzeile innerhalb des Registerkartenkopf-Objekts zu ermitteln, anhand derer die Größe des Sichtbarkeitsbereichs berechnet wird.
Ich werde dies in späteren Artikeln zusammen mit der Entwicklung eines neuen WinForms-Objekts behandeln.
Was kommt als Nächstes?
Im nächsten Artikel werde ich mit der Entwicklung des SplitContainer WinForms Objekts beginnen.
*Vorherige Artikel in dieser Reihe:
DoEasy. Steuerung (Teil 13): Optimierung der Interaktion von WinForms-Objekten mit der Maus, Beginn der Entwicklung des WinForms-Objekts TabControl
DoEasy. Steuerung (Teil 14): Neuer Algorithmus zur Benennung von grafischen Elementen. Fortsetzung der Arbeit am TabControl WinForms Objekt
DoEasy. Steuerung (Teil 15): TabControl WinForms Objekt — mehrere Reihen von Registerkartenüberschriften, Methoden zur Behandlung von Registerkarten
DoEasy. Steuerung (Teil 16): TabControl WinForms-Objekt — mehrere Reihen von Registerkarten-Kopfzeilen, Dehnung der Kopfzeilen zur Anpassung an den Container
DoEasy. Steuerung (Teil 17): Beschneiden unsichtbarer Objektteile, Hilfspfeiltasten WinForms-Objekte
DoEasy. Steuerung (Teil 18): Funktionalität für scrollende Registerkarten in TabControl
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/11490





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.