はじめに
Windows Forms 標準のカレンダーコントロールは、リサイズができないなど、小回りが利きません。そこで今回はカレンダーコントロールを自作してみました。
改訂履歴
- 2026/07/03 : 初版公開。
本文
1. 環境
- Visual Studio Community 2022
- .NET 8.0
2. 実装
見た目はこんな感じになりました。
実装はユーザーコントロールでシンプルに作っています。デザイナーでポトペタはせず、全てコードで書いています。
MyMonthCalendar.cs
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace WindowsFormsMonthCalendarExample;
/// <summary>
/// 月カレンダーを表示します。
/// </summary>
[ClassInterface(ClassInterfaceType.AutoDispatch)]
[ComVisible(true)]
[DesignerCategory("UserControl")]
public class MyMonthCalendar : UserControl
{
private static readonly int s_dayButtonCount = 42;
private static readonly int s_saturdayIndex = 6;
private static readonly int s_sundayIndex = 0;
private static readonly string[] s_weekNames = { "日", "月", "火", "水", "木", "金", "土" };
private readonly Button _prevButton = new();
private readonly Button _nextButton = new();
private readonly Button[] _dayButtons = new Button[s_dayButtonCount];
private readonly Label _titleLabel = new();
private readonly Label[] _weekLabels = new Label[s_weekNames.Length];
private readonly Panel _headerPanel = new();
private readonly TableLayoutPanel _calendarTable = new();
private DateTime _displayMonth = new(DateTime.Today.Year, DateTime.Today.Month, 1);
private DateTime? _selectedDate = null;
/// <summary>
/// 表示中の年月を取得または設定します。
/// </summary>
[Browsable(true)]
public DateTime DisplayMonth
{
get => _displayMonth;
set
{
value = new DateTime(value.Year, value.Month, 1);
if (_displayMonth == value)
{
return;
}
else
{
_displayMonth = value;
}
DrawCalendar();
}
}
/// <summary>
/// 選択中の日付を取得または設定します。
/// </summary>
public DateTime? SelectedDate
{
get => _selectedDate;
private set
{
if (_selectedDate == value)
{
return;
}
else
{
_selectedDate = value;
DrawCalendar();
}
}
}
/// <summary>
/// <see cref="MyMonthCalendar"/> クラスの新しいインスタンスを初期化します。
/// </summary>
public MyMonthCalendar()
{
InitializeControls();
CreateCalendar();
DrawCalendar();
}
/// <summary>
/// コントロールの初期化を行います。
/// </summary>
private void InitializeControls()
{
// --------------------------------------------------------------------
// マンスリーカレンダー本体の設定。
// --------------------------------------------------------------------
DoubleBuffered = true;
Font = new Font("Yu Gothic UI", 9F);
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true);
TabStop = false;
// --------------------------------------------------------------------
// 前月ボタンの設定。
// --------------------------------------------------------------------
_prevButton.Click += (_, _) => DisplayMonth = DisplayMonth.AddMonths(-1);
_prevButton.Dock = DockStyle.Left;
_prevButton.FlatAppearance.BorderSize = 0;
_prevButton.FlatStyle = FlatStyle.Flat;
_prevButton.TabStop = false;
_prevButton.Text = "◀";
_prevButton.Width = 40;
// --------------------------------------------------------------------
// 次月ボタンの設定。
// --------------------------------------------------------------------
_nextButton.Click += (_, _) => DisplayMonth = DisplayMonth.AddMonths(1);
_nextButton.Dock = DockStyle.Right;
_nextButton.FlatAppearance.BorderSize = 0;
_nextButton.FlatStyle = FlatStyle.Flat;
_nextButton.TabStop = false;
_nextButton.Text = "▶";
_nextButton.Width = 40;
// --------------------------------------------------------------------
// タイトルラベルの設定。
// --------------------------------------------------------------------
_titleLabel.Dock = DockStyle.Fill;
_titleLabel.Font = new Font(Font, FontStyle.Bold);
_titleLabel.TabStop = false;
_titleLabel.TextAlign = ContentAlignment.MiddleCenter;
// --------------------------------------------------------------------
// ヘッダーパネルの設定。
// --------------------------------------------------------------------
_headerPanel.Controls.Add(_titleLabel);
_headerPanel.Controls.Add(_prevButton);
_headerPanel.Controls.Add(_nextButton);
_headerPanel.Dock = DockStyle.Top;
_headerPanel.Height = 36;
_headerPanel.TabStop = false;
// --------------------------------------------------------------------
// カレンダーパネルの設定。
// --------------------------------------------------------------------
_calendarTable.CellBorderStyle = TableLayoutPanelCellBorderStyle.Single;
_calendarTable.ColumnCount = 7;
_calendarTable.Dock = DockStyle.Fill;
_calendarTable.RowCount = 7;
_calendarTable.TabStop = false;
// 列の作成。
_calendarTable.ColumnStyles.Clear();
for (var i = 0; i < 7; i++)
{
_calendarTable.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100f / 7f));
}
// 行の作成。
_calendarTable.RowStyles.Clear();
for (var i = 0; i < 7; i++)
{
if (i == 0)
{
_calendarTable.RowStyles.Add(new RowStyle(SizeType.Absolute, 26));
}
else
{
_calendarTable.RowStyles.Add(new RowStyle(SizeType.Percent, 100f / 6f));
}
}
// --------------------------------------------------------------------
// マンスリーカレンダー本体に作成した各コントロールを追加する。
// --------------------------------------------------------------------
Controls.Add(_calendarTable);
Controls.Add(_headerPanel);
}
/// <summary>
/// カレンダーを生成します。
/// </summary>
private void CreateCalendar()
{
// 曜日を作成する。
for (var i = 0; i < s_weekNames.Length; i++)
{
var lbl = new Label
{
Dock = DockStyle.Fill,
Font = new Font(Font, FontStyle.Bold),
TabStop = false,
Text = s_weekNames[i],
TextAlign = ContentAlignment.MiddleCenter,
};
if (i == s_sundayIndex)
{
lbl.ForeColor = Color.Red;
}
else if (i == s_saturdayIndex)
{
lbl.ForeColor = Color.Blue;
}
_weekLabels[i] = lbl;
_calendarTable.Controls.Add(lbl, i, 0);
}
// 日付を作成する。
for (var i = 0; i < s_dayButtonCount; i++)
{
var btn = new Button
{
BackColor = SystemColors.Window,
Dock = DockStyle.Fill,
Enabled = false,
FlatStyle = FlatStyle.Flat,
Margin = Padding.Empty,
TabStop = false,
};
btn.Click += DayButton_Click;
btn.FlatAppearance.BorderSize = 0;
btn.FlatAppearance.BorderColor = btn.BackColor;
_dayButtons[i] = btn;
_calendarTable.Controls.Add(btn, i % s_weekNames.Length, i / s_weekNames.Length + 1);
}
}
/// <summary>
/// カレンダーを描画します。
/// </summary>
private void DrawCalendar()
{
_calendarTable.SuspendLayout();
_titleLabel.Text = DisplayMonth.ToString("yyyy年MM月");
// 日付ボタンのリセットを行う。
foreach (Button btn in _dayButtons)
{
btn.Tag = null;
btn.Text = string.Empty;
}
DateTime firstDay = new(DisplayMonth.Year, DisplayMonth.Month, 1);
var firstColumn = (int)firstDay.DayOfWeek;
DateTime startDate = firstDay.AddDays(-firstColumn);
for (var i = 0; i < s_dayButtonCount; i++)
{
DateTime date = startDate.AddDays(i);
// 日付ボタンの設定。
Button btn = _dayButtons[i];
btn.Tag = date;
btn.Text = date.Day.ToString();
// 日付ボタンが今月であるかどうかを判定する。
if (date.Month == DisplayMonth.Month && date.Year == DisplayMonth.Year)
{
// 今月の場合、活性化する。
btn.BackColor = SystemColors.Window;
btn.Enabled = true;
// 文字色を設定する。
switch (date.DayOfWeek)
{
case DayOfWeek.Sunday:
btn.ForeColor = Color.Red;
break;
case DayOfWeek.Saturday:
btn.ForeColor = Color.Blue;
break;
default:
btn.ForeColor = SystemColors.ControlText;
break;
}
}
else
{
// 今月以外の場合、非活性化する。
btn.BackColor = SystemColors.Control;
btn.Enabled = false;
}
// 日付ボタンが本日かどうかを判定する。
if (date.Date == DateTime.Today)
{
// 本日の場合、背景色を変更する。
btn.BackColor = ControlPaint.LightLight(SystemColors.Highlight);
}
// 日付ボタンが選択中かどうかを判定する。
if (SelectedDate.HasValue && SelectedDate.Value.Date == date.Date)
{
// 選択中の場合、枠線を表示する。
btn.FlatAppearance.BorderSize = 2;
btn.FlatAppearance.BorderColor = SystemColors.Highlight;
}
else
{
// 選択中でない場合、枠線を非表示にする。
btn.FlatAppearance.BorderSize = 0;
btn.FlatAppearance.BorderColor = btn.BackColor;
}
}
_calendarTable.ResumeLayout(true);
}
/// <summary>
/// 日付ボタンのクリックを処理します。
/// </summary>
/// <param name="sender">イベントの送信元を表すオブジェクト。</param>
/// <param name="e">イベントデータを表す <see cref="EventArgs"/> オブジェクト。</param>
private void DayButton_Click(object? sender, EventArgs e)
{
if (sender is not Button btn || btn.Tag is not DateTime date)
{
return;
}
SelectedDate = date;
btn.Parent?.Focus();
}
}
おわりに
祝日対応はしていません。また TableLayoutPanel を使っているため、リアルタイムでのリサイズの挙動は重いです。