System.Windows.Forms.MonthCalendarで日付の色を変える
MonthCalendarで休日の色を分けたいので調査
Nicke Andersson The writings of a .NET nut. http://nickeandersson.blogs.com/blog/2006/05/_modifying_the_.html
このソースを貼り付けて、クラスを作るとあっさり動いた。
と、思ったけど地雷が多くて一般公開したくない人の気持ちがよくわかった。
例えば、1ヶ月表示でないと、正しい範囲が取得できなくて変になる。
あとはフォントサイズ変えると表示がずれる。
CalendarDimensionsで3x4とかはわかるけど、そこから正確な座標と該当月の取得ロジックを書くのは時間がかかりそうなので断念。このコントロール一杯並べて逃げる。
フォントサイズは9ptと12ptだけ対応した補正を入れて回避。
ソース
以下のように、System.Windows.Forms.MonthCalendarを継承したクラスを作る
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Text; using System.Threading; using System.Windows.Forms; namespace MonthCalendarExTest { /// <summary> /// カレンダーの指定日に色をつける /// /// 元ネタ /// http://nickeandersson.blogs.com/blog/2006/05/_modifying_the_.html /// /// 1ヶ月表示のみ対応。複数月対応やフォントサイズ変更対応は後から来た人に期待。 /// </summary> public class MonthCalendarEx:System.Windows.Forms.MonthCalendar { protected static int WM_PAINT = 0x000F; private List<DateTime> _warningDates = new List<DateTime>(); private Color _warningDateBackColor = Color.FromArgb(192,255,192,192); private Color _warningDateForeColor = Color.FromArgb(255,128,0,0); #region Properties public List<DateTime> WarningDates { get { return _warningDates; } set { _warningDates = value; } } public Color WarningDateBackColor { get { return _warningDateBackColor; } set { _warningDateBackColor = value; } } public Color WarningDateForeColor { get { return _warningDateForeColor; } set { _warningDateForeColor = value; } } #endregion protected override void WndProc(ref System.Windows.Forms.Message m) { base.WndProc(ref m); if(m.Msg == WM_PAINT) { Graphics graphics = Graphics.FromHwnd(this.Handle); PaintEventArgs pe = new PaintEventArgs(graphics,new Rectangle(0,0,this.Width,this.Height)); OnPaint(pe); } } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); if(WarningDates.Count <= 0) return; // 1ヶ月表示限定 if(base.CalendarDimensions.Height != 1 || base.CalendarDimensions.Width != 1) return; // Create a list of those dates that actually should be marked as warnings. List<DateTime> visibleWarningDates = new List<DateTime>(); SelectionRange calendarRange = GetDisplayRange(false); foreach(DateTime date in WarningDates) { if(date >= calendarRange.Start && date <= calendarRange.End) visibleWarningDates.Add(date); } if(visibleWarningDates.Count <= 0) return; int leftMargin = 0; int center = (base.Width / 2); while((base.HitTest(leftMargin,center).HitArea != HitArea.PrevMonthDate && base.HitTest(leftMargin,center).HitArea != HitArea.Date) && leftMargin < Width ) { leftMargin++; } // 上限と下限から、塗りつぶす領域の高さを算出する // 表示域の上限を取得 int firstWeekPosition = 0; while((base.HitTest(leftMargin + 25,firstWeekPosition).HitArea != HitArea.PrevMonthDate && base.HitTest(leftMargin + 25,firstWeekPosition).HitArea != HitArea.Date) && firstWeekPosition < Height ) { firstWeekPosition++; } // 表示域の下限を取得 int lastWeekPosition = Height; while((base.HitTest(25,lastWeekPosition).HitArea != HitArea.NextMonthDate && base.HitTest(25,lastWeekPosition).HitArea != HitArea.Date) && lastWeekPosition >= 0 ) { lastWeekPosition--; } if(firstWeekPosition <= 0 || lastWeekPosition <= 0) return; // 塗りつぶす範囲のサイズ(1日分) int dayBoxWidth = (base.Width - leftMargin * 2) / (base.ShowWeekNumbers ? 8 : 7); int dayBoxHeight = (int)(((float)(lastWeekPosition - firstWeekPosition)) / 6.0f); // 指定色で背景と文字を描画 using(Brush warningBrush = new SolidBrush(_warningDateBackColor)) { foreach(DateTime visDate in visibleWarningDates) { DrawText(e.Graphics,warningBrush,calendarRange,visDate,dayBoxWidth,dayBoxHeight,firstWeekPosition,lastWeekPosition,leftMargin); } } } private void DrawText( Graphics graphics, Brush warningBrush, SelectionRange calendarRange, DateTime visDate, int dayBoxWidth, int dayBoxHeight, int firstWeekPosition, int lastWeekPosition, int leftMargin ) { TimeSpan span = visDate.Subtract(calendarRange.Start); int row = span.Days / 7; int col = span.Days % 7; // 微妙にずれるので修正 9pt と 12pt対応 int tune = leftMargin + (leftMargin / 10) + 1; Rectangle fillRect = new Rectangle( (col + (ShowWeekNumbers ? 1 : 0)) * dayBoxWidth + tune, firstWeekPosition + row * dayBoxHeight + 1, dayBoxWidth - 2, dayBoxHeight - 2 ); graphics.FillRectangle(warningBrush,fillRect); // Check if the date is in the bolded dates array bool makeDateBolded = false; foreach(DateTime boldDate in BoldedDates) { if(boldDate == visDate) makeDateBolded = true; } using(Font textFont = new Font(Font,(makeDateBolded ? FontStyle.Bold : FontStyle.Regular))) { TextRenderer.DrawText( graphics, visDate.Day.ToString(), textFont, fillRect, _warningDateForeColor, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter ); } } } }
上記クラスをフォームに貼り付けたら
WarningDates.Add で日付を入れておけば、色が塗られる
DateTime startDate = new DateTime(DateTime.Today.Year,DateTime.Today.Month,1); DateTime finishDate = startDate.AddMonths(3); DateTime currentDate = startDate; for(;;) { if(finishDate <= currentDate) break; if(currentDate.DayOfWeek == DayOfWeek.Sunday || currentDate.DayOfWeek == DayOfWeek.Saturday) this.monthCalendarEx1.WarningDates.Add(currentDate); currentDate = currentDate.AddDays(1); }