English Русский 中文 Español 日本語 Português
Graphische Interfaces VIII: Der Kalender (Kapitel 1)

Graphische Interfaces VIII: Der Kalender (Kapitel 1)

MetaTrader 5Beispiele | 14 September 2016, 17:07
769 0
Anatoli Kazharski
Anatoli Kazharski

Inhalt


Einführung

Um ein besseres Verständnis des Zweckes dieser Bibliothek zu erhalten, lesen Sie bitte den ersten Artikel: Grafische Interfaces I: Vorbereitung der Bibliotheksstruktur (Kapitel 1). Eine Liste der Kapitel mit Verweisen befindet sich in jedem Teil am Ende der Artikel. Sie können auch die aktuellste Vollversion der Bibliothek von dort herunterladen. Die Dateien müssen in die gleichen Verzeichnissen kopiert werden, wie sie sich in dem Archiv befinden.

In diesem Kapitel konzentrieren wir uns auf komplexe, zusammengesetzte Steuerelemente:

  • Statische und Dropdown-Kalender
  • Baumdarstellung
  • Dateinavigator

In diesem Artikel betrachten wir Klassen, für die Erstellung von statischen und Dropdown-Kalender aber auch die Struktur (struct) von CDateTime aus der Standardbibliothek, um, für unser Ziel, mit Datum und Zeit arbeiten zu können.

 


Der Kalender

Der Kalender ist ein Ringsystem zur Zeitberechnung einer Tabellendarstellung. Graphische Schnittstellen müssen Elemente besitzen, über die der Nutzer leicht zum gewünschte Datum kommt. Abgesehen von nutzerdefinierten Methoden für die Interaktion mit einem Anwender kann die Kalenderklasse auch andere Steuerelemente aus der Bibliothek enthalten. Wir werden zum Beispiel Klassen zur Erstellung von (1) Kombinationsfelder, (2) Eingabefelder und (3) Knöpfe integrieren.

Listen wir alle Element des Kalenders einmal auf.

  1. Bereich
  2. Pfeiltasten um zum vorherigen und nächsten Monat zu wechseln
  3. Kombinationsfeld mit einer Liste der Monate
  4. Eingabefeld für das Jahr
  5. Eine Liste mit Abkürzungen der Wochentage
  6. Teilungslinie
  7. Zweidimensionale Tabelle mit den Tagen des Monats
  8. Taste zum Springen auf das nächste Datum

 

Fig. 1. Komponenten des Kalenders.


Wenn zum Beispiel für eine MQL Anwendung, die entwickelt wird, eine Zeitspanne benötigt, muss ein Anfangs- und ein Enddatum angegeben werden. Um einen Tag auszuwählen, genügt der Klick auf ein Datum. Es gibt ein paar Möglichkeiten ein Monat zu wählen: (1) die Taste für den vorherigen Monat, (2) die Taste für den nächsten Monat und (3) ein Kombinationsfeld mit der Liste aller Monate. Das Jahr wird bestimmt durch eine manuelle Eingabe oder über Schaltelemente. Um schnell zum aktuellen Datum zu gelangen, reicht es auf die Taste "Today: YYYY.MM.DD" unten am Rand zu klicken.

Schauen wir nun genauer, wie CDateTime aufgebaut ist, um mit Datum und Zeit zu arbeiten.

 


Beschreibung der Struktur von CDateTime

Die Datei DateTime.mqh mit der Struktur CDateTime liegt im Verzeichnis des MetaTrader Terminals:

  • MetaTrader 4: <data folder>\MQL4\Include\Tools 
  • MetaTrader 5: <data folder>\MQL5\Include\Tools

CDateTime ist abgeleitet (erweitert) aus dem Basissystem von Datum und Zeit MqlDateTime mit 8 Feldern (für Beispiele der Verwendung sehen Sie in die Dokumentation von MQL):

struct MqlDateTime
  {
   int year;           // Jahr
   int mon;            // Monat
   int day;            // Tag
   int hour;           // Stunde
   int min;            // Minuten
   int sec;            // Sekunden
   int day_of_week;    // Wochentag (0-Sonntag, 1-Montag, ... ,6-Samstag)
   int day_of_year;    // Zahl des Tages im Jahr (1. Januar ist 0)
  };

Eine kurze Beschreibung der Methoden von CDateTime ohne Code findet sich mit der lokalen Suche (F1) in der Sektion MQL5 Referenz/Standardbibliothek/Klassen für Control Panel und Dialoge/CDateTime. Bei der Arbeit mit Strukturen dürfen wir nicht vergessen, dass die Zählweise der Monate bei 1 beginnt, die der Wochen aber bei 0. 

Öffnen Sie DateTime.mqh und machen Sie sich mit dem Code vertraut.

 


Entwicklung der Klasse CCalendar

Wir erstellen die Datei Calendar.mqh und binden sie in die Datei WndContainer.mqh, so wir wir es mit allem machen, das wir jetzt noch entwickeln.

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

In der Datei Calendar.mqh erstellen wir die Klasse CCalendar mit den Standardmethoden für alle Kontrollen der Bibliotheken und mit den Verweisen auf die Dateien, die für die Entwicklung der Elemente notwendig sind:

//+------------------------------------------------------------------+
//|                                                     Calendar.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
#include "SpinEdit.mqh"
#include "ComboBox.mqh"
#include "IconButton.mqh"
#include <Tools\DateTime.mqh>
//+------------------------------------------------------------------+
//| Klasse des Kalenders                                             |
//+------------------------------------------------------------------+
class CCalendar : public CElement
  {
private:
   //--- Pointer auf das Formular, das dem Steuerelement zugeordnet ist.
   CWindow          *m_wnd;
   //---
public:
                     CCalendar(void);
                    ~CCalendar(void);
   //--- Sichere den Pointer auf das Formular
   void              WindowPointer(CWindow &object)             { m_wnd=::GetPointer(object);            }
   //---
public:
   //--- Handler auf das Chart-Ereignis
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Timer
   virtual void      OnEventTimer(void);
   //--- Bewegungskontrolle
   virtual void      Moving(const int x,const int y);
   //--- (1) Zeigen, (2) Verbergen, (3) Rücksetzen, (4) Löschen
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- (1) Einstellen, (2) Rücksetzen der Prioritäten der linken Maustaste
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
   //--- Rücksetzen der Farben
   virtual void      ResetColors(void) {}
  };

Genau wie bei den anderen Steuerelementen der Bibliotheksschnittstelle muss es eine Möglichkeit für die Darstellungsform des Kalenders geben. Unten sind die Eigenschaften die sich auf die Darstellung beziehen, die dem Nutzer zur Verfügung stehen.

  • Farbe des Bereiches
  • Farbe der Grenze des Bereiches
  • Farben der Elemente (Daten) in verschiedenen Zuständen
  • Farben von Elementgrenzen in verschiedenen Zuständen
  • Textfarbe in verschiedenen Zuständen
  • Farbe der Teilungslinie
  • Beschriftung der Tasten (aktiv/gesperrt), um zum vorherigen/folgenden Monat zu wechseln

Der Code unten zeigt die Namen der Felder und Methoden der Klasse CCalendar, die die Darstellung des Kalenders vor seinem Erstellen einstellen:

class CCalendar : public CElement
  {
private:
   //--- Farbe der Fläche
   color             m_area_color;
   //--- Farbe der Grenze des Bereiches
   color             m_area_border_color;
   //--- Farben der Elemente (Daten) in verschiedenen Zuständen
   color             m_item_back_color;
   color             m_item_back_color_off;
   color             m_item_back_color_hover;
   color             m_item_back_color_selected;
   //--- Farben von Elementgrenzen in verschiedenen Zuständen
   color             m_item_border_color;
   color             m_item_border_color_hover;
   color             m_item_border_color_selected;
   //--- Textfarbe in verschiedenen Zuständen
   color             m_item_text_color;
   color             m_item_text_color_off;
   color             m_item_text_color_hover;
   //--- Farbe der Teilungslinie
   color             m_sepline_color;
   //--- Beschriftung der Tasten (aktiv/gesperrt), um zum vorherigen/folgenden Monat zu wechseln
   string            m_left_arrow_file_on;
   string            m_left_arrow_file_off;
   string            m_right_arrow_file_on;
   string            m_right_arrow_file_off;
   //---
public:
   //--- Einstellen der Farbe für (1) Bereich, (2) Bereichsgrenze (3) und Teilungslinie
   void              AreaBackColor(const color clr)             { m_area_color=clr;                      }
   void              AreaBorderColor(const color clr)           { m_area_border_color=clr;               }
   void              SeparateLineColor(const color clr)         { m_sepline_color=clr;                   }
   //--- Farben der Elemente (Daten) in verschiedenen Zuständen
   void              ItemBackColor(const color clr)             { m_item_back_color=clr;                 }
   void              ItemBackColorOff(const color clr)          { m_item_back_color_off=clr;             }
   void              ItemBackColorHover(const color clr)        { m_item_back_color_hover=clr;           }
   void              ItemBackColorSelected(const color clr)     { m_item_back_color_selected=clr;        }
   //--- Farben von Elementgrenzen in verschiedenen Zuständen
   void              ItemBorderColor(const color clr)           { m_item_border_color=clr;               }
   void              ItemBorderColorHover(const color clr)      { m_item_border_color_hover=clr;         }
   void              ItemBorderColorSelected(const color clr)   { m_item_border_color_selected=clr;      }
   //--- Textfarbe in verschiedenen Zuständen
   void              ItemTextColor(const color clr)             { m_item_text_color=clr;                 }
   void              ItemTextColorOff(const color clr)          { m_item_text_color_off=clr;             }
   void              ItemTextColorHover(const color clr)        { m_item_text_color_hover=clr;           }
   //--- Beschriftung der Tasten (aktiv/gesperrt), um zum vorherigen/folgenden Monat zu wechseln
   void              LeftArrowFileOn(const string file_path)    { m_left_arrow_file_on=file_path;        }
   void              LeftArrowFileOff(const string file_path)   { m_left_arrow_file_off=file_path;       }
   void              RightArrowFileOn(const string file_path)   { m_right_arrow_file_on=file_path;       }
   void              RightArrowFileOff(const string file_path)  { m_right_arrow_file_off=file_path;      }
  };

Wir benötigen neun "private" Methoden und eine "public" Methode, um den Kalender zu erstellen. Statische Arrays vom Objekttyp CEdit werden für die Darstellung der Wochentage und der Daten gebraucht. 

