Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

Windows Forms カレンダーコントロールを自作してみた

0
Posted at

はじめに

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 を使っているため、リアルタイムでのリサイズの挙動は重いです。

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?