
グラフィカルインタフェースVIII:カレンダーコントロール(チャプター1)
コンテンツ
はじめに
このライブラリの目的のより良い理解を得るためには当シリーズ最初のグラフィカルインタフェース I: ライブラリストラクチャの準備(チャプター 1)稿をお読みください。リンクを含むチャプターのリストは各部の記事の終わりに設けられています。また、そこからはライブラリーの最新のフルバージョンをダウンロードすることもできます。ファイルはアーカイブと同じディレクトリに配置される必要があります。
第八部では、複雑な複合コントロールに焦点が当てられます。
- 静的なドロップダウンカレンダー
- ツリービュー
- ファイルナビゲータ
本稿では、静的ドロップダウンカレンダーの作成に使われるクラスと、開発に使われる日付と時刻で動作する標準ライブラリのCDateTimeの構造体(struct)が考慮されます。
カレンダーコントロール
カレンダーは、テーブルに示された時間を計算するサイクリックシステムです。グラフィカルインターフェースは、ユーザが簡単にカレンダーで必要な日付を選択できるようにするコントロールを持っている必要があります。カレンダークラスは、ユーザと対話するカスタムメソッドに加えて他のライブラリーコントロールを含むことができます。ここでの例は(1)コンボボックス(2)入力フィールドおよび(3)ボタンを作成するためのクラスを含みます。
カレンダーのすべてのコンポーネントをリストしてみましょう。
- 領域
- 前月と翌月に切り替えるためのボタン
- 月のリストを持ったコンボボックスコントロール
- 年を入力するためのフィールド
- 曜日の略語でラベルされたテキストの配列
- 区切り線
- 日付のテキストラベルをもった2次元配列
- すぐに次の日にジャンプするためのボタン
図1 カレンダーのすべてのコンポーネント
例えば、開発中のMQLアプリケーションでは日付の範囲の選択、つまり開始日と終了日の指定が必要です。日を選択するには、テーブルで任意の日付の項目をクリックするので十分です。月を選択するには(1)前月に切り替えるボタン(2)翌月に切り替えるボタン(3)すべての月のリストを持つコンボボックス と複数のオプションがあります。年はフィールドにデータを手動で入力するか制御スイッチを使用して示すことができます。現在の日付にすばやく移動するためには、カレンダーの下部にあるToday: YYYY.MM.DDボタンをクリックするのに十分です。
CDateTime構造体が日付と時間の捜査のためにどのように配置されているかを詳しく見てみましょう。
CDateTime構造体の説明
CDateTimeを持ったDateTime.mqhファイルは下記のMetaTrader取引ターミナルのディレクトリに置かれます。
- MetaTrader 4: <data folder>\MQL4\Include\Tools
- MetaTrader 5: <data folder>\MQL5\Include\Tools
CDateTime構造体は、8つのint型のフィールドを持つMqlDateTimeと呼ばれる日付と時刻の基本システム構造体から派生(拡張)したもので す(使用例はMQL言語ドキュメントを参照)。
struct MqlDateTime { int year; // 年 int mon; // 月 int day; // 日 int hour; // 時 int min; // 分 int sec; // 秒 int day_of_week; // 曜日(0-日、1-月、 ... ,6-土) int day_of_year; // 年での日の番号(元旦は0) };
コードを伴わないCDateTime構造体の説明はMQL5 Reference/Standard Library/Classes for Control Panels and Dialogs/CDateTimeセクションのローカル検索(F1)で見つけられます。構造体での操作にあたり月の番号付けは1、週の番号付けは0から始まることを忘れてはなりません。
DateTime.mqhファイルを開いてコードに慣れるようお願いします。
CCalendarクラスの開発
開発中のライブラリの他のコントロールすべてで行うように、Calendar.mqhファイルを作成しWndContainer.mqhと呼ばれるファイルに含みます。
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Calendar.mqh"
Calendar.mqhファイルではすべてのライブラリコントロールに標準的な方法でCCalendarクラスを作成し、コントロールの開発に必要なファイルを含みます。
//+------------------------------------------------------------------+ //| Calendar.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" #include "SpinEdit.mqh" #include "ComboBox.mqh" #include "IconButton.mqh" #include <Tools\DateTime.mqh> //+------------------------------------------------------------------+ //| カレンダー作成クラス | //+------------------------------------------------------------------+ class CCalendar : public CElement { private: //--- コントロールが接続されるフォームへのポインタ CWindow *m_wnd; //--- public: CCalendar(void); ~CCalendar(void); //--- フォームへのポインタを格納する void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } //--- public: //--- チャートイベントの処理 virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- タイマー virtual void OnEventTimer(void); //--- コントロールの移動 virtual void Moving(const int x,const int y); //--- (1)表示 (2)非表示 (3)リセット (4)削除 virtual void Show(void); virtual void Hide(void); virtual void Reset(void); virtual void Delete(void); //--- マウスの左クリックの優先順位の(1)設定と(2)リセット virtual void SetZorders(void); virtual void ResetZorders(void); //--- 色をリセットする virtual void ResetColors(void) {} };
ライブラリインタフェースの他のコントロールと同様に、カレンダーの外観を設定するオプションが存在しなければなりません。以下は、ユーザが利用できる外観を参照するプロパティです。
- 領域の色
- 領域の境界の色
- 異なる状態での項目(日付)の色
- 異なる状態での項目の境界の色
- 異なる状態での項目テキストの色
- 区切り線の色
- 前月/翌月に移動するボタンのラベル(アクティブ/ブロックされた)
以下のコードは、カレンダーが作成される前にその外観を設定するCCalendar クラスのフィールド名とメソッド名を明らかにします。
class CCalendar : public CElement { private: //--- 領域の色 color m_area_color; //--- 領域の境界の色 color m_area_border_color; //--- 異なる状態でのカレンダー項目(日付)の色 color m_item_back_color; color m_item_back_color_off; color m_item_back_color_hover; color m_item_back_color_selected; //--- 異なる状態での項目の境界の色 color m_item_border_color; color m_item_border_color_hover; color m_item_border_color_selected; //--- 異なる状態での項目テキストの色 color m_item_text_color; color m_item_text_color_off; color m_item_text_color_hover; //--- 区切り線の色 color m_sepline_color; //--- 前月/翌月に移動するボタンのラベル(アクティブ/ブロックされた状態で) 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: //--- (1) 領域 (2) 領域の境界 (3) 区切り線の色を設定する 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; } //--- 異なる状態でのカレンダー項目(日付)の色 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; } //--- 異なる状態での項目の境界の色 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; } //--- 異なる状態での項目テキストの色 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; } //--- 前月/翌月に移動するボタンのラベル(アクティブ/ブロックされた状態で)の設定 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; } };
カレンダーの作成には9つのプライベートメソッドと1つのパブリックメソッドが必要です。CEditの静的配列型のオブジェクトは曜日と日付の表示に必要です。
日付のテーブルは42項目を含みます。月の最初の日が日曜日に当たるときのテーブルの最大オフセットを考慮し、31日ある月の最大日数に合わせるので十分です(この実装では、日曜日は週の第7日です)。
class CCalendar : public CElement { private: //--- カレンダー作成のオブジェクトとコントロール 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: //--- カレンダー作成メソッド 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); };
カレンダーが別のコントロールの一部である場合には、カレンダーコンポーネントコントロールへのアクセスが必要になるかもしれません。このような理由から、クラスに下記のコントロールへのポインタを返すメソッドを追加します。
- コンボボックス(CComboBox)
- コンボボックスのリスト(CListView)
- リストの縦スクロールバー(CScrollV)
- 入力フィールド(CSpinEdit)
- ボタン(CIconButton)
class CCalendar : public CElement { public: //--- (1) コンボボックスポインタを取得する // (2) リストポインタを取得する(3) リストスクロールバーポインタを取得する // (4) 入力フィールドポインタを取得する(5) ボタンポインタを取得する 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)); } };
日付と時刻との操作にはCDateTime構造体の3つのインスタンスが必要となります。
- ユーザとの対話。ユーザが自分自身で選択する日付(カレンダーで選択)。
- ユーザのPC上の現在またはシステム日付。この日付は、常にカレンダーにマークされています。
- 計算や確認のためのインスタンス。これは CCalendar クラスの多くのメソッドでカウンタとして使用されます。
class CCalendar : public CElement { private: //--- 日付と時刻との操作のための構造体の例 CDateTime m_date; // ユーザが選択した日付 CDateTime m_today; // ユーザのPC上の現在/システム日付 CDateTime m_temp_date; // 計算や確認のためのインスタンス };
時間構造体の初期化は CCalendar クラスコンストラクタで実行されます。m_dateとm_today構造体インスタンスでは、ユーザのPCのローカル時刻が設定されます(黄色で強調表示)。
//+------------------------------------------------------------------+ //| コンストラクタ | //+------------------------------------------------------------------+ 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("") { //--- コントロールクラス名を基本クラスに格納する CElement::ClassName(CLASS_NAME); //--- マウスの左クリックの優先順位の設定 m_zorder =0; m_area_zorder =1; m_button_zorder =2; //--- 時刻構造体の初期化 m_date.DateTime(::TimeLocal()); m_today.DateTime(::TimeLocal()); }
カレンダーの設定後、現在の月と年の値はテーブル(日付)の項目で設定する必要があります。これには4つのメソッドが必要です。
1. テーブルの1番目の項目から現在の月の1日までの違いを(日単位で)計算するCCalendar::OffsetFirstDayOfMonth() メソッド。このメソッドの初めでは、現在の年月の最初の日付を文字列として形成します。その後、文字列をdatetime形式に変換し、日付と構造体を計算のために設定します。現在の週番号から1を減算した結果が0以上の場合は結果はそのまま返されますが、1未満(-1)の場合は6が返されます。メソッドの最後には得られた差分による後方へのシフトが行われます(日の数)。
class CCalendar : public CElement { private: //--- テーブルの1番目の項目と現在の月の1日の差を計算する int OffsetFirstDayOfMonth(void); }; //+------------------------------------------------------------------+ //| カレンダータブの1番目の項目と現在の月の1日の | //| 差を定義する | //+------------------------------------------------------------------+ int CCalendar::OffsetFirstDayOfMonth(void) { //--- 選択された年と月の最初の日の日付を文字列として取得する string date=string(m_date.year)+"."+string(m_date.mon)+"."+string(1); //---この日付を計算のために構造内で設定する m_temp_date.DateTime(::StringToTime(date)); //--- 1が現在の週番号からひかれた結果が0以上の場合結果はそのまま返す // その他の場合は6を返す int diff=(m_temp_date.day_of_week-1>=0) ?m_temp_date.day_of_week-1 : 6; //--- テーブルの最初の項目にあるデータを格納する m_temp_date.DayDec(diff); return(diff); }
2. CCalendar::SetCalendar() メソッド。このメソッドは、選択された年と月のためにテーブルの項目を書き入れるのに使用されます。初めにCCalendar::OffsetFirstDayOfMonth()メソッドが呼び出されます。そして、構造体から計算のための日数を設定してループ内でテーブルのすべての項目を反復処理します(m_temp_date.day)。ここで CCalendar::OffsetFirstDayOfMonth() メソッドで数え始めに使われる日付が設定されます。カレンダーの翌日への変化はメソッドの終わり
で行われます。
Class CCalendar : public CElement { private: //--- カレンダーテーブルの直近の変化を表示する void SetCalendar(void); }; //+------------------------------------------------------------------+ //| カレンダー値の設定 | //+------------------------------------------------------------------+ void CCalendar::SetCalendar(void) { //--- テーブルの1番目の項目と現在の月の1日の違いを計算する int diff=OffsetFirstDayOfMonth(); //--- ループでカレンダーテーブルのすべての項目を反復処理する for(int i=0; i<42; i++) { //--- テーブルの現在の項目の日を設定する m_days[i].Description(string(m_temp_date.day)); //--- 翌日に移る m_temp_date.DayInc(); } }
3. CCalendar::HighlightDate() メソッドは現在の日(ユーザPCのシステム時刻)とカレンダーテーブルでユーザによって選択された日付の強調表示に使われます。テーブルの最初の項目の日付の計算のための構造体(m_temp_date)にここで最初に設定されます。次に(1)領域(2)領域境界(3)表示されたテキスト の色がループで決定されます(下記のコードを参照)。
Class CCalendar : public CElement { private: //--- 現在の日付とユーザが選択した日付を強調表示する void HighlightDate(void); }; //+------------------------------------------------------------------+ //| 現在の日付とユーザが選択した日付を強調表示する | //+------------------------------------------------------------------+ void CCalendar::HighlightDate(void) { //--- テーブルの1番目の項目と現在の月の1日の違いを計算する OffsetFirstDayOfMonth(); //--- テーブルの項目を反復処理する for(int i=0; i<42; i++) { //--- 月が現在の月と一致して // 項目の日付が選択された日付と一致する場合 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); //--- テーブルの次の項目に移る m_temp_date.DayInc(); continue; } //--- これが現在の日(今日)の場合 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); //--- テーブルの次の項目に移る 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); //--- テーブルの次の項目に移る m_temp_date.DayInc(); } }
4. CCalendar::UpdateCalendar() メソッドはカレンダーのグラフィカルインタフェースをユーザとの対話のために適用されるすべてのメソッドで使用されます。前述されたCCalendar::SetCalendar() 及び CCalendar::HighlightDate() メソッドはここで順番に呼び出されます。その後、年と月がカレンダーの入力フィールドとコンボボックスで設定されます。月の列挙は日付と時刻の構造体で1から始まりライブラリリスト(CListView)の列挙は0から始まるため、コンボボックスの一覧で必要な項目を選択するには 月からは1が引かれることにご注意ください。
Class CCalendar : public CElement { public: //--- カレンダーでの直近の変化を表示する void UpdateCalendar(void); }; //+------------------------------------------------------------------+ //| カレンダーでの直近の変化を表示する //+------------------------------------------------------------------+ void CCalendar::UpdateCalendar(void) { //--- カレンダーテーブルの直近の変化を表示する SetCalendar(); //--- 現在の日付とユーザが選択した日付を強調表示する HighlightDate(); //--- 設定フィールドで年を設定する m_years.ChangeValue(m_date.year); //--- コンボボックスリストで月を設定する m_months.SelectedItemByIndex(m_date.mon-1); }
マウスカーソルがホバーした際のカレンダーのテーブル内の日付の項目の色の変更は CCalendar::ChangeObjectsColor()で実行されます。x、y座標はそこに送信される必要があります。すべての項目の反復を伴うループを開始する前に、初期カウンタ値(m_temp_date構造体)を設定するために、月の最初の日を持つ項目のテーブルの最初の項目からのシフト量を決定します。その後、選択された日付と現在の(ユーザのPC上のシステム日付)日がスキップされ、残りはマウスカーソルのフォーカスをチェックするために使用されます。色を変更する場合、日が属する月が考慮されます。
Class CCalendar : public CElement { public: //--- カレンダーのテーブルでオブジェクトの色を変える void ChangeObjectsColor(const int x,const int y); }; //+------------------------------------------------------------------+ //| ホバーされているときに | //| カレンダーのテーブルでオブジェクトの色を変える | //+------------------------------------------------------------------+ void CCalendar::ChangeObjectsColor(const int x,const int y) { //--- テーブルの1番目の項目から現在の月の1日までの違いを計算する OffsetFirstDayOfMonth(); //--- テーブルの項目を反復処理する int items_total=::ArraySize(m_days); for(int i=0; i<items_total; i++) { //--- 月が現在の月と一致して // 項目の日付が選択された日付と一致する場合 if(m_temp_date.mon==m_date.mon && m_temp_date.day==m_date.day) { //--- テーブルの次の項目に移る m_temp_date.DayInc(); continue; } //--- 項目の年/月/日が現在の日付の年/月/日(今日)と一致した場合 if(m_temp_date.year==m_today.year && m_temp_date.mon==m_today.mon && m_temp_date.day==m_today.day) { //--- テーブルの次の項目に移る m_temp_date.DayInc(); continue; } //--- マウスカーソルがこの項目の上にある場合 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); } //--- テーブルの次の項目に移る m_temp_date.DayInc(); } }
プログラムを使用して、カレンダー上の日付を選択するオプションだけでなく、ユーザが選択した日付と現在の日付(今日)を取得するオプションも必要とされます。よってCCalendar::SelectedDate() 及びCCalendar::Today()publicメソッドの追加が必要です。
Class CCalendar : public CElement { public: //--- カレンダーの現在の日付を(1) 設定(選択) (2) 選択された日付を取得する (3) 現在の日付を取得する void SelectedDate(const datetime date); datetime SelectedDate(void) { return(m_date.DateTime()); } datetime Today(void) { return(m_today.DateTime()); } }; //+------------------------------------------------------------------+ //| 新しい日の選択 | //+------------------------------------------------------------------+ void CCalendar::SelectedDate(const datetime date) { //--- 日付を構造体とクラスフィールドに格納する m_date.DateTime(date); //--- カレンダーでの直近の変化を表示する UpdateCalendar(); }
例えば2016年2月29日(2016.02.29)がカレンダーで選択され、ユーザが入力フィールドに2017を入力した(または増減スイッチで設定した)とします。 2017年2月には28日あります。この場合には、カレンダーのテーブルでどの日が選択されるべきでしょうか?通常、これらの例では、グラフィカルインタフェースを備えたいろいろなカレンダーは選択された日に最も近い日、つまり月の最後の日が選ばれます。私たちの場合それは 2017年2月の28日です。なのでそうします。そのために、クラスに同様の補正のためのCCalendar::CorrectingSelectedDay() メソッドが追加されます。このメソッドのコードは2、3行です。CDateTime構造体にはうるう年を考慮した月の日数を受信するCDateTime::DaysInMonth()メソッドがあります。したがって、現在の月の日数と月の現在の日の比較が十分で、日数がカレンダーで選択された数を超えている場合、それを交換する必要があります。
Class CCalendar : public CElement { private: //--- 月の日数によって選択された日を訂正する void CorrectingSelectedDay(void); }; //+------------------------------------------------------------------+ //| 月の1日を決定する | //+------------------------------------------------------------------+ void CCalendar::CorrectingSelectedDay(void) { //--- 選択された日の値のほうが大きい場合には、月の現在の日番号を設定する if(m_date.day>m_date.DaysInMonth()) m_date.day=m_date.DaysInMonth(); }
カレンダーイベントハンドラ
今度は、カレンダーイベントのハンドラを見てみましょう。下記の一覧にあるイベントを処理する計8のプライベートメソッドがあります。
- 前月に移動するためのボタンのクリック
- 翌月に移動するためのボタンのクリック
- コンボボックスのドロップダウンリストからの月の選択
- 年の入力フィールドへの値を入力
- 翌年に移動するためのボタンのクリック
- 去年に移動するためのボタンのクリック
- 月の1日のクリック
- 現在の日に移動するためのボタンのクリック
- 上記の一覧のすべてのアクションはカレンダー内の変化をもたらします。これらのメソッドのコードに関しては論理的でありましょう。
カレンダーでの操作のためには、ユーザがカレンダー上の日付を変更したことを通知するメッセージの送信に使用されるユーザーイベントON_CHANGE_DATEの新しい識別子が必要となります。それに加えて、コンボボックスの操作のために、そのボタンのクリックを決定する識別子であるON_CLICK_COMBOBOX_BUTTONをもう1つ加えます 。
#define ON_CLICK_COMBOBOX_BUTTON (21) // コンボボックスボタンのクリック #define ON_CHANGE_DATE (22) // カレンダーの日付を変える
ON_CLICK_COMBOBOX_BUTTON識別子を持つメッセージの送信は CComboBoxクラスのOnClickButton() メソッドで行われます。このメソッドにメッセージを送信するオプションを追加しましょう(下のコードを参照)。
//+------------------------------------------------------------------+ //| コンボボックスボタンをクリックする | //+------------------------------------------------------------------+ bool CComboBox::OnClickButton(const string clicked_object) { //--- フォームがブロックされていて識別子が一致しない場合は終了する if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id()) return(false); //--- オブジェクト名が異なる場合は終了する | if(clicked_object!=m_button.Name()) return(false); //--- リストの状態を変える ChangeComboBoxListState(); //--- 関連したメッセージを送信する ::EventChartCustom(m_chart_id,ON_CLICK_COMBOBOX_BUTTON,CElement::Id(),0,""); return(true); }
ON_CLICK_COMBOBOX_BUTTON識別子を持ったカスタムイベントの追跡はカレンダーの一部であるコントロールの状態を管理するためにCCalendarクラスで必要になります(CSpinEdit及びCIconButton)(下のコードを参照)。
//+------------------------------------------------------------------+ //| イベントハンドラ | //+------------------------------------------------------------------+ void CCalendar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- コンボボックスボタンのクリックイベントを処理する if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_BUTTON) { //--- コントロールの識別子が一致しない場合は終了する if(lparam!=CElement::Id()) return; //--- 現在のリストの可視性によってコントロールをアクティブ化するかブロックする m_years.SpinEditState(!m_months.GetListViewPointer().IsVisible()); m_button_today.ButtonState(!m_months.GetListViewPointer().IsVisible()); } }
例えばWindows 7の様なオペレーティングシステムのグラフィカルインタフェースでは、左/右矢印ボタンのクリックによって前/翌月へ移動すると、以前にカレンダーのテーブルで選択されていた日に関係なく月の最初の日が選択されます。開発中のライブラリのカレンダーでは同じ動作を実装します。前/翌月に移動するにはCCalendar::OnClickMonthDec() 及びCCalendar::OnClickMonthInc() メソッドが使われます。これらのメソッドのコードは非常に似ています。例としてそれらの1つ、すなわち、前月に移動するためのボタンクリックイベントの処理についての説明を表示します。
ユーザがクリックしたグラフィックオブジェクトの名前はメソッドの開始時に確認されます。その後、カレンダーでの現在の年が指定された最小に等しく(すなわち最小限に達した)現在の月が「1月」である場合、入力フィールドのテキストを一瞬強調表示してメソッドを終了します。
最小限に達していない場合、下記が起こります。
- ボタンの状態をOnに設定する。
- 翌月に移る。
- 月の1番目の数を設定する。
- 時刻をリセットする(00:00:00)。このためにはクラスにCCalendar::ResetTime()メソッドが追加されます。
- 直近の変化を表示するためにカレンダーを更新する。
- (1) チャート識別子 (2) ON_CHANGE_DATEイベント識別子 (3) コントロール識別子 (4) 指定された日付を持ったメッセージを送信する。同じパラメータを持つメッセージが他のカレンダーのイベント処理メソッドから送信される。
Class CCalendar : public CElement { private: //--- 前月に移動するためのボタンクリックを処理する bool OnClickMonthDec(const string clicked_object); //--- 翌月に移動するためのボタンクリックを処理する bool OnClickMonthInc(const string clicked_object); //--- 時刻をリセットする void ResetTime(void); }; //+------------------------------------------------------------------+ //| 左矢印をクリックして前月に移動する | //+------------------------------------------------------------------+ bool CCalendar::OnClickMonthDec(const string clicked_object) { //--- オブジェクト名が異なる場合は終了する | if(::StringFind(clicked_object,m_month_dec.Name(),0)<0) return(false); //--- カレンダーの現在の年が指定された最小値と等しく // 現在の月が1月の場合 if(m_date.year==m_years.MinValue() && m_date.mon==1) { //--- 値を強調表示して終了する m_years.HighlightLimit(); return(true); } //--- 状態をOnに設定する m_month_dec.State(true); //--- 前月に移る m_date.MonDec(); //--- 月の1日を設定する m_date.day=1; //--- 時刻をリセットする ResetTime(); //--- カレンダーでの直近の変化を表示する UpdateCalendar(); //--- 関連したメッセージを送信する ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),""); return(true); } //+------------------------------------------------------------------+ //| 時刻をリセットする | //+------------------------------------------------------------------+ void CCalendar::ResetTime(void) { m_date.hour =0; m_date.min =0; m_date.sec =0; }
CCalendar::OnClickMonthInc() メソッドのコードはほとんど同じです。唯一の違いは、制限をチェックする際に、年間カレンダーのプロパティで設定された最大年の最後の月(12月)が超えられた場合の終了がチェックされていることです。
リストからの月の選択はON_CLICK_COMBOBOX_ITEM識別子を持ったイベントが到着した際にCCalendar::OnClickMonthList() メソッドで処理されます(下のコードを参照)。メインハンドラでは、コントロール識別子を含んだlongパラメータがこのメソッドに送信されます。識別子が一致しない場合、プログラムはメソッドを終了します。リスト内の項目の選択はその閉鎖につながるので、以前にブロックされたカレンダーのコントロール(入力フィールドやボタン)のロックは解除される必要があります。その後、選択された月は日付と時刻の構造体で設定され、必要な場合には、選択された日を月の日数に応じて修正します。後は単に時間を設定して、カレンダーに行われた直近の変更を表示し カレンダー上の日付が変更されたというメッセージをイベントのスレッドに送信するだけです。
Class CCalendar : public CElement { private: //--- リストでの月の選択を処理する bool OnClickMonthList(const long id); }; //+------------------------------------------------------------------+ //| リストでの月の選択を処理する | //+------------------------------------------------------------------+ bool CCalendar::OnClickMonthList(const long id) { //--- コントロールの識別子が一致しない場合は終了する if(id!=CElement::Id()) return(false); //--- コントロールのロックを解除する m_years.SpinEditState(true); m_button_today.ButtonState(true); //--- リストで選択された月を取得する int month=m_months.GetListViewPointer().SelectedItemIndex()+1; m_date.Mon(month); //--- 月の日数によって選択された日を訂正する CorrectingSelectedDay(); //--- 時刻をリセットする ResetTime(); //--- カレンダーテーブルの直近の変化を表示する UpdateCalendar(); //--- 関連したメッセージを送信する ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),""); return(true); }
年の入力フィールドへの値の入力の処理にはCCalendar::OnEndEnterYear() メソッドが使われます。メソッドの先頭で、値が入力されたOBJ_EDIT型のオブジェクトの名前が確認されます。さらに、値が変更されたかどうかを確認するための別のチェックがあります。入力フィールドの名前がこのカレンダー属していない場合や値が変更されていない場合は、プログラムはメソッドを終了します。最初の2つのチェックが通過された場合は、さらに、入力された値が指定された制限を超える場合に修正されます。次に、月の日数がこの月の日数を超えた場合、ユーザの選択した日付が修正されます。その後、日付と時刻の構造体に設定してカレンダーで行った変更を表示し、カレンダー上の日付が変更されたというメッセージを送信します。CCalendar::OnEndEnterYear() メソッドのコードは下記で詳しく研究できます。
Class CCalendar : public CElement { private: //--- 年の入力フィールドへの値の入力を処理する bool OnEndEnterYear(const string edited_object); }; //+------------------------------------------------------------------+ //| 年の入力フィールドへの値の入力を処理する | //+------------------------------------------------------------------+ bool CCalendar::OnEndEnterYear(const string edited_object) { //--- オブジェクト名が異なる場合は終了する | if(::StringFind(edited_object,m_years.Object(2).Name(),0)<0) return(false); //--- 値が変更されていない場合は終了する string value=m_years.Object(2).Description(); if(m_date.year==(int)value) return(false); //--- 指定された制限が超えられた場合は値を修正する if((int)value<m_years.MinValue()) { value=(string)int(m_years.MinValue()); //--- 値を強調表示する m_years.HighlightLimit(); } if((int)value>m_years.MaxValue()) { value=(string)int(m_years.MaxValue()); //--- 値を強調表示する m_years.HighlightLimit(); } //--- 現在の月の日数を定義する string year =value; string month =string(m_date.mon); string day =string(1); m_temp_date.DateTime(::StringToTime(year+"."+month+"."+day)); //--- 選択された日が月の日数を超える場合 // 現在の付きの日数を選択された日として設定する if(m_date.day>m_temp_date.DaysInMonth()) m_date.day=m_temp_date.DaysInMonth(); //--- 構造体で日付を設定する m_date.DateTime(::StringToTime(year+"."+month+"."+string(m_date.day))); //--- カレンダーテーブルで変化を表示する UpdateCalendar(); //--- 関連したメッセージを送信する ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),""); return(true); }
前/翌年に移動するためのボタンクリックを処理するメソッドはCCalendar::OnClickYearInc() と CCalendar::OnClickYearDec()です。制限に達していることを確認するための条件を除いて、コードは実質的に同一であるので、ここではそのうちの1つだけを表示します。初めに、月を選択するためのリストの現在の状態がチェックされます。リストが開いている場合には閉じます。さらに、コントロールの識別子が一致しない場合にはプログラムは終了します。その後でメソッドのメインコンディションが続き、CCalendar::OnClickYearInc()では前向きのステップ、CCalendar::OnClickYearDec()では後ろ向きのステップが取られます。その後、必要に応じて(1)選択した日が月の日数に基づいて補正され(2)最後の変更がカレンダーに表示され(3)カレンダー上の日付が変更されたというメッセージを送信されます。
Class CCalendar : public CElement { private: //--- 翌年に移動するためのボタンクリックを処理する bool OnClickYearInc(const long id); //--- 前年に移動するためのボタンクリックを処理する bool OnClickYearDec(const long id); }; //+------------------------------------------------------------------+ //| 翌年に移動するためのボタンクリックを処理する | //+------------------------------------------------------------------+ bool CCalendar::OnClickYearInc(const long id) { //--- 月のリストが開いている場合には閉じる if(m_months.GetListViewPointer().IsVisible()) m_months.ChangeComboBoxListState(); //--- コントロールの識別子が一致しない場合は終了する if(id!=CElement::Id()) return(false); //---年が指定された最大値以下の場合は1で増加する必要がある if(m_date.year<m_years.MaxValue()) m_date.YearInc(); //--- 月の日数によって選択された日を訂正する CorrectingSelectedDay(); //--- カレンダーテーブルの直近の変化を表示する UpdateCalendar(); //--- 関連したメッセージを送信する ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),""); return(true); }
ここではカレンダーテーブルでの月の日のクリックを処理するCCalendar::OnClickDayOfMonth() メソッドを考察します。
前述したように、カレンダーテーブルは42項目で構成されています。したがって、現在の月の最初の項目の番号によって、時々、テーブルに前月と翌月の日が現れることがあります。ユーザが現在の月に属していない日をクリックした場合には、選択した日の月への切り替えが行われます。
メソッドの先頭では、カレンダーテーブルの項目に属するオブジェクトのチェックが通過される必要があり、コントロール識別子も比較されます。識別子は、以前に他のライブラリのコントロールを通して考慮された CCalendar::IdFromObjectName() メソッドを使用してオブジェクト名から抽出されます。最初の2つのチェックが通過された場合は、 CCalendar::OffsetFirstDayOfMonth() メソッドが使用されて現在の月の最初の日とテーブルの最初の項目との差が決定されます。このメソッドの呼び出しの後はm_temp_dateカウンタの出番で、さらに、プログラムはループ内のすべての項目を反復処理してユーザがクリックした項目を検索します。
それでは、このループの背後にある論理を理解してみましょう。最初の項目の日付が最小値としてシステムに設定された年(1970年)以前の年を参照することが可能です。この場合、指定した制限をユーザが通過することは禁止されているため、年の入力フィールドの値を強調表示してすぐに次の項目に進みます。項目が使用可能な領域にあってクリックされた場合には、メソッドの終わりで(1)日付が構造体(m_date)に格納され(2)カレンダーが最新の変更内容を表示し(3)ループが中断され(4)カレンダー上の日付が変更されたというメッセージを送信されます 。
ループの最初の2つの条件のいずれもが満たされない場合は、計算のための構造体カウンタ(m_temp_date)が1日で増加され、カレンダーシステムに設定された最大値が超えられた場合の終了に対するチェックが続きます。最大値に達していない場合、プログラムは次の項目に移動します。最大値が達すると、強調表示された値が年の入力フィールドで呼び出され、プログラムはメソッドを終了します。
Class CCalendar : public CElement { private: //--- 月の日のクリックを処理する bool OnClickDayOfMonth(const string clicked_object); }; //+------------------------------------------------------------------+ //| 月の日のクリックを処理する | //+------------------------------------------------------------------+ bool CCalendar::OnClickDayOfMonth(const string clicked_object) { //--- クリックされたのがカレンダー上の日でなかった場合は終了する if(::StringFind(clicked_object,CElement::ProgramName()+"_calendar_day_",0)<0) return(false); //--- オブジェクト名から識別子とインデックスを取得する int id=IdFromObjectName(clicked_object); //--- 識別子が一致しない場合は終了する if(id!=CElement::Id()) return(false); //--- テーブルの1番目の項目から現在の月の1日までの違いを計算する OffsetFirstDayOfMonth(); //--- テーブルの項目を反復処理する for(int i=0; i<42; i++) { //--- 現在の項目の日付がシステムで設定された最小値よりも低い場合 if(m_temp_date.DateTime()<datetime(D'01.01.1970')) { //--- これがクリックしたオブジェクトの場合 if(m_days[i].Name()==clicked_object) { //--- 値を強調表示して終了する m_years.HighlightLimit(); return(false); } //--- 翌日に移る m_temp_date.DayInc(); continue; } //--- これがクリックしたオブジェクトの場合 if(m_days[i].Name()==clicked_object) { //--- 日付を格納する m_date.DateTime(m_temp_date.DateTime()); //--- カレンダーでの直近の変化を表示する UpdateCalendar(); break; } //--- 翌日に移る m_temp_date.DayInc(); //--- システムで設定された最大値を超える場合の終了をチェック if(m_temp_date.year>m_years.MaxValue()) { //--- 値を強調表示して終了する m_years.HighlightLimit(); return(false); } } //--- 関連したメッセージを送信する ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),""); return(true); }
残るのは、現在の日付へ移動するボタンのクリックを処理するカレンダーイベントの最後のハンドラメソッドであるCCalendar::OnClickTodayButton() の考察です。long パラメータ(コントロール識別子)がイベントのメインハンドラから送信されます。メソッドの先頭では、リストが現在開いている場合には閉じられます。その後、識別子が比較されます。識別子が一致した場合は、構造体に(ユーザのPC上の)ローカル日付と時刻を設定します。その後、カレンダーが更新されて最新の変更が表示され、メソッドの終わりでカレンダー上の日付が変更されたというメッセージをイベントのスレッドに送信されます。
Class CCalendar : public CElement { private: //--- 現在の日付けに移るためのクリックを処理する bool OnClickTodayButton(const long id); }; //+------------------------------------------------------------------+ //| 現在の日付けに移るためのクリックを処理する | //+------------------------------------------------------------------+ bool CCalendar::OnClickTodayButton(const long id) { //--- 月のリストが開いている場合には閉じる if(m_months.GetListViewPointer().IsVisible()) m_months.ChangeComboBoxListState(); //--- コントロールの識別子が一致しない場合は終了する if(id!=CElement::Id()) return(false); //--- 現在の日付けを設定する m_date.DateTime(::TimeLocal()); //--- カレンダーでの直近の変化を表示する UpdateCalendar(); //--- 関連したメッセージを送信する ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),""); return(true); }
カレンダーイベントのメインハンドラであるCCalendar::OnEvent()には8つのコードブロックがあります(1つは既にこのセクションの冒頭で提供されました)。次がその一覧です。
- マウスカーソル移動イベント(CHARTEVENT_MOUSE_MOVE)の処理。マウスカーソルの追跡はコントロールが隠されていない場合とドロップダウンコントロールとフォームがブロックされていない場合にのみ実行されます。<プログラムは、月のリストがアクティブ(オープン)であっても、このブロックから出ます。リストが隠されていてマウスの左ボタンがクリックされると、それらのうちの少なくとも1つがまだブロックされている場合は、以前に(リストを開いた瞬間に)ブロックされたカレンダーのコントロールがアクティブにされます。最終的には、テーブル項目の色を変えるためにマウスカーソルの座標がCCalendar::ChangeObjectsColor() メソッドに送られます。
//--- マウスカーソル移動イベントを処理する if(id==CHARTEVENT_MOUSE_MOVE) { //--- コントロールが隠れている場合は終了する if(!CElement::IsVisible()) return; //--- これがドロップダウン要素ではなくフォームがブロックされている場合には終了する if(!CElement::IsDropdown() && m_wnd.IsLocked()) return; //--- マウスの左クリックの座標と状態 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()); //--- 月のリストがアクティブな場合は終了する if(m_months.GetListViewPointer().IsVisible()) return; //--- リストがアクティブでなくマウスの左ボタンがクリックされた場合 else if(m_mouse_state) { //--- ...以前に(リストを開いた瞬間に)ブロックされたコントロールをアクティブにする // もしいずれかがブロックされていない場合 if(!m_button_today.ButtonState()) { m_years.SpinEditState(true); m_button_today.ButtonState(true); } } //--- オブジェクトの色の変更 ChangeObjectsColor(x,y); return; }
- オブジェクトの左マウスクリックイベント(CHARTEVENT_OBJECT_CLICK)の処理。これは、カレンダーの任意のオブジェクトがクリックされた後に発生します。その後、フラグがマウスの左クリックボタンに設定されている場合、カレンダーコントロール(入力フィールドとボタン)がアクティブにされます。これは、コントロールがクリックされると更にチェックされます。このために、前述されたCCalendar::OnClickMonthDec()、CCalendar::OnClickMonthInc() 及び CCalendar::OnClickDayOfMonth()メソッドを使います。
//--- オブジェクトの左マウスクリックイベントを処理する if(id==CHARTEVENT_OBJECT_CLICK) { //--- フォームがブロックされていて識別子が一致しない場合は終了する if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id()) return; //--- 月のリストがアクティブな場合は終了する if(m_months.GetListViewPointer().IsVisible()) return; //--- 左マウスボタンがクリックされた場合にコントロール(リストと入力フィールド)をアクティブにする if(m_mouse_state) { m_years.SpinEditState(true); m_button_today.ButtonState(true); } //--- 月間を移動するためのボタンクリックを処理する if(OnClickMonthDec(sparam)) return; if(OnClickMonthInc(sparam)) return; //--- カレンダーの日付けのクリックを処理する if(OnClickDayOfMonth(sparam)) return; }
- コンボボックスボリスト項目のクリックイベント(ON_CLICK_COMBOBOX_ITEM)の処理。このイベントの処理にはCCalendar::OnClickMonthList() メソッドが使われます。
//--- コンボボックスボリスト項目のクリックイベントを処理する if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM) { //--- リストでの月の選択を処理する if(!OnClickMonthList(lparam)) return; //--- return; }
- インクリメント/デクリメントのボタンクリックのイベント(ON_CLICK_INC/ON_CLICK_DEC)の処理。これらのイベントの処理にはCCalendar::OnClickYearInc()及びCCalendar::OnClickYearDec()メソッドを呼び出すための2つの別々なコードブロックが存在します。
//--- インクリメントボタンクリックのイベントを処理する if(id==CHARTEVENT_CUSTOM+ON_CLICK_INC) { //--- 翌年に移動するためのボタンクリックを処理する if(!OnClickYearInc(lparam)) return; //--- return; } //--- デクリメントボタンクリックのイベントを処理する if(id==CHARTEVENT_CUSTOM+ON_CLICK_DEC) { //--- 前年に移動するためのボタンクリックを処理する if(!OnClickYearDec(lparam)) return; //--- return; }
- 入力フィールドへの値の入力イベント(CHARTEVENT_OBJECT_ENDEDIT)の処理。年の入力フィールドに値を入力するとプログラムはCCalendar::OnEndEnterYear() メソッドを呼び出すためのコードブロックに進みます。
//--- 入力フィールドへのの値の入力イベントを処理する if(id==CHARTEVENT_OBJECT_ENDEDIT) { //--- 年の入力フィールドへの値の入力を処理する if(OnEndEnterYear(sparam)) return; //--- return; }
- ボタンクリックイベント(ON_CLICK_BUTTON)の処理。このコードブロックでは、次の日付(ユーザPCのローカル時刻)に移動するボタンのクリックがCCalendar::OnClickTodayButton()メソッドで処理されます。
//--- ボタンクリックイベントを処理する if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { //--- 現在の日付けに移るためのクリックを処理する if(!OnClickTodayButton(lparam)) return; //--- return; }
カレンダーなどの複雑なコントロールをより便利に利用するために、その値をすばやく切り替える機能を追加します。似たようなことは、すでにリストのクラス(CListView)と入力フィールドのクラス(CSpinEdit)で実装されました。入力フィールドの値を高速で切り替えたときに発生するすべての変更に必要な更新を取得するために、これをカレンダーテーブルに接続する必要があります。また、月を切り替えるボタンを使用してカレンダーを高速で切り替えるオプションを追加します。
動作ブロックに入るための条件は既に他の記事で検討されているので、ここではメソッドの主要な(運用)部分を説明します。条件がそろった場合、どちらのカレンダーコントロールのボタンが現在クリックされているかを確認しなければなりません(黄色で強調表示された行を参照)。記載されているボタンのどれもが現在押下されていない場合、プログラムはメソッドを終了します。クリックされたボタンが検出された後、値がカレンダーに設定された制限を超えたかどうかを決定するためにいくつかのチェックが行われなければなりません。制限が侵された場合入力フィールドのテキストが強調表示されてプログラムがメソッドを終了します。制限内の場合カレンダーが最新の変更を表示するように更新され、日付変更を知らせるメッセージが送信されます。
Class CCalendar : public CElement { private: //--- カレンダー値の高速切り替え void FastSwitching(void); }; //+------------------------------------------------------------------+ //| カレンダーの高速切り替え | //+------------------------------------------------------------------+ void CCalendar::FastSwitching(void) { //--- コントロールに焦点が当てられていない場合には終了する if(!CElement::MouseFocus()) return; //--- フォームがブロックされていて識別子が一致しない場合は終了する if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id()) return; //--- マウスボタンが離された場合はカウンターを初期値に戻す if(!m_mouse_state) m_timer_counter=SPIN_DELAY_MSC; //--- マウスボタンがクリックされた場合 else { //--- 指定された間隔でカウンタを増加する m_timer_counter+=TIMER_STEP_MSC; //--- 負の場合は終了する if(m_timer_counter<0) return; //--- 左矢印がクリックされた場合 if(m_month_dec.State()) { //--- カレンダーの現在の年が指定された最小値以上の場合 if(m_date.year>=m_years.MinValue()) { //--- カレンダーの現在の年が指定された最小値で // 現在の月が1月の場合 if(m_date.year==m_years.MinValue() && m_date.mon==1) { //--- 値を強調表示して終了する m_years.HighlightLimit(); return; } //--- 次の月に移る(下向き) m_date.MonDec(); //--- 月の1日を設定する m_date.day=1; } } //--- 右矢印がクリックされた場合 else if(m_month_inc.State()) { //--- カレンダーの現在の年が指定された最大値以下の場合 if(m_date.year<=m_years.MaxValue()) { //--- カレンダーの現在の年が指定された最大値で // 現在の月が12月の場合 if(m_date.year==m_years.MaxValue() && m_date.mon==12) { //--- 値を強調表示して終了する m_years.HighlightLimit(); return; } //--- 次の月に移る(上向き) m_date.MonInc(); //--- 月の1日を設定する m_date.day=1; } } //--- 年入力フィールドのインクレメントボタンがクリックされた場合 else if(m_years.StateInc()) { //--- 指定された最大年未満の場合 // 次の月に移る(上向き) if(m_date.year<m_years.MaxValue()) m_date.YearInc(); else { //--- 値を強調表示して終了する m_years.HighlightLimit(); return; } } //--- 年入力フィールドのデクレメントボタンがクリックされた場合 else if(m_years.StateDec()) { //--- 指定された最小年以上の場合 // 次の月に移る(下向き) if(m_date.year>m_years.MinValue()) m_date.YearDec(); else { //--- 値を強調表示して終了する m_years.HighlightLimit(); return; } } else return; //--- カレンダーでの直近の変化を表示する UpdateCalendar(); //--- 関連したメッセージを送信する ::EventChartCustom(m_chart_id,ON_CHANGE_DATE,CElement::Id(),m_date.DateTime(),""); } }
日が変わる場合、カレンダーで現在の日(ユーザPC上のローカル時刻)を更新する必要があります。また、現在の日付に移動するためのボタン上の表示の日付も更新される必要があります。このためには特別なCCalendar::UpdateCurrentDate() メソッドを書きます。このメソッドは新しい日への変更を毎秒追跡します(以下のコードを参照)。
Class CCalendar : public CElement { public: //--- 現在の日付けを更新する void UpdateCurrentDate(void); }; //+------------------------------------------------------------------+ //| 現在の日付けを更新する | //+------------------------------------------------------------------+ void CCalendar::UpdateCurrentDate(void) { //--- カウンタ static int count=0; //--- 1秒未満の場合は終了する if(count<1000) { count+=TIMER_STEP_MSC; return; } //--- カウンタをゼロにする count=0; //--- 現在の(ローカル)時刻を取得する MqlDateTime local_time; ::TimeToStruct(::TimeLocal(),local_time); //--- 新しい日が始まった場合 if(local_time.day!=m_today.day) { //--- カレンダーで日付を更新する m_today.DateTime(::TimeLocal()); m_button_today.Object(2).Description(::TimeToString(m_today.DateTime())); //--- カレンダーでの直近の変化を表示する UpdateCalendar(); return; } //--- カレンダーで日付を更新する m_today.DateTime(::TimeLocal()); }
カレンダーコントロールのタイマーコードは、この場合次のようになります。
//+------------------------------------------------------------------+ //| タイマー | //+------------------------------------------------------------------+ void CCalendar::OnEventTimer(void) { //--- これがドロップダウン要素でリストが非表示な場合 if(CElement::IsDropdown() && !m_months.GetListViewPointer().IsVisible()) { ChangeObjectsColor(); FastSwitching(); } else { //--- 色と高速スイッチング値の変化を追跡する // (フォームがブロックされていない場合のみ) if(!m_wnd.IsLocked()) { ChangeObjectsColor(); FastSwitching(); } } //--- カレンダーで現在の日付を更新する UpdateCurrentDate(); }
CCalendar クラスのメソッドはすべて考察されました。ここでその動作を検証しましょう。しかし、その前には、正しい動作のためにライブラリのエンジンとコントロールを接続する必要があります。詳しいステップごとのガイドはグラフィカルインタフェース V:コンボボックス要素(チャプター 3)稿で参照できます。したがって、ここではコントロールの個々の配列の構造体の配列の(CWndContainer クラスのボディでの)宣言と(1)MQLアプリケーションのグラフィカルインターフェースをカレンダーの数を受信するためのメソッド(2)グラフィカルインターフェースの作成時にコントロールの指標をベースにを追加する際に保存するためのメソッドのみを提供します。コードはこの記事に添付されたファイルで参照できます。
//+------------------------------------------------------------------+ //| インターフェースオブジェクト格納クラス | //+------------------------------------------------------------------+ class CWndContainer { protected: //--- コントロール配列の構造体 struct WindowElements { //--- コントロールの配列 //--- カレンダーの配列 CCalendar *m_calendars[]; //--- public: //--- カレンダーの数 int CalendarsTotal(const int window_index); //--- private: //--- べースにカレンダーコントロールへのポインタを格納する bool AddCalendarElements(const int window_index,CElement &object); };
カレンダーコントロールの検証
検証目的のためには、前回の任意の記事からのエキスパートアドバイザーを使用することができます。フォームに取り付けられたグラフィカルインターフェースのコントロールのうちでは、メインメニューと状態を示す文字列のみを保持します。動作中にそれらの間に競合があるかどうかを分析するために、2つのカレンダーを作成します。
アプリケーションのカスタムクラス(CProgram)でCCalendar クラスの2つのインスタンスと、フォームの端の点からのオフセットでカレンダーコントロールを作成するメソッドを宣言します。
class CProgram : public CWndEvents { private: //--- カレンダー CCalendar m_calendar1; CCalendar m_calendar2; //--- private: //--- カレンダー #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); };
例として、これらのメソッドのうちの1つのコードを提供します。カレンダープロパティはデフォルトを保持します(下記のコードを参照)。
//+------------------------------------------------------------------+ //| カレンダー1を作成する | //+------------------------------------------------------------------+ bool CProgram::CreateCalendar1(void) { //--- オブジェクトをパネルに渡す m_calendar1.WindowPointer(m_window1); //--- 座標 int x=m_window1.X()+CALENDAR1_GAP_X; int y=m_window1.Y()+CALENDAR1_GAP_Y; //--- コントロールを作成する if(!m_calendar1.CreateCalendar(m_chart_id,m_subwin,x,y)) return(false); //--- オブジェクトをオブジェクトグループの共通配列に追加する CWndContainer::AddToElementsArray(0,m_calendar1); return(true); }
これらのメソッドの呼び出しを MQLアプリケーションのグラフィカルインターフェースを作成するメインメソッドに配置し、コンパイルしてチャートにアップロードします。最終的には、次の結果が受け取られるはずです。
図2 カレンダーコントロールの検証
カレンダーコントロールを作成するためのCCalendarクラスの開発はこの時点で完了です。将来的にはその機能が拡張され、本稿では、このコントロールの2番目のバージョンであるドロップダウンカレンダーを考察します。
ドロップダウンカレンダー
静的なカレンダー(CCalendar)とは異なり、ドロップダウンカレンダーのコントロールはほとんどの時に最小化されているためにアプリケーションのグラフィカルインターフェースのスペースを大幅に節約することができます。ユーザによって選択された日付はコンボボックスのテキストフィールドに表示されます。別の日付を選択するには、コンボボックスのボタンをクリックする必要があります。このアクションによってカレンダーが呼び出されます(表示されます)。もう一度ボタンをクリックすると、カレンダーが開かれます。カレンダーは、マウスの左ボタンがコントロールの領域外にクリックされたときに最小化されます。さもないとチャートの質が変化します。
図3 ドロップダウンカレンダーのコンポーネント
このコントロールの構造体はさらに分析されます。
CDropCalendarクラス
コンボボックスコントロールは、以前にグラフィカルインタフェース V:コンボボックス要素(チャプター 3)とグラフィカルインタフェースVI:チェックボックスコントロール、編集コントロールとその混合型(チャプター 1)で、リスト付きのコンボボックス(CComboBox)やリストとチェックボックス付きのコンボボックス(CCheckComboBox)といった形で説明されました。したがって、ここでは単にドロップダウンカレンダーコントロールの重要な側面に触れるだけにします。
DropCalendar.mqhファイルを作成してライブラリ( WndContainer.mqhファイル)に含みます。
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "DropCalendar.mqh"
DropCalendar.mqhファイルですべてのライブラリコントロールが持つ標準的なメソッドを持つクラスを作成しファイルをCalendar.mqhカレンダークラスと接続します。
//+------------------------------------------------------------------+ //| DropCalendar.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" #include "Calendar.mqh"
コントロールの作成に必要なメソッドの一覧に今すぐ進みます。6つのプライベートメソッドと1つのパブリックメソッドが必要です。
//+------------------------------------------------------------------+ //| ドロップダウンカレンダー作成クラス | //+------------------------------------------------------------------+ class CDropCalendar : public CElement { private: //--- コントロール作成のためのオブジェクトとコントロール CRectLabel m_area; CLabel m_label; CEdit m_field; CEdit m_drop_button; CBmpLabel m_drop_button_icon; CCalendar m_calendar; //--- public: //--- ドロップダウンカレンダー作成メソッド 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); };
カレンダーの作成中にはそれがドロップダウンカレンダーコントロールであることを示さなければならないことにご注意ください。CDropCalendar::CreateDropCalendar()メイン(パブリック)メソッドでコントロールが含むすべてのオブジェクトを作成した後には、下記が必要です。
- カレンダーを非表示にする
- コンボボックスの情報フィールドで作成された後 CCalendar::SelectedDate() メソッドで取得可能な、カレンダーにすでに存在する選択された日を設定する
CDropCalendar::CreateDropCalendar() メソッドの短縮版は以下のコードに示されています。完全なコードは本稿添付のファイルで確認可能です。
//+------------------------------------------------------------------+ //| ドロップダウンカレンダー1を作成する | //+------------------------------------------------------------------+ bool CDropCalendar::CreateDropCalendar(const long chart_id,const int subwin,const string text,const int x,const int y) { //--- フォームへのポインタがない場合終了する //--- 変数の初期化 //--- 端点からのオフセット //--- コントロールを作成する //--- カレンダーを非表示にする m_calendar.Hide(); //--- カレンダーで選択された日付を表示する m_field.Description(::TimeToString((datetime)m_calendar.SelectedDate(),TIME_DATE)); //--- ダイアログウィンドウか最小化されたウィンドウのコントロールを非表示にする if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized()) Hide(); //--- return(true); } //+------------------------------------------------------------------+ //| リストを作成する | //+------------------------------------------------------------------+ bool CDropCalendar::CreateCalendar(void) { //--- オブジェクトをパネルに渡す m_calendar.WindowPointer(m_wnd); //--- 座標 int x=m_field.X(); int y=m_field.Y2(); //--- カレンダーのドロップダウンコントロールだと示す m_calendar.IsDropdown(true); //--- コントロールを作成する if(!m_calendar.CreateCalendar(m_chart_id,m_subwin,x,y)) return(false); //--- return(true); }
コンボボックスの動作には以下のメソッドが必要です。
- カレンダーの可視性の反対の状態への変更 — CDropCalendar::ChangeComboBoxCalendarState()
- コンボボックスボタンのクリックの処理 — CDropCalendar::OnClickButton()
- コンボボックスボタンで押下された左マウスボタンをチェック — CDropCalendar::CheckPressedOverButton()
これらのメソッドはすべてCComboBoxクラスで考察されたものと似ているので、ここではコードを省きます。
カレンダーのドロップダウンのイベントのハンドラは、以下のようになります。次のイベントを処理するための4つのブロックがあります。
- マウスカーソルの移動 — CHARTEVENT_MOUSE_MOVE
- カレンダーでの新しい日付の選択 — ON_CHANGE_DATE
- グラフィックオブジェクトでのマウスの左ボタンのクリック — CHARTEVENT_OBJECT_CLICK
- チャートプロパティの変更 — CHARTEVENT_CHART_CHANGE
CCalendarクラスで生成されたON_CHANGE_DATEイベントが到着した場合、コントロールの識別子を比較しコンボボックスフィールドで新しい日付を設定する必要があります。
//+------------------------------------------------------------------+ //| イベントハンドラ | //+------------------------------------------------------------------+ void CDropCalendar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- マウスカーソル移動イベントを処理する if(id==CHARTEVENT_MOUSE_MOVE) { //--- コントロールが隠れている場合は終了する if(!CElement::IsVisible()) return; //--- マウスの左クリックの座標と状態 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()); //--- コンボボックスボタンでクリックされた左マウスボタンをチェックする CheckPressedOverButton(); return; } //--- カレンダーでの新しい日付の選択のイベントを処理する if(id==CHARTEVENT_CUSTOM+ON_CHANGE_DATE) { //--- コントロールの識別子が一致しない場合は終了する if(lparam!=CElement::Id()) return; //--- コンボボックスフィールドで新しい日付を設定する m_field.Description(::TimeToString((datetime)dparam,TIME_DATE)); return; } //--- オブジェクトの左マウスクリックイベントを処理する if(id==CHARTEVENT_OBJECT_CLICK) { //--- コンボボックスボタンをクリックする if(OnClickButton(sparam)) return; //--- return; } //--- チャートプロパティ変更イベントを処理する if(id==CHARTEVENT_CHART_CHANGE) { //--- コントロールがブロックされている場合は終了する if(!m_drop_calendar_state) return; //--- カレンダーを非表示にする m_calendar.Hide(); m_drop_button_icon.State(false); //--- 色をリセットする ResetColors(); return; } }
このコントロールを正しく動作させるためには CCalendarクラス同様にCWndContainerライブラリの基本クラスに追加が必要です。
//+------------------------------------------------------------------+ //| インターフェースオブジェクト格納クラス | //+------------------------------------------------------------------+ class CWndContainer { protected: //--- コントロール配列の構造体 struct WindowElements { //--- コントロールの配列 //--- ドロップダウンカレンダーの配列 CDropCalendar *m_drop_calendars[]; //--- public: //--- ドロップダウンカレンダーの合計 int DropCalendarsTotal(const int window_index); //--- private: //--- べースにドロップダウンカレンダーコントロールへのポインタを格納する bool AddDropCalendarElements(const int window_index,CElement &object); };
さらに、 チャートの状態をコントロールするCWndEvents::SetChartState() メソッドに、すべてのコントロールのイベントを処理するライブラリのメインクラスに ドロップダウンカレンダーをチェックするのに使われるコードブロックを加えなければなりません(以下のメソッド短縮版を参照)。
//+------------------------------------------------------------------+ //| チャートの状態を設定する | //+------------------------------------------------------------------+ void CWndEvents::SetChartState(void) { int awi=m_active_window_index; //--- 管理を無効にするためのイベントを特定する必要がある場合 bool condition=false; //--- ウィンドウを確認する //--- ドロップダウンリストを確認する //--- カレンダーを確認する 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; } } } //--- コンテキストメニューのフォーカスを確認する //--- スクロールバーの状態を確認する //--- すべてのフォームでチャートの状態を設定する for(int i=0; i<windows_total; i++) m_windows[i].CustomEventChartState(condition); }
ドロップダウンカレンダーコントロールの検証
以前本稿でエキスパートアドバイザーのグラフィカルインターフェイスで検証された2つのドロップダウンカレンダーも追加します。アプリケーションのカスタムクラスでCDropCalendar 型のドロップダウンカレンダーの2つのインスタンスと、フォームの端の点からのオフセットを宣言します。
class CProgram : public CWndEvents { private: //--- ドロップダウンカレンダー CDropCalendar m_drop_calendar1; CDropCalendar m_drop_calendar2; //--- private: //--- ドロップダウンカレンダー #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); };
例として、これらのメソッドのうちの1つのコードを表示します。
//+------------------------------------------------------------------+ //| ドロップダウンカレンダー1を作成する | //+------------------------------------------------------------------+ bool CProgram::CreateDropCalendar1(const string text) { //--- オブジェクトをパネルに渡す m_drop_calendar1.WindowPointer(m_window1); //--- 座標 int x=m_window1.X()+DROPCALENDAR1_GAP_X; int y=m_window1.Y()+DROPCALENDAR1_GAP_Y; //--- 作成前にプロパティを設定する m_drop_calendar1.XSize(145); m_drop_calendar1.YSize(20); m_drop_calendar1.AreaBackColor(clrWhiteSmoke); //--- コントロールを作成する if(!m_drop_calendar1.CreateDropCalendar(m_chart_id,m_subwin,text,x,y)) return(false); //--- ベースにコントロールポインタを追加する CWndContainer::AddToElementsArray(0,m_drop_calendar1); return(true); }
コントロールを作成するためのメソッドの呼び出しは、グラフィカルインターフェースを作成するためのメインメソッドに配置されます。この場合それはCProgram::CreateExpertPanel() メソッドです。CProgram::OnEvent() メソッドでのカレンダーイベントの処理の例には、下にあるようなコードを追加します。カレンダーの日付を変更するたびに、このイベントに含まれるパラメータの値を持つメッセージがログに表示されます。
//+------------------------------------------------------------------+ //| イベントハンドラ | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- カレンダーで日付を変更するイベント if(id==CHARTEVENT_CUSTOM+ON_CHANGE_DATE) { ::Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",datetime(dparam),"; sparam: ",sparam); } }
アプリケーションコードをコンパイルしてターミナルのチャートにアップロードします。結果は以下のスクリーンショットに見られます。
図4 ドロップダウンコントロールの検証
おわりに
本稿(第八部の最初の章)では、カレンダーグラフィカルインタフェースの比較的複雑な統合コントロールとその拡張版であるドロップダウンカレンダーを考察してきました。このようなコントロールは時間軸を持つチャートに非常に適しています。これは最終版ではなく、カレンダーをより便利かつ機能的にすることを目的とする更新が将来的に計画されています。コメントでアイデアを提案なさることもできます。
次回の記事では、ツリービューや階層リストなどのグラフィカルインターフェースの重要なコントロールを分析します。
第八部の資料はダウンロードされ、動作の検証が可能です。これらのファイルに含まれている資料の使用についてご質問がある場合は、以下のリストにある記事のいずれかでライブラリの開発の詳細をご参照になるるか、本稿へのコメント欄でご質問ください。
第八部の記事(チャプター)のリストは下記の通りです。
- グラフィカルインタフェースVIII:カレンダーコントロール(チャプター1)
- グラフィカルインタフェースVIII: ツリービューコントロール(チャプター2)
- グラフィカルインタフェースVIII: ファイルナビゲータコントロール(チャプター3)
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/2537





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索