Die Tabelle der Daten besteht aus 42 Elementen. Das reicht für die maximale Tagesanzahl im Monat, das sind 31, inklusive einer Berücksichtigung einer Verschiebung des ersten Tages, falls dieser auf einen Sonntag fällt (Sonntag ist der siebte Wochentag in dieser Version).

class CCalendar : public CElement
  {
private:
   //--- Objekte und Kontrollen des Kalenders
   CRectLabel        m_area;
   CBmpLabel         m_month_dec;
   CBmpLabel         m_month_inc;
   CComboBox         m_months;
   CSpinEdit         m_years;
   CEdit             m_days_week[7];
   CRectLabel        m_sep_line;
   CEdit             m_days[42];
   CIconButton       m_button_today;
   //---
public:
   //--- Methoden der Erstellung des Kalenders
   bool              CreateCalendar(const long chart_id,const int subwin,const int x,const int y);
   //---
private:
   bool              CreateArea(void);
   bool              CreateMonthLeftArrow(void);
   bool              CreateMonthRightArrow(void);
   bool              CreateMonthsList(void);
   bool              CreateYearsSpinEdit(void);
   bool              CreateDaysWeek(void);
   bool              CreateSeparateLine(void);
   bool              CreateDaysMonth(void);
   bool              CreateButtonToday(void);
  };

Falls der Kalender Teil anderer Steuerelemente ist, kann es erforderlich sein, auf die Komponenten des Kalenders zugreifen zu können. Aus diesem Grund ergänzen wir Methoden, die Pointer auf die unten gelistete Steuerelemente liefern.

  • Kombinationsfeld (CComboBox)
  • Liste der Kombinationsfelder (CListView)
  • Liste der vertikalen Bildlaufleiste (CScrollV)
  • Eingabefeld (CSpinEdit)
  • Taste (CIconButton)
class CCalendar : public CElement
  {
public:
   //--- (1) Abfrage des Pointer auf das Kombinationsfeld 
   //    (2) Abfrage des Pointers auf die Liste, (3) Abfrage des Pointers auf Bildlaufleiste, 
   //    (4) Abfrage des Pointers auf das Eingabefeld, (5) Abfrage des Pointers auf die Taste
   CComboBox        *GetComboBoxPointer(void)             const { return(::GetPointer(m_months));        }
   CListView        *GetListViewPointer(void)                   { return(m_months.GetListViewPointer()); }
   CScrollV         *GetScrollVPointer(void)                    { return(m_months.GetScrollVPointer());  }
   CSpinEdit        *GetSpinEditPointer(void)             const { return(::GetPointer(m_years));         }
   CIconButton      *GetIconButtonPointer(void)           const { return(::GetPointer(m_button_today));  }
  };

Wir benötigen drei Muster der Struktur CDateTime für die Arbeit mit Datum und Zeit.

  • Zur Interaktion mit den Nutzern. Ein Datum, das von Nutzer selbst gewählt wurde (aus dem Kalender).
  • Aktuelles Systemdatum des PCs des Nutzers. Dieses Datum wird im Kalender immer hervorgehoben.
  • Muster für Berechnung und Prüfung. Es wird als Zähler in vielen Methoden der Klasse CCalendar verwendet.
class CCalendar : public CElement
  {
private:
   //--- Beispiel der Struktur für die Arbeit mit Datum und Zeit:
   CDateTime         m_date;      // nutzergewähltes Datum
   CDateTime         m_today;     // aktuelles Systemdatum des Nutzer-PCs
   CDateTime         m_temp_date; // Beispiel für die Berechnung und Prüfung
  };

Die erste Initialisierung der Zeitstrukturen wird vom Konstruktor der Klasse CCalendar vorgenommen. Wir verwenden in unserem Beispiel die Lokalzeit des PCs (gelb markiert im Code unten), um m_date and m_today zu setzen.

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCalendar::CCalendar(void) : m_area_color(clrWhite),
                             m_area_border_color(clrSilver),
                             m_sepline_color(clrBlack),
                             m_item_back_color(clrWhite),
                             m_item_back_color_off(clrWhite),
                             m_item_back_color_hover(C'235,245,255'),
                             m_item_back_color_selected(C'193,218,255'),
                             m_item_border_color(clrWhite),
                             m_item_border_color_hover(C'160,220,255'),
                             m_item_border_color_selected(C'85,170,255'),
                             m_item_text_color(clrBlack),
                             m_item_text_color_off(C'200,200,200'),
                             m_item_text_color_hover(C'0,102,204'),
                             m_left_arrow_file_on(""),
                             m_left_arrow_file_off(""),
                             m_right_arrow_file_on(""),
                             m_right_arrow_file_off("")
  {
//--- Sichere den Namen der Kontrollklasse in der Basisklasse
   CElement::ClassName(CLASS_NAME);
//--- Setze die Priorität eines Klicks der linken Maustaste
   m_zorder        =0;
   m_area_zorder   =1;
   m_button_zorder =2;
//--- Initialisierung der Zeitstrukturen
   m_date.DateTime(::TimeLocal());
   m_today.DateTime(::TimeLocal());
  }

Nach der Einstellung des Kalenders müssen noch der aktuelle Monat und das Jahr in die Tabelle (Daten) eingetragen werden. Dafür werden vier Methoden benötigt.

1. Die Methode CCalendar::OffsetFirstDayOfMonth() berechnet die Differenz (in Tagen) zwischen dem ersten Element und dem ersten des aktuellen Monats. Am Anfang der Methode erstellen wir das Datum als Zeichenkette aus aktuellem Jahr, Monat und dem Ersten dieses Monats. Dann konvertieren wir diese Zeichenfolge in das Format datetime und sichern sie für die weitere Berechnung. Wenn 1 von der aktuellen Zahl des Wochentages abgezogen wird und das Ergebnis größer oder gleich Null ist, können wir die Zahl zurückgeben, ist sie aber kleiner als Null (-1) retournieren wir 6. Am Ende der Methode muss noch eine Rückverschiebung um die resultierende Differenz durchgeführt werden (Anzahl der Tage).
class CCalendar : public CElement
  {
private:
   //--- Berechnung der Differenz zwischen dem ersten Element der Tabelle des Kalenders und dem ersten des aktuellen Monats
   int               OffsetFirstDayOfMonth(void);
  };
//+------------------------------------------------------------------+
//| Ermittele die Differenz zwischen ersten Element der Tabelle      |
//| und dem ersten Tag des aktuellen Monats                          |
//+------------------------------------------------------------------+
int CCalendar::OffsetFirstDayOfMonth(void)
  {
//--- Der erste Tage des gewählten Jahres und Monats als Zeichenkette
   string date=string(m_date.year)+"."+string(m_date.mon)+"."+string(1);
//--- Sicherung des Datums für die weitere Berechnung
   m_temp_date.DateTime(::StringToTime(date));
//--- Wenn 1 von der aktuellen Zahl des Wochentages abgezogen wird und das Ergebnis größer oder gleich Null ist,
//    können wir die Zahl zurückgeben, ist sie aber kleiner als Null, retournieren wir 6,
   int diff=(m_temp_date.day_of_week-1>=0) ? m_temp_date.day_of_week-1 : 6;
//--- Store date that is in the first item of the table
   m_temp_date.DayDec(diff);
   return(diff);
  }

2. Die Methode CCalendar::SetCalendar(). Diese Methode lässt uns die Elemente eines gewählten Jahres und Monats eintragen. Die Methode CCalendar::OffsetFirstDayOfMonth() wird zu Beginn aufgerufen. Dann iterieren wir mit einer Schleife über alle Elemente der Tabelle und setzten die Datumszahl mittels der Struktur (m_temp_date.day) für jedes Element, wobei die Methode CCalendar::OffsetFirstDayOfMonth() dafür verwendet wird. Ein Wechsel auf den nächsten Tag des Kalenders wird am Ende der Methode durchgeführt.

Class CCalendar : public CElement
  {
private:
   //--- Zeige die letzten Änderungen in der Tabelle des Kalenders
   void              SetCalendar(void);
  };
//+------------------------------------------------------------------+
//| Eintragung der Kalenderwerte                                     |
//+------------------------------------------------------------------+
void CCalendar::SetCalendar(void)
  {
//--- Berechnung der Differenz zwischen dem ersten Element der Tabelle des Kalenders und dem ersten des aktuellen Monats
   int diff=OffsetFirstDayOfMonth();
//--- Iteration über alle Elemente der Kalendertabelle in der Schleife
   for(int i=0; i<42; i++)
     {
      //--- Eintragen der Tage in die aktuelle Tabelle der Element
      m_days[i].Description(string(m_temp_date.day));
      //--- Wechsel zum nächsten Datum
      m_temp_date.DayInc();
     }
  }

3. Die Methode CCalendar::HighlightDate() markiert das aktuelle Datum (Systemdatum des Nutzer PCs) und das vom Nutzer gewählte Datum in der Tabelle des Kalenders. Das Datum des ersten Elements der Tabelle wird hier als erstes in der Struktur (m_temp_date) berechnet. Dann werden die Farben für (1) Bereich, (2) Bereichsgrenzen und (3) gezeigtem Text in der Schleife unten gesetzt. 

Class CCalendar : public CElement
  {
private:
   //--- Hervorheben des aktuellen und des nutzergewählten Datums
   void              HighlightDate(void);
  };
//+------------------------------------------------------------------+
//| Hervorheben des aktuellen und des nutzergewählten Datums         |
//+------------------------------------------------------------------+
void CCalendar::HighlightDate(void)
  {
//--- Berechnung der Differenz zwischen dem ersten Element der Tabelle des Kalenders und dem ersten des aktuellen Monats
   OffsetFirstDayOfMonth();
//--- Iteration über alle Elemente der Tabelle
   for(int i=0; i<42; i++)
     {
      //--- Wenn eine Monat gleich dem aktuellen und  
      //    und der Tag mit dem des gewählten Tages
      if(m_temp_date.mon==m_date.mon &&
         m_temp_date.day==m_date.day)
        {
         m_days[i].Color(m_item_text_color);
         m_days[i].BackColor(m_item_back_color_selected);
         m_days[i].BorderColor(m_item_border_color_selected);
         //--- Wechsel zum nächsten Element der Tabelle
         m_temp_date.DayInc();
         continue;
        }
      //--- Wenn es das aktuelle Datum (heute) ist
      if(m_temp_date.year==m_today.year && 
         m_temp_date.mon==m_today.mon &&
         m_temp_date.day==m_today.day)
        {
         m_days[i].BackColor(m_item_back_color);
         m_days[i].BorderColor(m_item_text_color_hover);
         m_days[i].Color(m_item_text_color_hover);
         //--- Wechsel zum nächsten Element der Tabelle
         m_temp_date.DayInc();
         continue;
        }
      //---
      m_days[i].BackColor(m_item_back_color);
      m_days[i].BorderColor(m_item_border_color);
      m_days[i].Color((m_temp_date.mon==m_date.mon)? m_item_text_color : m_item_text_color_off);
      //--- Wechsel zum nächsten Element der Tabelle
      m_temp_date.DayInc();
     }
  }

