はじめに
この記事では、Windows Forms の MonthCalendar コントロールで頻出する2つのイベント DateChanged と DateSelected の違いを、最短で・確実に理解します。両者の発火タイミングと使い分けを明確にし、現場で迷わない実装パターンをサンプルコードとともに解説します。
説明
DateChanged は「選択範囲が変わったら毎回発火(ユーザー操作・コード変更どちらでも)」、DateSelected は「ユーザーが選択操作を確定したときに発火(コード変更では発火しない)」という性質です。
・DateChanged
— 発火契機:SelectionStart / SelectionEnd(=選択範囲)が変化した瞬間。
— 起点:ユーザー操作(クリック・ドラッグ・矢印/PageUp/Down 等のキー操作)またはプログラムからの代入(例:monthCalendar.SelectionRange = ...)。
— 連続発火:ドラッグ中など、変化のたびに繰り返し発火します。
・DateSelected
— 発火契機:ユーザーが「選択を確定」したタイミング(マウスボタンを離した時など)。
— 起点:ユーザー操作のみ。コードから選択範囲を変えても発火しません。
— 連続発火:通常は確定の一度だけ(ドラッグ完了時など)。
つまり、「変化をすべて捉えたい」のが DateChanged、
「ユーザー確定の瞬間だけ欲しい」のが DateSelected です。用途に応じて最適なイベントを選ぶと、無駄な処理やバグ(例えば保存が複数回走る等)を防げます。
サンプルコード
以下は、2つのイベントの違いと発火順序が一目で分かる最小サンプルです。
フォーム上に MonthCalendar、ListBox、そして「コードで今日に変更」ボタンを配置し、イベントログで挙動を観察します。
using System;
using System.Drawing;
using System.Windows.Forms;
namespace MonthCalendarSample
{
public class MainForm : Form
{
private MonthCalendar monthCalendar;
private ListBox listBoxLog;
private Button btnSetTodayByCode;
public MainForm()
{
Text = "MonthCalendar: DateChanged vs DateSelected";
Width = 640;
Height = 420;
monthCalendar = new MonthCalendar
{
MaxSelectionCount = 31,
Location = new Point(20, 20)
};
listBoxLog = new ListBox
{
Location = new Point(300, 20),
Width = 300,
Height = 300
};
btnSetTodayByCode = new Button
{
Text = "コードで今日に変更",
Location = new Point(20, 200),
Width = 200
};
// イベント購読
monthCalendar.DateChanged += MonthCalendar_DateChanged;
monthCalendar.DateSelected += MonthCalendar_DateSelected;
// コードから選択変更(DateChanged は発火、DateSelected は発火しない)
btnSetTodayByCode.Click += (s, e) =>
{
var today = DateTime.Today;
monthCalendar.SelectionStart = today;
monthCalendar.SelectionEnd = today;
Log($"[CODE] Selection = {today:yyyy-MM-dd}");
};
Controls.Add(monthCalendar);
Controls.Add(listBoxLog);
Controls.Add(btnSetTodayByCode);
}
private void MonthCalendar_DateChanged(object sender, DateRangeEventArgs e)
{
// 変化のたびに発火(ユーザー & コード両方)
Log($"DateChanged Start={e.Start:yyyy-MM-dd}, End={e.End:yyyy-MM-dd}");
}
private void MonthCalendar_DateSelected(object sender, DateRangeEventArgs e)
{
// ユーザーが選択確定した時だけ発火(コード変更では発火しない)
Log($"DateSelected Start={e.Start:yyyy-MM-dd}, End={e.End:yyyy-MM-dd}");
}
private void Log(string message)
{
// ログを上に積む
listBoxLog.Items.Insert(0, $"{DateTime.Now:HH:mm:ss.fff} {message}");
}
[STAThread]
public static void Main()
{
Application.EnableVisualStyles();
Application.Run(new MainForm());
}
}
}
動かし方と観察ポイント:
1) カレンダー上で日付をクリック:
— 通常、DateChanged → DateSelected の順でログされます。
2) ドラッグして複数日選択:
— ドラッグ中は DateChanged が連続で走り、マウスボタンを離した時に DateSelected が1回走ります。
3) 「コードで今日に変更」を押す:
— DateChanged のみがログされ、DateSelected は出ません(=ユーザー確定操作ではないため)。
4) キーボード操作(矢印キー、PageUp/Down など):
— 範囲が変わる都度 DateChanged が走り、確定操作時に DateSelected が走ります。
実践での使い分けテンプレ:
・入力中も都度プレビュー更新したい(費用概算、仮検索など) ⇒ DateChanged で軽い処理。
・保存・検索実行などの重い処理は確定後だけ ⇒ DateSelected。
・プログラムで日付を変更した際に UI を同期したい ⇒ DateChanged をトリガーにする(DateSelected は反応しません)。
つまづきポイント
1) 「コードで変えたのに DateSelected が動かない」
仕様です。DateSelected はユーザー確定操作のイベント。コード変更に反応させたいときは DateChanged を使いましょう。
2) ドラッグ中に処理が何度も走って重い
DateChanged は連続発火します。重い処理は DateSelected に寄せるか、デバウンス(一定時間入力が止まってから実行)などで対策を。
3) 月送りで意図せず発火する
表示月の変更に伴い選択範囲が変化すると DateChanged が発火します。
「単に月を見ただけでは処理したくない」なら、ユーザー確定操作のみを受ける DateSelected を使用。
4) 「Value が見つからない」
MonthCalendar には Value プロパティはありません。SelectionStart / SelectionEnd(または SelectionRange)を使います。
5) 複数日選択と MaxSelectionCount
複数日を許可する場合は MaxSelectionCount を忘れずに。想定より広い範囲が選べてしまい、処理が重くなることがあります。
まとめ
DateChanged は「選択範囲が動いたら毎回」、DateSelected は「ユーザーが確定したら一度」。この違いを押さえれば、入力中プレビューと確定後の重い処理をきれいに分離できます。
サンプルをベースに、あなたのアプリで「軽いのは DateChanged、重いのは DateSelected」の設計を徹底しましょう。

コメント