4. Die Methode CCalendar::UpdateCalendar() wird von allen Methoden verwendet, die bei Nutzeraktionen im Kalender aufgerufen werden. Die oben erwähnten Methoden CCalendar::SetCalendar() und CCalendar::HighlightDate() werden hier aufgerufen. Danach werden das Jahr und der Monat im Eingabefeld des Kalenders und im Kombinationsfeld eingetragen. Bitte beachten Sie, dass um ein angefordertes Element aus der Liste der Kombinationsfelder vom Monat 1 abgezogen werden muss, weil die Zählweise der Monate mit 1 beginnt, die Aufzählung der Elemente in der Liste (CListView) aber mit 0. 

Class CCalendar : public CElement
  {
public:
   //--- Anzeige der letzten Änderungen im Kalender
   void              UpdateCalendar(void);
  };
//+------------------------------------------------------------------+
//| Anzeige der letzten Änderungen im Kalender                       
//+------------------------------------------------------------------+
void CCalendar::UpdateCalendar(void)
  {
//--- Anzeige der Änderungen in der Kalendertabelle
   SetCalendar();
//--- Hervorheben des aktuellen und des nutzergewählten Datums
   HighlightDate();
//--- Eintragen des Jahres in das Eingabefeld
   m_years.ChangeValue(m_date.year);
//--- Eintrag des Monats in die Liste der Kombinationsfelder
   m_months.SelectedItemByIndex(m_date.mon-1);
  }

Das Ändern der Farben der Elemente in der Tabelle des Kalenders, wenn die Maus darüber schwebt, wird durch CCalendar::ChangeObjectsColor() erreicht. Die Koordinaten x, y müssen hier übergeben werden. Vor der Schleife über alle Elemente müssen wir bestimmen, um wie viel Elemente der erste Tag des Monats vom ersten Element der Tabelle verschoben werden muss, damit wir den Wert des ersten Zählers (die Struktur m_temp_date) setzen können. Dann werden sowohl der gewählte Tag und das aktuelle Datum (Systemdatum des Nutzer-PCs) ausgelassen und der Rest wird zur Prüfung der Maus verwendet. Beim Ändern einer Farbe wird ein Monat, zu dem der Tag gehört, berücksichtigt.

Class CCalendar : public CElement
  {
public:
   //--- Ändere das Objekt Farbe in der Tabelle des Kalenders 
   void              ChangeObjectsColor(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| Ändere das Objekt Farbe in der Tabelle des Kalenders             |
//| wenn die Maus darüber schwebt                                    |
//+------------------------------------------------------------------+
void CCalendar::ChangeObjectsColor(const int x,const int y)
  {
//--- Berechnung der Differenz zwischen dem ersten Element der Tabelle des Kalenders und dem ersten des aktuellen Monats
   OffsetFirstDayOfMonth();
//--- Iteration über alle Elemente der Tabelle
   int items_total=::ArraySize(m_days);
   for(int i=0; i<items_total; i++)
     {
      //--- Wenn eine Monat gleich dem aktuellen und  
      //    und der Tag mit dem des gewählten Tages
      if(m_temp_date.mon==m_date.mon &&
         m_temp_date.day==m_date.day)
        {
         //--- Wechsel zum nächsten Element der Tabelle
         m_temp_date.DayInc();
         continue;
        }
      //--- Wenn Jahr/Monat/Tag eines Elementes mit Jahr/Monat/Tag des aktuellen Datums (heute) übereinstimmt
      if(m_temp_date.year==m_today.year && 
         m_temp_date.mon==m_today.mon &&
         m_temp_date.day==m_today.day)
        {
         //--- Wechsel zum nächsten Element der Tabelle
         m_temp_date.DayInc();
         continue;
        }
      //--- Wenn die Maus über dem Element schwebt
      if(x>m_days[i].X() && x<m_days[i].X2() &&
         y>m_days[i].Y() && y<m_days[i].Y2())
        {
         m_days[i].BackColor(m_item_back_color_hover);
         m_days[i].BorderColor(m_item_border_color_hover);
         m_days[i].Color((m_temp_date.mon==m_date.mon)? m_item_text_color_hover : m_item_text_color_off);
        }
      else
        {
         m_days[i].BackColor(m_item_back_color);
         m_days[i].BorderColor(m_item_border_color);
         m_days[i].Color((m_temp_date.mon==m_date.mon)? m_item_text_color : m_item_text_color_off);
        }
      //--- Wechsel zum nächsten Element der Tabelle
      m_temp_date.DayInc();
     }
  }

Wir benötigen noch die Möglichkeit ein Datum im Kalender mittels des Programms zu wählen und die Möglichkeiten ein vom Nutzer gewähltes Datum und den aktuellen Tag (heute) zu übernehmen. Daher müssen wir die "public" Methoden CCalendar::SelectedDate() und CCalendar::Today() ergänzen.

Class CCalendar : public CElement
  {
public:
   //--- (1) Setzen (wählen) und (2) ein gewähltes Datum abfragen, (3) das aktuelle Datum im Kalender abfragen
   void              SelectedDate(const datetime date);
   datetime          SelectedDate(void)                         { return(m_date.DateTime());             }
   datetime          Today(void)                                { return(m_today.DateTime());            }
  };
//+------------------------------------------------------------------+
//| Auswahl eines neuen Datums                                       |
//+------------------------------------------------------------------+
void CCalendar::SelectedDate(const datetime date)
  {
//--- Sichern des Datums in Struktur und Feld der Klasse
   m_date.DateTime(date);
//--- Anzeige der letzten Änderungen im Kalender
   UpdateCalendar();
  }

Zum Beispiel wurde im Kalender der 29. Februar 2016 (2016.02.29) ausgewählt und der Nutzer hat im Eingabefeld (oder über den Inkrementalzähler) 2017 stehen. Es gibt aber nur 28 Tage im Februar 2017. Welcher Tag sollte jetzt in diesem Fall in der Kalender-Tabelle ausgewählt werden? Kalender mit einer anderen Umsetzung nehmen den nächstgelegenen Tag, das wäre der letzte Tag des Monats. In unserem Fall wäre das der 28. Februar 2017. Wir werden es auch so machen. Deshalb ergänzen wir die Klasse noch mit der Methode CCalendar::CorrectingSelectedDay() für ähnliche Korrekturen. Der Code dieser Methode beinhaltet mehrere Zeichenketten. Die Struktur CDateTime verfügt bereits über eine Methode, die die Zahl der Tage eines Monats zurückgibt, unter Beachtung der Schaltjahre — CDateTime::DaysInMonth(). Daher reicht es, den aktuellen Tag des Monats mit der Anzahl der Tage des aktuellen Monats zu vergleichen und, wenn der gewählte Tag die Anzahl der Tage des Monats übersteigt, sollte er ersetzt werden.

Class CCalendar : public CElement
  {
private:
   //--- Korrektur des gewählten Tages durch den Letzten des Monats
   void              CorrectingSelectedDay(void);
  };
//+------------------------------------------------------------------+
//| Bestimme den ersten Tag des Monats                               |
//+------------------------------------------------------------------+
void CCalendar::CorrectingSelectedDay(void)
  {
//--- Korrigiere die Tageszahl des Monats, wenn sie zu groß ist
   if(m_date.day>m_date.DaysInMonth())
      m_date.day=m_date.DaysInMonth();
  }

 

 


Zugriff auf Kalenderereignisse

Schauen wir auf die Organisation der Ereignisse des Kalenders. Es gibt insgesamt 8 "private" Methoden, die die unten aufgelisteten Ereignisse handhaben.

  • Klick auf die Taste, um zu einem vorherigen Monat zu wechseln
  • Klick auf die Taste, um zum nächsten Monat zu wechseln
  • Auswahl eines Monats aus der Dropdown-Liste der Kombinationsfelder
  • Eingetragener Wert im Eingabefeld des Jahres
  • Klick auf die Taste, um zum nächsten Jahr zu wechseln
  • Klick auf die Taste, um zu einem vorherigen Jahr zu wechseln
  • Klick auf einen Tag des Monats
  • Klick auf die Taste, um zu einem aktuellen Datum zu wechseln
  • Jede Aktion aus der obigen Liste führt zu Veränderungen im Kalender. Aber bleiben wir beim Code dieser Methoden methodisch.

Für das Funktionieren der Kalender benötigen wir eine neue Kennung für das Ereignis des Nutzers ON_CHANGE_DATE, das für die Änderungsnachricht, der Nutzer hat das Datum geändert, verwendet wird. Darüber hinaus ergänzen wir weitere Kennung für die Arbeit mit einem Kombinationsfeld — ON_CLICK_COMBOBOX_BUTTON für einen Klick auf dessen Taste. 

#define ON_CLICK_COMBOBOX_BUTTON  (21) // Klick auf die Taste des Kombinationsfeld
#define ON_CHANGE_DATE            (22) // Ändern des Datums im Kalender

Ein Nachricht mit der Kennung ON_CLICK_COMBOBOX_BUTTON wird durch die Methode OnClickButton() aus der Klasse CComboBox ausgeführt. Ergänzen wir noch die Möglichkeit, eine Nachricht an diese Methode zu senden (siehe den Code unten):

//+------------------------------------------------------------------+
//| Klick auf die Taste des Kombinationsfeldes                       |
//+------------------------------------------------------------------+
bool CComboBox::OnClickButton(const string clicked_object)
  {
//--- Beenden, wenn das Element blockiert ist und die Kennungen nicht übereinstimmen
   if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id())
      return(false);
//--- Beenden, bei einem anderen Objektnamen  
   if(clicked_object!=m_button.Name())
      return(false);
//--- Ändern des Status' einer Liste
   ChangeComboBoxListState();
//--- Senden einer Nachricht darüber
   ::EventChartCustom(m_chart_id,ON_CLICK_COMBOBOX_BUTTON,CElement::Id(),0,"");
   return(true);
  }

Das Verfolgen von Nutzerereignissen mittels der Kennung ON_CLICK_COMBOBOX_BUTTON wird durch die Klasse CCalendar verlangt, um den Zustand der Steuerelemente (CSpinEdit und CIconButton) zu verwalten, die Teil des Kalenders sind (siehe Code unten): 

//+------------------------------------------------------------------+
//| Ereignishandhabung                                               |
//+------------------------------------------------------------------+
void CCalendar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Klick auf die Taste des Kombinationsfeldes
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_BUTTON)
     {
      //--- Beenden, wenn die Kennungen der Elemente nicht übereinstimmen
      if(lparam!=CElement::Id())
         return;
      //--- Aktivieren oder blockieren der Steuerelemente abhängig von der Sichtbarkeit der Liste
      m_years.SpinEditState(!m_months.GetListViewPointer().IsVisible());
      m_button_today.ButtonState(!m_months.GetListViewPointer().IsVisible());
     }
  }

In den grafischen Schnittstellen verschiedener Systeme, zum Beispiel im Betriebssystem Windows 7, führt eine Änderung auf den vorherigen/nächsten Monat durch einen Klick auf die rechten/linken Pfeil-Taste zur Auswahl des ersten Tag des Monats, egal welcher Tag zuvor vom Nutzer in der Tabelle des Kalenders ausgewählt war. Wir setzen das gleiche Verhalten des Kalenders in der zu entwickelnden Bibliothek um. Um zum vorherigen/nächsten Monat zu wechseln, verwenden wir die Methoden CCalendar::OnClickMonthDec() und CCalendar::OnClickMonthInc(). Der Code dieser Methoden ist sehr ähnlich. Als Beispiel zeigen wir die Beschreibung einer von denen, d.h. die Handhabung eines Klicks auf der Taste, um zum vorherigen Monat zu wechseln.

Die Methode prüft als erstes den Namen des graphischen Objektes, auf das der Nutzer geklickt hat. Dann, wenn das Jahr das kleinste Angegebene und der aktuelle Monat "Januar" ist (d.h. es ist das Mindestdatum), wird der Text im Eingabefeld hervorgehoben und die Methode verlassen. 

Wurde das Mindestdatum nicht erreicht, passiert folgendes.

  • Setzen des Zustandes der Taste auf On.
  • Wechseln zum nächsten Monat. 
  • Setzen des Erstens des Monats.
  • Rücksetzen der Zeit auf (00:00:00). Zu diesem Zweck wurde die Methode CCalendar::ResetTime() zur Klasse hinzu gefügt.
  • Aktualisiere den Kalender, damit er die Änderungen zeigt.
  • Wir senden eine Nachricht mit (1) Chart-Kennung, (2) ON_CHANGE_DATE Ereignis-Kennung, (3) Elements-Kennung und (4) das spezifizierte Datum. Die Nachricht mit den gleichen Parametern wird zu und von anderen Methoden der Kalenderereignisse gesendet werden. 
Class CCalendar : public CElement
  {
private:
   //--- Erledige das Klicken Sie auf die Taste, um zu einem vorherigen Monat zu wechseln
   bool              OnClickMonthDec(const string clicked_object);
   //--- Erledige das Klicken Sie auf die Taste, um zu einem nächsten Monat zu wechseln
   bool              OnClickMonthInc(const string clicked_object);
   //--- Rücksetzen der Zeit
   void              ResetTime(void);
  };
//+------------------------------------------------------------------+
//| Klick auf den linken Pfeil. Wechsel zum Vormonat.                |
//+------------------------------------------------------------------+
bool CCalendar::OnClickMonthDec(const string clicked_object)
  {
//--- Beenden, bei einem anderen Objektnamen
   if(::StringFind(clicked_object,m_month_dec.Name(),0)<0)
      return(false);
//--- Ist das Jahr das kleinst mögliche und
//    der Monat "Januar"
   if(m_date.year==m_years.MinValue() && m_date.mon==1)
     {
      //--- Wert markieren und verlassen
      m_years.HighlightLimit();
      return(true);
     }
//--- Setze den Status auf "On"
   m_month_dec.State(true);
//--- Gehe zum vorherigen Monat
   m_date.MonDec();
//--- Setze den ersten Tag des Monats
   m_date.day=1;
//--- Rücksetzen der Zeit
   ResetTime();
//--- Anzeige der letzten Änderungen im Kalender
   UpdateCalendar();
//--- Senden einer Nachricht darüber
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }
//+------------------------------------------------------------------+
//| Rücksetzen der Zeit                                              |
//+------------------------------------------------------------------+
void CCalendar::ResetTime(void)
  {
   m_date.hour =0;
   m_date.min  =0;
   m_date.sec  =0;
  }

Der Code der Methode CCalendar::OnClickMonthInc() ist fast der gleiche. Der einzige Unterschied ist, dass überprüft wird, ob das in den Eigenschaften des Kalenders existente Maximaldatum (Jahr, Dezember) überschritten wird.

Die Auswahl des Monats aus der Liste erledigt die Methode CCalendar::OnClickMonthList() nach Erreichen des Ereignisses mit der Kennung ON_CLICK_COMBOBOX_ITEM (siehe den Code unten). Der Hauptmethode wird der "long" Parameter übergeben, der die Kennung des Steuerelementes darstellt. Wenn die Kennung nicht übereinstimmt, beendet sich die Methode. Das die Auswahl des Elementes aus der Liste zu ihrem Ende führt, müssen wir die vorherige Blockierung der Steuerelemente des Kalenders aufheben (Eingabefeld und Taste). Dann wird der gewählte Monat in die Struktur von Datum und Zeit gesetzt und, falls nötig, wird noch der gewählte Tag, abhängig vom Monat, angepasst. Es bleibt noch, die Zeit einstellen, die letzten Änderungen des Kalenders anzeigen und eine Nachricht an alle Ereignisse senden, dass sich das Datum im Kalender geändert hat.

Class CCalendar : public CElement
  {
private:
   //--- Bearbeite die Auswahl des Monats aus der Liste
   bool              OnClickMonthList(const long id);
  };
//+------------------------------------------------------------------+
//| Bearbeite die Auswahl des Monats aus der Liste                   |
//+------------------------------------------------------------------+
bool CCalendar::OnClickMonthList(const long id)
  {
//--- Beenden, wenn die Kennungen der Elemente nicht übereinstimmen
   if(id!=CElement::Id())
      return(false);
//--- Entsperren der Steuerelemente
   m_years.SpinEditState(true);
   m_button_today.ButtonState(true);
//--- Erhalt des gewählten Monats aus der Liste
   int month=m_months.GetListViewPointer().SelectedItemIndex()+1;
   m_date.Mon(month);
//--- Korrektur des gewählten Tages durch den Letzten des Monats
   CorrectingSelectedDay();
//--- Rücksetzen der Zeit
   ResetTime();
//--- Anzeige der Änderungen in der Kalendertabelle
   UpdateCalendar();
//--- Senden einer Nachricht darüber
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }

Die Methode CCalendar::OnEndEnterYear() verarbeitet den Eintrag in das Eingabefeld für das Jahr. Am Anfang dieser Methode wird dort, wo etwas eingetragen wurde, der Objektname vom Typ OBJ_EDIT überprüft. Dann folgt eine weitere Prüfung, ob der Wert geändert wurde. Falls der Name des Eingabefeldes nicht zu diesem Kalender gehört, oder der Wert sich nicht geändert hat, beendet sich diese Methode. Waren die beiden erste Prüfungen gültig, würde als nächstes der Eintrag korrigiert, falls die Beschränkungen verletzt wurden. Die nächste Änderung beträfe das gewählte Datum, falls die Tageszahl des Monats die Maximalzahl des Monats überschreitet. Danach können wir das Datum in der Struktur von Datum und Tag sichern und die Kalenderanzeige aktualisieren und eine Nachricht senden, dass das Datum im Kalender geändert wurde. Der Code unten der Methode CCalendar::OnEndEnterYear() kann gründlich studiert werden: 

Class CCalendar : public CElement
  {
private:
   //--- Bearbeite den eingetragenen Wert im Eingabefeld des Jahres
   bool              OnEndEnterYear(const string edited_object);
  };
//+------------------------------------------------------------------+
//| Bearbeite den eingetragenen Wert im Eingabefeld des Jahres       |
//+------------------------------------------------------------------+
bool CCalendar::OnEndEnterYear(const string edited_object)
  {
//--- Beenden, bei einem anderen Objektnamen
   if(::StringFind(edited_object,m_years.Object(2).Name(),0)<0)
      return(false);
//--- Beenden ohne Änderung des Wertes
   string value=m_years.Object(2).Description();
   if(m_date.year==(int)value)
      return(false);
//--- Gültiger Wert nach Prüfung dieser Einschränkungen
   if((int)value<m_years.MinValue())
     {
      value=(string)int(m_years.MinValue());
      //--- Wert markieren
      m_years.HighlightLimit();
     }
   if((int)value>m_years.MaxValue())
     {
      value=(string)int(m_years.MaxValue());
      //--- Wert markieren
      m_years.HighlightLimit();
     }
//--- Bestimme die Anzahl der Tage im aktuellen Monat
   string year  =value;
   string month =string(m_date.mon);
   string day   =string(1);
   m_temp_date.DateTime(::StringToTime(year+"."+month+"."+day));
//--- Falls der Wert des gewählten Tages die Anzahl der Tage des Monats überschreitet,
//    ersetze sie mit der Zahl des Monatsletzten
   if(m_date.day>m_temp_date.DaysInMonth())
      m_date.day=m_temp_date.DaysInMonth();
//--- Sichere das Datum in der Struktur
   m_date.DateTime(::StringToTime(year+"."+month+"."+string(m_date.day)));
//--- Anzeige der Änderungen in der Tabelle des Kalenders
   UpdateCalendar();
//--- Senden einer Nachricht darüber
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }

Die Methoden zur Bearbeitung der Klicks auf den Tasten für den Wechsel des Jahres sind CCalendar::OnClickYearInc() und CCalendar::OnClickYearDec(). Es wird hier nur der Code von nur einer der beiden gezeigt, da sie praktisch identisch sind, abgesehen von der Überprüfung der Beschränkungen. Am Anfang wird der aktuelle Status der Liste für die Auswahl eines Monats überprüft. Wenn er geöffnet ist, schließen wir ihn Danach, falls der Kennung nicht passt, beendet sich die Methode. Jetzt folgt die Hauptbedingung dieser Methode, in der entweder das nächste Jahr durch CCalendar::OnClickYearInc() oder das vorherige durch CCalendar::OnClickYearDec() gewählt wird. Dann, falls nötig, wird (1) der gewählte Tag auf Basis der Anzahl der Tage des Monats korrigiert, (2) die letzten Änderungen im Kalender angezeigt und (3) eine Nachricht gesendet, dass das Datum im Kalender geändert wurde.  

Class CCalendar : public CElement
  {
private:
   //--- Erledige das Klicken Sie auf die Taste, um zu einem nächsten Jahr zu wechseln
   bool              OnClickYearInc(const long id);
   //--- Klick auf die Taste, um zum vorherigen Jahr zu wechseln
   bool              OnClickYearDec(const long id);
  };
//+------------------------------------------------------------------+
//| Klick auf die Taste, um zu einem nächsten Jahr zu wechseln       |
//+------------------------------------------------------------------+
bool CCalendar::OnClickYearInc(const long id)
  {
//--- Ist die Liste geöffnet, wird sie geschlossen
   if(m_months.GetListViewPointer().IsVisible())
      m_months.ChangeComboBoxListState();
//--- Beenden, wenn die Kennungen der Elemente nicht übereinstimmen
   if(id!=CElement::Id())
      return(false);
//--- Ist das Jahr kleiner als das mögliche Maximum, erhöhen wir den Wert um 1
   if(m_date.year<m_years.MaxValue())
      m_date.YearInc();
//--- Korrektur des gewählten Tages durch den Letzten des Monats
   CorrectingSelectedDay();
//--- Anzeige der Änderungen in der Kalendertabelle
   UpdateCalendar();
//--- Senden einer Nachricht darüber
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }

Kommen wir jetzt zur Methode CCalendar::OnClickDayOfMonth(), die das Klicken auf den Tag des Monats in der Tabelle des Kalenders abarbeitet. 

Wie bereits erwähnt, besteht die Kalendertabelle aus 42 Elementen. Daher, abhängig von der Position der ersten Zahl des aktuellen Monats, passiert es manchmal, dass es Tage des vorherigen und des nächsten Monats in der Tabelle gibt. Falls jetzt ein Nutzer auf einen Tag klickt, der nicht zu den aktuellen Monat gehört, wird zum Monat des gewählten Tages gewechselt. 

Am Anfang der Methode überprüfen wir, ob das Objekt ein Element der Tabelle des Kalenders ist und vergleichen die Kennung. Eine Kennung wird aus dem Objekt gelesen mittels der Methode CCalendar::IdFromObjectName(), die früher bereits besprochen wurde. Wenn diese beiden Prüfungen bestanden wurden, verwenden wir die Methode CCalendar::OffsetFirstDayOfMonth(), um die Differenz des ersten Elementes der Tabelle zum Ersten des aktuellen Monats zu ermitteln. Nach dem Aufruf dieser Methode ist der Zähler m_temp_date bereit für die Arbeit, und wird später im Programm über alle Elemente iterieren, um das Element zu finden, das der Nutzer angeklickt hat. 

Versuchen wir die Logik hinter dieser Schleife zu verstehen. Es ist möglich, dass das Datum auf das Jahr vor dem Mindestjahr des Systems (Jahr 1970) verweist. Wenn dies der Fall ist, markieren wir den Wert im Eingabefeld des Jahres und gehen sofort zum nächsten Element, denn der Nutzer darf diese Einschränkungen nicht umgehen. Liegt ein Element auf der verfügbaren Fläche und wurde geklickt, dann (1) wird das Datum gesichert in der Struktur (m_date), (2) zeigt der Kalender die letzten Änderungen, (3) wird die Schleife unterbrochen und (4) wird ein Nachricht, das das Datum im Kalender geändert wurde, am Ende der Methode gesendet.

Wenn keine der ersten beiden Bedingungen in der Schleife erfüllt werden, dann wird der Struktur-Zähler der Berechnung (m_temp_date) um einen Tag erhöht, gefolgt von der Überprüfung des Abbruchs der Methode wegen der Überschreitung des Maximaldatums im Kalendersystem. Wurde das Maximum nicht überschritten, folgt das nächste Element. Wurde aber das Maximum erreicht, wird der Wert im Eingabefeld des Jahres markiert und die Methode verlassen. 

Class CCalendar : public CElement
  {
private:
   //--- Klick Sie auf einen Tag des Monats
   bool              OnClickDayOfMonth(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Klick auf einen Tag des Monats                                   |
//+------------------------------------------------------------------+
bool CCalendar::OnClickDayOfMonth(const string clicked_object)
  {
//--- Beenden, wenn der Klick nicht auf einem Tag des Kalenders passierte
   if(::StringFind(clicked_object,CElement::ProgramName()+"_calendar_day_",0)<0)
      return(false);
//--- Abfrage der Kennung und des Index vom Objektnamen
   int id=IdFromObjectName(clicked_object);
//--- Verlassen, wenn die Kennung nicht passt
   if(id!=CElement::Id())
      return(false);
//--- Berechnung der Differenz zwischen dem ersten Element der Tabelle des Kalenders und dem ersten des aktuellen Monats
   OffsetFirstDayOfMonth();
//--- Iteration über alle Elemente der Tabelle
   for(int i=0; i<42; i++)
     {
      //--- Wenn das Datum des aktuellen Elements kleiner ist als das angegebene Mindestdatum
      if(m_temp_date.DateTime()<datetime(D'01.01.1970'))
        {
         //--- Wenn dieses Objekt angeklickt wurde
         if(m_days[i].Name()==clicked_object)
           {
            //--- Wert markieren und verlassen
            m_years.HighlightLimit();
            return(false);
           }
         //--- Wechsel zum nächsten Datum
         m_temp_date.DayInc();
         continue;
        }
      //--- Wenn dieses Objekt angeklickt wurde
      if(m_days[i].Name()==clicked_object)
        {
         //--- Sichere das Datum
         m_date.DateTime(m_temp_date.DateTime());
         //--- Anzeige der letzten Änderungen im Kalender
         UpdateCalendar();
         break;
        }
      //--- Wechsel zum nächsten Datum
      m_temp_date.DayInc();
      //--- Verlassen, wenn das Datum zu groß ist
      if(m_temp_date.year>m_years.MaxValue())
        {
         //--- Wert markieren und verlassen
         m_years.HighlightLimit();
         return(false);
        }
     }
//--- Senden einer Nachricht darüber
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }

Jetzt bleibt noch die letzte Methode der Kalenderereignisse — CCalendar::OnClickTodayButton(), die den Klick auf eine Taste verarbeitet, um zum aktuellen Datum zu gelangen. Der "long" Parameter (Element-Kennung) wird von der zentralen Methode aller Ereignisse versendet. Am Anfang der Methode wird die Liste geschlossen, wenn sie geöffnet ist. Dann werden die Kennungen verglichen. Passen die Kennungen, setzen wir das lokale Datum und die lokale Zeit (vom PC des Nutzers) in die Struktur. Dann wird der Kalender aktualisiert, um die letzte Änderungen anzuzeigen und am Ende der Methode wird eine Nachricht an alle Ereignisse gesendet, dass sich das Datum im Kalender geändert hat

Class CCalendar : public CElement
  {
private:
   //--- Klick auf die Taste, um zum aktuellen Datum zu wechseln
   bool              OnClickTodayButton(const long id);
  };
//+------------------------------------------------------------------+
//| Klick auf die Taste zum Wechsel des aktuellen Datums             |
//+------------------------------------------------------------------+
bool CCalendar::OnClickTodayButton(const long id)
  {
//--- Ist die Liste geöffnet, wird sie geschlossen
   if(m_months.GetListViewPointer().IsVisible())
      m_months.ChangeComboBoxListState();
//--- Beenden, wenn die Kennungen der Elemente nicht übereinstimmen
   if(id!=CElement::Id())
      return(false);
//--- Setze das aktuelle Datum
   m_date.DateTime(::TimeLocal());
//--- Anzeige der letzten Änderungen im Kalender
   UpdateCalendar();
//--- Senden einer Nachricht darüber
   ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
   return(true);
  }


Es gibt 8 Codeblocks (einer wurde bereits am Anfang dieses Teils besprochen) in der zentralen Bearbeitung der Kalenderereignisse CCalendar::OnEvent(). Sie sind unten aufgelistet.

  • Bearbeite Ereignisse der bewegten Maus (CHARTEVENT_MOUSE_MOVE). Das Reagieren auf die Maus passiert nur, wenn das Steuerelement nicht verborgen ist, wenn es ein Dropdown-Element ist und das Formular nicht blockiert ist. Das Programm verlässt diesen Block auch, wenn eine Liste der Monate aktiv (geöffnet) ist. Ist die Liste verborgen und wird die die linke Maustaste gedrückt, werden vorher blockierte (im Moment der Öffnung der Liste) Steuerelemente des Kalenders aktiviert, wenn zumindest eines von ihnen noch blockiert ist.  Ganz am Ende, werden die Koordinaten der Maus für die Änderung der Farben der Elemente der Tabelle an die Methode CCalendar::ChangeObjectsColor() gesendet. 
//--- Bearbeite Ereignisse der bewegten Maus
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Verlassen, wenn Element minimiert ist
      if(!CElement::IsVisible())
         return;
      //--- Verlassen, wenn es kein Dropdown-Element oder die Form blockiert ist
      if(!CElement::IsDropdown() && m_wnd.IsLocked())
         return;
      //--- Koordinaten uns Status der linken Maustaste
      int x=(int)lparam;
      int y=(int)dparam;
      m_mouse_state=(bool)int(sparam);
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      m_month_dec.MouseFocus(x>m_month_dec.X() && x<m_month_dec.X2() && 
                             y>m_month_dec.Y() && y<m_month_dec.Y2());
      m_month_inc.MouseFocus(x>m_month_inc.X() && x<m_month_inc.X2() && 
                             y>m_month_inc.Y() && y<m_month_inc.Y2());
      //--- Verlassen, wenn die Monatsliste aktiv ist
      if(m_months.GetListViewPointer().IsVisible())
         return;
      //--- Ist sie nicht aktiv und wurde die linke Maustaste geklickt...
      else if(m_mouse_state)
        {
         //--- ...aktiviere die Steuerelemente, die vorher blockiert waren (im Moment der Öffnung der Liste),
         //       wenn wenigsten eines nicht blockiert ist
         if(!m_button_today.ButtonState())
           {
            m_years.SpinEditState(true);
            m_button_today.ButtonState(true);
           }
        }
      //--- Ändern der Farben der Objekte
      ChangeObjectsColor(x,y);
      return;
     }

  • Handhabung des Klicks der linken Maustaste über einem Objekt (CHARTEVENT_OBJECT_CLICK). Dies geschieht bei einem Klick über einem beliebigen Objekt des Kalenders. Wenn die linke Maustaste geklickt wurde, werden die Steuerelemente des Kalenders aktiviert (Eingabefeld und Taste). Es wird weiter geprüft, welches Element geklickt wurde. Zu diesem Zweck verwenden wir die oben erwähnten Methoden CCalendar::OnClickMonthDec(), CCalendar::OnClickMonthInc() und CCalendar::OnClickDayOfMonth().  
//--- Klick der linken Maustaste über einem Objekt
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Beenden, wenn das Element blockiert ist und die Kennungen nicht übereinstimmen
      if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id())
         return;
      //--- Verlassen, wenn die Monatsliste aktiviert ist
      if(m_months.GetListViewPointer().IsVisible())
         return;
      //--- Aktiviere die Steuerelemente (Liste und Eingabefeld) beim Klick der linken Maustaste 
      if(m_mouse_state)
        {
         m_years.SpinEditState(true);
         m_button_today.ButtonState(true);
        }
      //--- Bearbeite die Taste zum Monatswechsel
      if(OnClickMonthDec(sparam))
         return;
      if(OnClickMonthInc(sparam))
         return;
      //--- Klick auf einen Tag des Monats
      if(OnClickDayOfMonth(sparam))
         return;
     }

  • Bearbeite den Klick auf ein Element der Liste der Kombinationsfelder (ON_CLICK_COMBOBOX_ITEM). Für diese Ereignisse wird die Methode CCalendar::OnClickMonthList() verwendet.  
//--- Klick auf ein Element der Liste der Kombinationsfelder
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
     {
      //--- Bearbeite die Auswahl des Monats aus der Liste
      if(!OnClickMonthList(lparam))
         return;
      //---
      return;
     }

  •  Klick auf die Taste erhöhen/verringern (ON_CLICK_INC/ON_CLICK_DEC). Zur Bearbeitung dieser Ereignisse gibt es zwei einzelne Codeblöcke für den Aufruf der Methoden CCalendar::OnClickYearInc() und CCalendar::OnClickYearDec()
//--- Klick auf die Taste eins mehr
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_INC)
     {
      //--- Erledige das Klicken Sie auf die Taste, um zu einem nächsten Jahr zu wechseln
      if(!OnClickYearInc(lparam))
         return;
      //---
      return;
     }
//--- Klick auf die Taste eins weniger
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_DEC)
     {
      //--- Klick auf die Taste, um zum vorherigen Jahr zu wechseln
      if(!OnClickYearDec(lparam))
         return;
      //---
      return;
     }

  • Bearbeitung eines Eintrags in das Eingabefeld (CHARTEVENT_OBJECT_ENDEDIT). Ein Eintrag in das Eingabefeld des Jahres verzweigt in diesen Codeblock und ruft die Methode CCalendar::OnEndEnterYear() auf. 
//--- Ein Eintrag in das Eingabefeld
   if(id==CHARTEVENT_OBJECT_ENDEDIT)
     {
      //--- Bearbeite den eingetragenen Wert im Eingabefeld des Jahres
      if(OnEndEnterYear(sparam))
         return;
      //---
      return;
     }

  • Bearbeitung eines Klicks auf die Taste (ON_CLICK_BUTTON). In diesem Codeblock wird unter Verwendung der Methode CCalendar::OnClickTodayButton() bei einem Klick auf die Taste des aktuellen Datums (lokales Datum des Nutzer-PCs) umgeschaltet:
//--- Klick auf die Taste
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- Klick auf die Taste, um zum aktuellen Datum zu wechseln
      if(!OnClickTodayButton(lparam))
         return;
      //---
      return;
     }

Um die Verwendung solcher komplexer Steuerungen des Kalenders bequemer zu machen, fügen wir Funktionen hinzu, die einen schnellen Wechsel der Werte erlauben. Etwas Ähnliches wurde bereits in den Klassen der Liste (CListView) und des Eingabefeldes (CSpinEdit) umgesetzt. Sie müssen mit der Kalendertabelle verbunden werden, um mit den geänderten Werten der Eingabefelder die Tabelle zu aktualisieren. Wir werde auch die Möglichkeit ergänzen, für einen schnellen Wechsel des Monats. 

Wir bieten hier nur eine Beschreibung des zentralen (operationalen) Teils der Methode, weil die Bedingungen des operationalen Blocks bereits in diesem Artikel beschrieben wurden. Nach Erfüllung der Bedingungen müssen wir überprüfen, welche Taste der Kalenderelemente gerade geklickt wurden (siehe gelb markierte Zeilen). Wurde keine der aufgeführten Tasten geklickt, wird diese Methode verlassen. Nachdem eine geklickte Taste erkannt wurde, müssen ein paar Prüfungen gemacht werden, ob der Wert die Beschränkungen des Kalenders verletzt. Wurden diese Beschränkungen verletzt, wird der Wert im Eingabefeld markiert und das Programm verlässt die Methode. Wurden diese Beschränkungen nicht verletzt, wird der Kalender aktualisiert, er zeigt die letzten Änderungen und sendet eine Nachricht, dass das Datum geändert wurde

Class CCalendar : public CElement
  {
private:
   //--- Schnellwechsel der Kalenderwerte
   void              FastSwitching(void);
  };
//+------------------------------------------------------------------+
//| Schnellwechsel der Kalenderwerte                                 |
//+------------------------------------------------------------------+
void CCalendar::FastSwitching(void)
  {
//--- Verlassen, wenn es keinen Fokus auf ein Steuerelement gibt
   if(!CElement::MouseFocus())
      return;
//--- Beenden, wenn das Element blockiert ist und die Kennungen nicht übereinstimmen
   if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id())
      return;
//--- Rückgabe des Zählers des ersten Wertes, wenn die Maustaste losgelassen wurde
   if(!m_mouse_state)
      m_timer_counter=SPIN_DELAY_MSC;
//--- Wenn die Maustaste geklickt wurde
   else
     {
      //--- Erhöhe den Zähler um das angegebenen Intervall
      m_timer_counter+=TIMER_STEP_MSC;
      //--- Verlassen, wenn kleiner als Null
      if(m_timer_counter<0)
         return;
      //--- Beim Klick auf den linken Pfeil
      if(m_month_dec.State())
        {
         //--- Ist das aktuelle Jahr des Kalenders größer oder gleich dem Mindestwert
         if(m_date.year>=m_years.MinValue())
           {
            //--- Ist das aktuelle Jahr gleich dem Minimum und
            //    der Monat "Januar"
            if(m_date.year==m_years.MinValue() && m_date.mon==1)
              {
               //--- Wert markieren und verlassen
               m_years.HighlightLimit();
               return;
              }
            //--- Fortfahren mit dem nächsten (niedrigeren) Monat
            m_date.MonDec();
            //--- Setze den ersten Tag des Monats
            m_date.day=1;
           }
        }
      //--- Beim Klick auf den rechten Pfeil
      else if(m_month_inc.State())
        {
         //--- Ist das aktuelle Jahr kleiner/gleich dem maximalem Jahr
         if(m_date.year<=m_years.MaxValue())
           {
            //--- Ist das aktuelle Jahr gleich dem Maximum und
            //    der Monat ist "Dezember"
            if(m_date.year==m_years.MaxValue() && m_date.mon==12)
              {
               //--- Wert markieren und verlassen
               m_years.HighlightLimit();
               return;
              }
            //--- Wechseln Sie zum nächsten Monat (aufwärts)
            m_date.MonInc();
            //--- Setze den ersten Tag des Monats
            m_date.day=1;
           }
        }
      //--- Wenn Sie die Inkrement-Taste des Eingabefeldes des Jahres geklickt wurde
      else if(m_years.StateInc())
        {
         //--- Wenn es kleiner als das maximale Jahr ist,
         //    Wechseln Sie zum nächsten Jahr (aufwärts)
         if(m_date.year<m_years.MaxValue())
            m_date.YearInc();
         else
           {
            //--- Wert markieren und verlassen
            m_years.HighlightLimit();
            return;
           }
        }
      //--- Wenn Sie die Dekrement-Taste des Eingabefeldes geklickt wurde
      else if(m_years.StateDec())
        {
         //--- Wenn es größer als das minimale Jahr ist,
         //    wechseln Sie zum nächsten Jahr (abwärts)
         if(m_date.year>m_years.MinValue())
            m_date.YearDec();
         else
           {
            //--- Wert markieren und verlassen
            m_years.HighlightLimit();
            return;
           }
        }
      else
        return;
      //--- Anzeige der letzten Änderungen im Kalender
      UpdateCalendar();
      //--- Senden einer Nachricht darüber
      ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),"");
     }
  }

Im Moment, da von einem Tag auf den anderen gewechselt wird, muss der aktuelle Tag im Kalender aktualisiert werden (NutzerPCs Lokalzeit). Ebenso muss das Datum auf der Taste, mit der zum aktuellen Tag gewechselt wird, aktualisiert werden. Wir schreiben zu diesem Zweck ein eigene Methode namens CCalendar::UpdateCurrentDate() und werden den Wechsel des aktuellen Datums jede Sekunde abfragen (siehe den Code unten). 

Class CCalendar : public CElement
  {
public:
   //--- aktualisiere das aktuelle Tagesdatum
   void              UpdateCurrentDate(void);
  };
//+------------------------------------------------------------------+
//| aktualisiere das aktuelle Tagesdatum                             |
//+------------------------------------------------------------------+
void CCalendar::UpdateCurrentDate(void)
  {
//--- Zähler
   static int count=0;
//--- Verlassen, wenn kleiner als eine Sekunde
   if(count<1000)
     {
      count+=TIMER_STEP_MSC;
      return;
     }
//--- Zähler auf Null setzen
   count=0;
//--- Abfrage der aktuellen (lokalen) Zeit
   MqlDateTime local_time;
   ::TimeToStruct(::TimeLocal(),local_time);
//--- Falls das Datum wechselte
   if(local_time.day!=m_today.day)
     {
      //--- Aktualisiere das Datum im Kalender
      m_today.DateTime(::TimeLocal());
      m_button_today.Object(2).Description(::TimeToString(m_today.DateTime()));
      //--- Anzeige der letzten Änderungen im Kalender
      UpdateCalendar();
      return;
     }
//--- Aktualisiere das Datum im Kalender
   m_today.DateTime(::TimeLocal());
  }

Der Code eines Timers für den Kalender schaut in diesem Fall ganz ähnlich aus: 

//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CCalendar::OnEventTimer(void)
  {
//--- Ist es ein Dropdown-Element und die Liste verborgen
   if(CElement::IsDropdown() && !m_months.GetListViewPointer().IsVisible())
     {
      ChangeObjectsColor();
      FastSwitching();
     }
   else
     {
      //--- Verfolge den Farbwechsel und die Werte des schnellen Wechsels, 
      //    nur wenn es nicht blockiert ist
      if(!m_wnd.IsLocked())
        {
         ChangeObjectsColor();
         FastSwitching();
        }
     }
//--- Aktualisiere das aktuelle Datum des Kalenders
   UpdateCurrentDate();
  }

Wir haben jetzt alle Methoden der Klasse CCalendar besprochen. Testen wir, wie alles funktioniert. Zuvor müssen wir jedoch das Steuerelement mit der Engine der Bibliothek für ein reibungsloses Funktionieren verbinden. Eine ausführliche Schritt-für-Schritt Anleitung, wie dies geschieht, wurde bereits in dem vorherigen Artikel Grafische Interfaces V: Das Combobox Control (Kapitel 3) beschrieben. Daher werden wir hier nur (im Hauptteil der Klasse CWndContainer) die Arrays in der Struktur der persönlichen Arrays der Steuerelemente und die Methoden erklären, um (1) eine Anzahl Kalender auf der grafischen Oberfläche der MQL-Anwendung zu erhalten und (2) Indikatoren der Steuerelemente zu speichern, wenn Sie sie der Basis für die Erstellung einer graphischen Oberfläche hinzufügen. Den Code dieser Methoden finden Sie in den beigefügten Dateien des Artikels.  

//+------------------------------------------------------------------+
//| Klasse zum Sichern aller Interface-Objekte                       |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- Die Struktur der Kontroll-Arrays
   struct WindowElements
     {
      //--- Persönliche Arrays der Steuerelemente:
      //--- Array des Kalenders
      CCalendar        *m_calendars[];
   //---
public:
   //--- Anzahl der Kalender
   int               CalendarsTotal(const int window_index);
   //---
private:
   //--- Speichert die Zeiger der Kalenderelemente in der Basis
   bool              AddCalendarElements(const int window_index,CElement &object);
     };

 

 

 

Testelement des Kalenders

Für Testzwecke können Sie alle Expert Advisor aus dem vorherigen Artikel verwenden. Von den beigefügten Steuerelementen des graphischen Interfaces behalten wir nur das Hauptmenü und eine Zeichenkette, die den Status anzeigt. Wir erzeugen zwei Kalender, um zu analysieren, ob es zwischen beiden zu Konflikten kommen könnte.

In der Nutzerklasse der Anwendung (CProgram) deklarieren wir zwei Instanzen der Klasse CCalendar und jeweils eine Methode, die die Steuerelemente des jew. Kalenders erstellt:

class CProgram : public CWndEvents
  {
private:
   //--- Kalender
   CCalendar         m_calendar1;
   CCalendar         m_calendar2;
   //---
private:
   //--- Kalender
#define CALENDAR1_GAP_X       (2)
#define CALENDAR1_GAP_Y       (43)
   bool              CreateCalendar1(void);
#define CALENDAR2_GAP_X       (164)
#define CALENDAR2_GAP_Y       (43)
   bool              CreateCalendar2(void);
  };

Als Beispiel zeigen wir hier nur eine der beiden Methoden. Wir verwenden die Standardeinstellungen der Kalender (siehe den Code unten). 

//+------------------------------------------------------------------+
//| Erstelle Kalender 1                                              |
//+------------------------------------------------------------------+
bool CProgram::CreateCalendar1(void)
  {
//--- Übergebe das Objekt dem Bedienungsfeld
   m_calendar1.WindowPointer(m_window1);
//--- Koordinaten
   int x=m_window1.X()+CALENDAR1_GAP_X;
   int y=m_window1.Y()+CALENDAR1_GAP_Y;
//--- Erstellen der Steuerelemente
   if(!m_calendar1.CreateCalendar(m_chart_id,m_subwin,x,y))
      return(false);
//--- Hinzufügen der Objekte dem allg. Array der Objektgruppe
   CWndContainer::AddToElementsArray(0,m_calendar1);
   return(true);
  }

Wir rufen diese Methoden in der Hauptmethode bei der Erstellung des graphischen Interfaces durch die MQL-Anwendung auf, compilieren sie und laden sie auf den Chart. Am Ende sollte alles so aussehen:

 Fig. 2. Testen der Steuerelemente des Kalenders.

Fig. 2. Testen der Steuerelemente des Kalenders.


Die Entwicklung der Klasse CCalendar zur Erstellung eines Kalenders ist damit abgeschlossen. In die Zukunft wird seiner Funktionalität erweitert werden, in diesem Artikel betrachten wir aber noch eine zweite Version, einen Dropdown-Kalender. 

 

 


Ein Dropdown-Kalender

Ein Dropdown-Kalender, anders als der statische Kalender (CCalendar), spart Platz auf dem graphischen Interface der Anwendung, da er die meiste Zeit minimiert ist. Ein vom Nutzer gewähltes Datum erscheint im Textfeld des Kombinationsfeldes. Wenn ein anderes Datum gewählt werden soll, muss auf das Kombinationsfeld geklickt werden. Dieses Ereignis lässt den Kalender erscheinen. Wenn Sie erneut Sie auf die Taste klicken, wird der Kalender geöffnet. Ein Kalender kann minimiert werden, wenn die linke Taste der Maus außerhalb des Kalenders geklickt wird oder sich die Darstellung des Charts geändert hat.

 

Fig. 3. Elemente des Dropdown-Kalenders.


Die Struktur dieser Elemente wird im Folgenden betrachtet.

 


Die Klasse CDropCalendar

In den früheren Artikeln Grafische Interfaces V: Das Combobox Control (Kapitel 3) und Graphische Interfaces VI: Das Checkbox Control, Das Edit Control und deren gemischte Typen (Kapitel 1), wurde beispielsweise die Combobox schon als Bibliothekselement (CComboBox) und als Combobox mit einer Liste und einer Check-Combobox(CCheckComboBox) beschrieben. Daher werden wir einfach nur die wichtigsten Aspekte des Dropdown-Kalenders erwähnen.

Wir erstellen die Datei DropCalendar.mqh und verbinden sie mit der Bibliothek (die Datei WndContainer.mqh):

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

In der Datei DropCalendar.mqh erstellen wir ein Klasse mit den Standardmethoden die alle Steuerelemente hat und verbinden die Datei mit der Klasse des Kalenders Calendar.mqh:

//+------------------------------------------------------------------+
//|                                                 DropCalendar.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
#include "Calendar.mqh"

Wir gehen sofort zu der Liste der für die Erstellung der Steuerelemente notwendigen Methoden. Eine "public" und sechs "private" Methoden sind notwendig: 

//+------------------------------------------------------------------+
//| Klasse für die Erstellung eines Dropdown-Kalenders               |
//+------------------------------------------------------------------+
class CDropCalendar : public CElement
  {
private:
   //--- Objekte und Elemente für die Erstellung des Elementes
   CRectLabel        m_area;
   CLabel            m_label;
   CEdit             m_field;
   CEdit             m_drop_button;
   CBmpLabel         m_drop_button_icon;
   CCalendar         m_calendar;
   //---
public:
   //--- Methode zur Erstellung des Dropdown-Kalenders
   bool              CreateDropCalendar(const long chart_id,const int subwin,const string text,const int x,const int y);
   //---
private:
   bool              CreateArea(void);
   bool              CreateLabel(void);
   bool              CreateEditBox(void);
   bool              CreateDropButton(void);
   bool              CreateDropButtonIcon(void);
   bool              CreateCalendar(void);
  };

Beachten Sie, beim Erstellen des Kalenders, müssen wir den Status des Dropdown-Elementes setzen. Nach dem Erstellen aller Objekte des Steuerelementes durch die Hauptmethode ("public") CDropCalendar::CreateDropCalendar(), ist folgendes zu tun:

  • Ausblenden des Kalenders.
  • Setzen des gewählten Datums, das schon im Informationsfeld der Combobox des Kalenders nach dessen Erstellung steht und das mit der Methode CCalendar::SelectedDate() abgerufen werden kann.

Eine verkürzte Version der Methode CDropCalendar::CreateDropCalendar() ist unten aufgeführt. Die vollständige Version kann in den Dateien des Artikel eingesehen werden.

//+------------------------------------------------------------------+
//| Erstellt ein Dropdown-Kalender                                   |
//+------------------------------------------------------------------+
bool CDropCalendar::CreateDropCalendar(const long chart_id,const int subwin,const string text,const int x,const int y)
  {
//--- Verlassen ohne Pointer auf die Form
//--- Initialisierung der Variablen
//--- Abstand vom Ankerpunkt
//--- Erstellen der Steuerelemente
//--- Ausblenden des Kalenders
   m_calendar.Hide();
//--- Anzeige des gewählten Datums im Kalender
   m_field.Description(::TimeToString((datetime)m_calendar.SelectedDate(),TIME_DATE));
//--- Ausblenden des Elementes bei einem Dialogfenster oder einem minimierten Fenster
   if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized())
      Hide();
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Erstelle eine Liste                                              |
//+------------------------------------------------------------------+
bool CDropCalendar::CreateCalendar(void)
  {
//--- Übergebe das Objekt dem Bedienungsfeld
   m_calendar.WindowPointer(m_wnd);
//--- Koordinaten
   int x=m_field.X();
   int y=m_field.Y2();
//--- Setze den Status des Dropdown-Steuerelements des Kalenders
   m_calendar.IsDropdown(true);
//--- Erstellen der Steuerelemente
   if(!m_calendar.CreateCalendar(m_chart_id,m_subwin,x,y))
      return(false);
//---
   return(true);
  }

Die folgenden Methoden sind für das Funktionieren der Combobox erforderlich.

  • Wechsel der Sichtbarkeit des Kalenders — CDropCalendar::ChangeComboBoxCalendarState().
  • Bearbeitung der Taste der Combobox — CDropCalendar::OnClickButton().
  • Prüfe die geklickte linke Maustaste über der Taste der Combobox — CDropCalendar::CheckPressedOverButton().

Alle diese Methoden sind der bereits besprochenen Klasse CComboBox sehr ähnlich, wir werden daher nicht weiter auf sie eingehen. 

Die Bearbeitung der Ereignisse des Dropdown-Kalenders erfolgt wie unten gezeigt. Es gibt vier Blocks zur Bearbeitung dieser Ereignisse: 

Bei einem Ereignis ON_CHANGE_DATE, das durch die Klasse CCalendar erzeugt wird, müssen wir die Kennungen der Elemente überprüfen und das neue Datum in das Eingabefeld der Combobox eintragen.

//+------------------------------------------------------------------+
//| Ereignishandhabung                                               |
//+------------------------------------------------------------------+
void CDropCalendar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Bearbeite Ereignisse der bewegten Maus
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- Verlassen, wenn Element minimiert ist
      if(!CElement::IsVisible())
         return;
      //--- Koordinaten uns Status der linken Maustaste
      int x=(int)lparam;
      int y=(int)dparam;
      m_mouse_state=(bool)int(sparam);
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      m_drop_button.MouseFocus(x>m_drop_button.X() && x<m_drop_button.X2() && 
                               y>m_drop_button.Y() && y<m_drop_button.Y2());
      //--- Prüfung der geklickten, linken Maustaste über der Taste der Combobox
      CheckPressedOverButton();
      return;
     }
//--- Bearbeite das neu gewählte Datum im Kalender
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE_DATE)
     {
      //--- Beenden, wenn die Kennungen der Elemente nicht übereinstimmen
      if(lparam!=CElement::Id())
         return;
      //--- Eintragen des neuen Datums in das Feld der Combobox
      m_field.Description(::TimeToString((datetime)dparam,TIME_DATE));
      return;
     }
//--- Klick der linken Maustaste über einem Objekt
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Klick auf die Taste der Combobox
      if(OnClickButton(sparam))
         return;
      //---
      return;
     }
//--- Bearbeite die Änderung der Eigenschaften des Charts
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- Verlassen, wenn Element blockiert ist
      if(!m_drop_calendar_state)
         return;
      //--- Ausblenden des Kalenders
      m_calendar.Hide();
      m_drop_button_icon.State(false);
      //--- Rücksetzen der Farben
      ResetColors();
      return;
     }
  }

Für das ordnungsgemäßen Funktionieren des Elements wie auch der Klasse CCalendar müssen die Basisklassenbibliotheken CWndContainer ergänzt werden:

//+------------------------------------------------------------------+
//| Klasse zum Sichern aller Interface-Objekte                       |
//+------------------------------------------------------------------+
class CWndContainer
  {
protected:
   //--- Die Struktur der Kontroll-Arrays
   struct WindowElements
     {
      //--- Persönliche Arrays der Steuerelemente:
      //--- Array des Dropdown-Kalenders
      CDropCalendar    *m_drop_calendars[];
   //---
public:
   //--- Zahl der Dropdown-Kalender
   int               DropCalendarsTotal(const int window_index);
   //---
private:
   //--- Speichert den Pointer auf die Elemente des Dropdown-Kalenders in der Datenbank
   bool              AddDropCalendarElements(const int window_index,CElement &object);
     };

Weiters müssen wir der Hauptklasse der Bibliothek einen Codeblock hinzufügen, der den Dropdown-Kalender prüft, um die Ereignisse aller Steuerelemente der Methode CWndEvents::SetChartState() zu handhaben, wenn sich der Status des Chart ändert (eine verkürzte Version dieser Methode ist unten angeführt):

//+------------------------------------------------------------------+
//| Status des Chart                                                 |
//+------------------------------------------------------------------+
void CWndEvents::SetChartState(void)
  {
   int awi=m_active_window_index;
//--- Zur Identifizierung des Ereignisses, bei denen das Management deaktiviert werden soll
   bool condition=false;
//--- Prüfung der Fenster
//--- Prüfung der Dropdown-Liste
//--- Prüfung des Kalenders
   if(!condition)
     {
      int drop_calendars_total=CWndContainer::DropCalendarsTotal(awi);
      for(int i=0; i<drop_calendars_total; i++)
        {
         if(m_wnd[awi].m_drop_calendars[i].GetCalendarPointer().MouseFocus())
           {
            condition=true;
            break;
           }
        }
     }
//--- Prüfung des Fokus' des Kontextmenüs
//--- Prüfung des Status' der Bildlaufleiste
//--- Setze den Status des Charts in allen Bereichen
   for(int i=0; i<windows_total; i++)
      m_windows[i].CustomEventChartState(condition);
  }

 

 

 

Test des Steuerelements des Dropdown-Kalenders

Wir ergänzen zwei Dropdown-Kalender, die bereits in diesem Artikel in einem graphischen Interface eines Expert Advisors getestet wurden. In der Nutzerklasse einer Anwendung deklarieren wir zwei Instanzen des Dropdown-Kalenders vom Typ CDropCalendar mit einem Abstand vom Anker (obere linke Ecke). 

class CProgram : public CWndEvents
  {
private:
   //--- Dropdown-Kalender
   CDropCalendar     m_drop_calendar1;
   CDropCalendar     m_drop_calendar2;
   //---
private:
   //--- Dropdown-Kalender
#define DROPCALENDAR1_GAP_X   (7)
#define DROPCALENDAR1_GAP_Y   (207)
   bool              CreateDropCalendar1(const string text);
#define DROPCALENDAR2_GAP_X   (170)
#define DROPCALENDAR2_GAP_Y   (207)
   bool              CreateDropCalendar2(const string text);
  };

Beispielsweise zeigen wir hier nur den Code von einer der Methoden:

//+------------------------------------------------------------------+
//| Erstelle den Dropdown-Kalender 1                                 |
//+------------------------------------------------------------------+
bool CProgram::CreateDropCalendar1(const string text)
  {
//--- Übergebe das Objekt dem Bedienungsfeld
   m_drop_calendar1.WindowPointer(m_window1);
//--- Koordinaten
   int x=m_window1.X()+DROPCALENDAR1_GAP_X;
   int y=m_window1.Y()+DROPCALENDAR1_GAP_Y;
//--- Setze Eigenschaften vor der Erstellung
   m_drop_calendar1.XSize(145);
   m_drop_calendar1.YSize(20);
   m_drop_calendar1.AreaBackColor(clrWhiteSmoke);
//--- Erstellen der Steuerelemente
   if(!m_drop_calendar1.CreateDropCalendar(m_chart_id,m_subwin,text,x,y))
      return(false);
//--- Übergebe den Pointer an die Basis
   CWndContainer::AddToElementsArray(0,m_drop_calendar1);
   return(true);
  }

Der Aufruf der Methoden, die die Steuerelemente erstellen, muss in der Hauptmethode erfolgen, die die Schnittstelle erstellt. Das ist in diesem Fall die Methode CProgram::CreateExpertPanel(). Zum Beispiel sollte der Code für die Handhabung der Kalender-Ereignisse in der Methode CProgram::OnEvent() wie unten gezeigt hinzugefügt werden. Jedes Mal wenn das Datum im Kalender geändert wird, erscheint eine Nachricht mit den Parameterwerten des Ereignisses im Log.

//+------------------------------------------------------------------+
//| Ereignishandhabung                                               |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Ereignis des Änderns des Datums im Kalender
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE_DATE)
     {
      ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",datetime(dparam),"; sparam: ",sparam);
     }
  }

Kompilieren Sie den Code der Anwendung und starten Sie ihn auf dem Chart des Terminals. Das Ergebnis sehen Sie auf dem Bild unten:

 Fig. 4. Test des Dropdown-Elementes.

Fig. 4. Test des Dropdown-Elementes.

 

 


Schlussfolgerung

In diesem Artikel (das erste Kapitel des achten Teils der Serie) haben wir einen recht komplexen, graphischen Kalender und seine erweiterte Version betrachtet - einen Dropdown-Kalender. Solche Steuerelemente für Charts mit einer Zeitskala können sehr hilfreich sein. Dies ist nicht die endgültige Version und zukünftige Neuerungen zielen darauf, den Kalender noch bequemer und funktioneller zu gestalten. Sie können auch eigene Vorschläge und Ideen im Kommentar äußern.

Im nächsten Artikel wenden wir uns wichtigen Elementen graphischer Schnittstellen zu, wie Baumansichten und hierarchischen Listen. 

Sie können alle Informationen zum siebten Teil herunterladen, um alles selber zu testen. Für alle Ihre Fragen bezüglich der Verwendung des in den Dateien zur Verfügung gestellten Materials finden Sie eine ausführlichen Beschreibung des Prozesses der Entwicklung der Bibliothek in einem der Artikel der Liste unten oder stellen Sie einfach Ihre Fragen im Kommentarteil dieses Artikels.

Liste der Artikel (Kapitel) Teil 8:

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/2537

Beigefügte Dateien |
Grafische Interfaces VIII: Die Baumansicht (Kapitel 2) Grafische Interfaces VIII: Die Baumansicht (Kapitel 2)
Das vorherige Kapitel VIII der grafischen Schnittstellen hat sich mit den Elementen eines statischen und eines Dropdown-Kalenders beschäftigt. Das zweite Kapitel beschäftigt sich mit einem nicht weniger komplexen Element — der Baumansicht, die Teil aller kompletten Bibliotheken graphischer Schnittstellen ist. Die Baumansicht in diesem Artikel beinhaltet mehrere flexible Einstellungen und Modi und erlaubt daher die Elemente ganz Ihren Zielen anzupassen.
Wie man eine Handelsstrategie in MetaTrader 5 schnell entwickeln und debuggen kann Wie man eine Handelsstrategie in MetaTrader 5 schnell entwickeln und debuggen kann
Automatische Scalping-Systeme gelten zurecht als der Höhepunkt des algorithmischen Tradings, aber es ist auch am kompliziertesten, einen Code für diese Systeme zu schreiben. In diesem Artikel zeigen wir, wie man mithilfe von eingebauten Werkzeugen für Debugging und visuelles Testen Strategien entwickeln kann, die auf der Analyse eingehender Ticks basieren. Um Regeln für Einstieg und Ausstieg zu erarbeiten, braucht man häufig jahrelang manuellen zu handeln. Aber mithilfe von MetaTrader 5 können Sie jede solche Strategie anhand realer historischer Daten schnell testen.
Grafische Interfaces VIII: Das Datei-Navigator Control (Kapitel 3) Grafische Interfaces VIII: Das Datei-Navigator Control (Kapitel 3)
In den vorherigen Kapiteln des 8 Teils dieser Serie, haben wir unsere Bibliothek um mehrere Klassen für die Entwicklung von Mauszeigern, Kalendern und Baum-Ansichten erweitert. In dem aktuellen Artikel beschäftigen wir uns mit dem Datei-Navigator-Control, welcher auch als Teil eines grafischen Interfaces einer MQL Anwendung verwendet werden kann.
Grafische Interfaces VII: Das Tab-Control (Kapitel 2) Grafische Interfaces VII: Das Tab-Control (Kapitel 2)
Das erste Kapitel des siebten Teils befasste sich mit drei Klassen von Controls für die Erzeugung der folgenden Tabellen: Text-Label-Tabelle (CLabelsTable), Edit-box-Tabelle (CTable) und die gerenderte Tabelle (CCanvasTable). In diesem Artikel(Kapitel 2) werden wir das Tabs-Control besprechen.