2008-10-16 59 views
3

我正在使用MonthCalendar控件,並希望以編程方式選擇日期範圍。當我這樣做時,如果調用了Application.EnableVisualStyles(),則控件不能正確繪製。根據MSDN,這是一個已知問題。使用EnableVisualStyles的MonthCalendar控件選擇範圍?

使用的MonthCalendar啓用將導致選擇 範圍MonthCalendar控件來 視覺 風格不正確繪製 (來源:http://msdn.microsoft.com/en-us/library/system.windows.forms.monthcalendar.aspx

真的有沒有這方面比其他沒有鎖定不叫EnableVisualStyles?這似乎使得這個特定的控制對於一系列應用來說完全沒有用處,並且從我的角度來看也是一個相當明顯的疏忽。

回答

2

在尋找同樣問題的解決方案時,我第一次遇到這個問題,但後來我發現了一個Nicke Andersson的博客條目。我發現這非常有幫助。 這裏是我做了什麼Nicke的例子:(!CalendarDimensions =(1,1))

public class MonthCalendarEx : System.Windows.Forms.MonthCalendar 
{ 
    private int _offsetX; 
    private int _offsetY; 
    private int _dayBoxWidth; 
    private int _dayBoxHeight; 

    private bool _repaintSelectedDays = false; 

    public MonthCalendarEx() : base() 
    { 
     OnSizeChanged(null, null); 
     this.SizeChanged += OnSizeChanged; 
     this.DateChanged += OnSelectionChanged; 
     this.DateSelected += OnSelectionChanged; 
    } 

    protected static int WM_PAINT = 0x000F; 

    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); 
     } 
    } 

    private void OnSelectionChanged(object sender, EventArgs e) 
    { 
     _repaintSelectedDays = true; 
    } 

    private void OnSizeChanged(object sender, EventArgs e) 
    {       
     _offsetX = 0; 
     _offsetY = 0; 

     // determine Y offset of days area 
     while (
      HitTest(Width/2, _offsetY).HitArea != HitArea.PrevMonthDate && 
      HitTest(Width/2, _offsetY).HitArea != HitArea.Date) 
     { 
      _offsetY++; 
     } 

     // determine X offset of days area 
     while (HitTest(_offsetX, Height/2).HitArea != HitArea.Date) 
     { 
      _offsetX++; 
     } 

     // determine width of a single day box 
     _dayBoxWidth = 0; 
     DateTime dt1 = HitTest(Width/2, _offsetY).Time; 

     while (HitTest(Width/2, _offsetY + _dayBoxHeight).Time == dt1) 
     { 
      _dayBoxHeight++; 
     } 

     // determine height of a single day box 
     _dayBoxWidth = 0; 
     DateTime dt2 = HitTest(_offsetX, Height/2).Time; 

     while (HitTest(_offsetX + _dayBoxWidth, Height/2).Time == dt2) 
     { 
      _dayBoxWidth++; 
     } 
    } 

    protected override void OnPaint(PaintEventArgs e) 
    { 
     base.OnPaint(e); 

     if (_repaintSelectedDays) 
     { 
      Graphics graphics = e.Graphics; 
      SelectionRange calendarRange = GetDisplayRange(false); 
      Rectangle currentDayFrame = new Rectangle(
       -1, -1, _dayBoxWidth, _dayBoxHeight); 

      DateTime current = SelectionStart; 
      while (current <= SelectionEnd)     
      { 
       Rectangle currentDayRectangle; 

       using (Brush selectionBrush = new SolidBrush(
        Color.FromArgb(
         255, System.Drawing.SystemColors.ActiveCaption))) 
       {      
        TimeSpan span = current.Subtract(calendarRange.Start); 
        int row = span.Days/7; 
        int col = span.Days % 7; 

        currentDayRectangle = new Rectangle(
         _offsetX + (col + (ShowWeekNumbers ? 1 : 0)) * _dayBoxWidth, 
         _offsetY + row * _dayBoxHeight, 
         _dayBoxWidth, 
         _dayBoxHeight); 

        graphics.FillRectangle(selectionBrush, currentDayRectangle); 
       } 

       TextRenderer.DrawText(
        graphics, 
        current.Day.ToString(), 
        Font, 
        currentDayRectangle, 
        System.Drawing.SystemColors.ActiveCaptionText, 
        TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter); 

       if (current == this.TodayDate) 
       { 
        currentDayFrame = currentDayRectangle; 
       } 

       current = current.AddDays(1); 
      } 

      if (currentDayFrame.X > 0) 
      { 
       graphics.DrawRectangle(new Pen(
        new SolidBrush(Color.Red)), currentDayFrame); 
      } 

      _repaintSelectedDays = false; 
     } 
    } 
} 
+0

似乎工作,但onMouseLeave矩形重新繪製了默認顏色...需要改變什麼,矩形保持持久性?很高興知道。 – SiL3NC3 2016-10-21 16:28:27

2

這裏是一個版本時,將顯示超過一個月,做工作,並修復了一些其他問題也:

/// <summary> 
/// When Visual Styles are enabled on Windows XP, the MonthCalendar.SelectionRange 
/// does not paint correctly when more than one date is selected. 
/// See: http://msdn.microsoft.com/en-us/library/5d1acks5(VS.80).aspx 
/// "Additionally, if you enable visual styles on some controls, the control might display incorrectly 
/// in certain situations. These include the MonthCalendar control with a selection range set... 
/// This class fixes that problem. 
/// </summary> 
/// <remarks>Author: Mark Cranness</remarks> 
public class FixVisualStylesMonthCalendar : System.Windows.Forms.MonthCalendar { 

    /// <summary> 
    /// The width of a single cell (date) in the calendar. 
    /// </summary> 
    private int dayCellWidth; 
    /// <summary> 
    /// The height of a single cell (date) in the calendar. 
    /// </summary> 
    private int dayCellHeight; 

    /// <summary> 
    /// The calendar first day of the week actually used. 
    /// </summary> 
    private DayOfWeek calendarFirstDayOfWeek; 

    /// <summary> 
    /// Only repaint when VisualStyles enabled on Windows XP. 
    /// </summary> 
    private bool repaintSelectionRange = false; 

    /// <summary> 
    /// A MonthCalendar class that fixes SelectionRange painting problems 
    /// on Windows XP when Visual Styles is enabled. 
    /// </summary> 
    public FixVisualStylesMonthCalendar() { 

     if (Application.RenderWithVisualStyles 
       && Environment.OSVersion.Version < new Version(6, 0)) { 

      // If Visual Styles are enabled, and XP, then fix-up the painting of SelectionRange 
      this.repaintSelectionRange = true; 
      this.OnSizeChanged(this, EventArgs.Empty); 
      this.SizeChanged += new EventHandler(this.OnSizeChanged); 

     } 
    } 

    /// <summary> 
    /// The WM_PAINT message is sent to make a request to paint a portion of a window. 
    /// </summary> 
    public const int WM_PAINT = 0x000F; 

    /// <summary> 
    /// Override WM_PAINT to repaint the selection range. 
    /// </summary> 
    [System.Diagnostics.DebuggerStepThroughAttribute()] 
    protected override void WndProc(ref Message m) 
    { 
     base.WndProc(ref m); 
     if (m.Msg == WM_PAINT 
       && !this.DesignMode 
       && this.repaintSelectionRange) { 
      // MonthCalendar is ControlStyles.UserPaint=false => Paint event is not raised 
      this.RepaintSelectionRange(ref m); 
     } 
    } 

    /// <summary> 
    /// Repaint the SelectionRange. 
    /// </summary> 
    private void RepaintSelectionRange(ref Message m) { 

     using (Graphics graphics = this.CreateGraphics()) 
     using (Brush backBrush 
       = new SolidBrush(graphics.GetNearestColor(this.BackColor))) 
     using (Brush selectionBrush 
       = new SolidBrush(graphics.GetNearestColor(SystemColors.ActiveCaption))) { 

      Rectangle todayFrame = Rectangle.Empty; 

      // For each day in SelectionRange... 
      for (DateTime selectionDate = this.SelectionStart; 
        selectionDate <= this.SelectionEnd; 
        selectionDate = selectionDate.AddDays(1)) { 

       Rectangle selectionDayRectangle = this.GetSelectionDayRectangle(selectionDate); 
       if (selectionDayRectangle.IsEmpty) continue; 

       if (selectionDate.Date == this.TodayDate) { 
        todayFrame = selectionDayRectangle; 
       } 

       // Paint as 'selected' a little smaller than the whole rectangle 
       Rectangle highlightRectangle = Rectangle.Inflate(selectionDayRectangle, 0, -2); 
       if (selectionDate == this.SelectionStart) { 
        highlightRectangle.X += 2; 
        highlightRectangle.Width -= 2; 
       } 
       if (selectionDate == this.SelectionEnd) { 
        highlightRectangle.Width -= 2; 
       } 

       // Paint background, selection and day-of-month text 
       graphics.FillRectangle(backBrush, selectionDayRectangle); 
       graphics.FillRectangle(selectionBrush, highlightRectangle); 
       TextRenderer.DrawText(
        graphics, 
        selectionDate.Day.ToString(), 
        this.Font, 
        selectionDayRectangle, 
        SystemColors.ActiveCaptionText, 
        TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter); 

      } 

      if (this.ShowTodayCircle && !todayFrame.IsEmpty) { 
       // Redraw the ShowTodayCircle (square) that we painted over above 
       using (Pen redPen = new Pen(Color.Red)) { 
        todayFrame.Width--; 
        todayFrame.Height--; 
        graphics.DrawRectangle(redPen, todayFrame); 
       } 
      } 

     } 
    } 

    /// <summary> 
    /// When displayed dates changed, clear the cached month locations. 
    /// </summary> 
    private SelectionRange previousDisplayedDates = new SelectionRange(); 

    /// <summary> 
    /// Gets a graphics Rectangle for the area corresponding to a single date on the calendar. 
    /// </summary> 
    private Rectangle GetSelectionDayRectangle(DateTime selectionDateTime) { 

     // Handle the leading and trailing dates from the previous and next months 
     SelectionRange allDisplayedDates = this.GetDisplayRange(false); 
     SelectionRange fullMonthDates = this.GetDisplayRange(true); 
     int adjust1Week; 
     DateTime selectionDate = selectionDateTime.Date; 
     if (selectionDate < allDisplayedDates.Start 
       || selectionDate > allDisplayedDates.End) { 
      // Selection Date is not displayed on calendar 
      return Rectangle.Empty; 
     } else if (selectionDate < fullMonthDates.Start) { 
      // Selection Date is trailing from the previous partial month 
      selectionDate = selectionDate.AddDays(7); 
      adjust1Week = -1; 
     } else if (selectionDate > fullMonthDates.End) { 
      // Selection Date is leading from the next partial month 
      selectionDate = selectionDate.AddDays(-14); 
      adjust1Week = +2; 
     } else { 
      // A mainline date 
      adjust1Week = 0; 
     } 

     // Discard cached month locations when calendar moves 
     if (this.previousDisplayedDates.Start != allDisplayedDates.Start 
       || this.previousDisplayedDates.End != allDisplayedDates.End) { 
      this.DiscardCachedMonthDateAreaLocations(); 
      this.previousDisplayedDates.Start = allDisplayedDates.Start; 
      this.previousDisplayedDates.End = allDisplayedDates.End; 
     } 

     Point monthDateAreaLocation = this.GetMonthDateAreaLocation(selectionDate); 
     if (monthDateAreaLocation.IsEmpty) return Rectangle.Empty; 

     DayOfWeek monthFirstDayOfWeek = (new DateTime(selectionDate.Year, selectionDate.Month, 1)).DayOfWeek; 
     int dayOfWeekAdjust = (int)monthFirstDayOfWeek - (int)this.calendarFirstDayOfWeek; 
     if (dayOfWeekAdjust < 0) dayOfWeekAdjust += 7; 
     int row = (selectionDate.Day - 1 + dayOfWeekAdjust)/7; 
     int col = (selectionDate.Day - 1 + dayOfWeekAdjust) % 7; 
     row += adjust1Week; 

     return new Rectangle(
      monthDateAreaLocation.X + col * this.dayCellWidth, 
      monthDateAreaLocation.Y + row * this.dayCellHeight, 
      this.dayCellWidth, 
      this.dayCellHeight); 

    } 

    /// <summary> 
    /// Cached calendar location from the last lookup. 
    /// </summary> 
    private Point[] cachedMonthDateAreaLocation = new Point[13]; 

    /// <summary> 
    /// Discard the cached month locations when calendar moves. 
    /// </summary> 
    private void DiscardCachedMonthDateAreaLocations() { 
     for (int i = 0; i < 13; i++) this.cachedMonthDateAreaLocation[i] = Point.Empty; 
    } 

    /// <summary> 
    /// Gets the graphics location (x,y point) of the top left of the 
    /// calendar date area for the month containing the specified date. 
    /// </summary> 
    private Point GetMonthDateAreaLocation(DateTime selectionDate) { 

     Point monthDateAreaLocation = this.cachedMonthDateAreaLocation[selectionDate.Month]; 
     HitTestInfo hitInfo; 
     if (!monthDateAreaLocation.IsEmpty 
       && (hitInfo = this.HitTest(monthDateAreaLocation.X, monthDateAreaLocation.Y + this.dayCellHeight)) 
        .HitArea == HitArea.Date 
       && hitInfo.Time.Year == selectionDate.Year 
       && hitInfo.Time.Month == selectionDate.Month) { 

      // Use previously cached lookup 
      return monthDateAreaLocation; 

     } else { 

      // Assume the worst (Error: empty) 
      monthDateAreaLocation = this.cachedMonthDateAreaLocation[selectionDate.Month] = Point.Empty; 

      Point monthDataAreaPoint = this.GetMonthDateAreaMiddle(selectionDate); 
      if (monthDataAreaPoint.IsEmpty) return Point.Empty; 

      // Move left from the middle to find the left edge of the Date area 
      monthDateAreaLocation.X = monthDataAreaPoint.X--; 
      HitTestInfo hitInfo1, hitInfo2; 
      while ((hitInfo1 = this.HitTest(monthDataAreaPoint.X, monthDataAreaPoint.Y)) 
        .HitArea == HitArea.Date 
        && hitInfo1.Time.Month == selectionDate.Month 
       || (hitInfo2 = this.HitTest(monthDataAreaPoint.X, monthDataAreaPoint.Y + this.dayCellHeight)) 
        .HitArea == HitArea.Date 
        && hitInfo2.Time.Month == selectionDate.Month) { 
       monthDateAreaLocation.X = monthDataAreaPoint.X--; 
       if (monthDateAreaLocation.X < 0) return Point.Empty; // Error: bail 
      } 

      // Move up from the last column to find the top edge of the Date area 
      int monthLastDayOfWeekX = monthDateAreaLocation.X + (this.dayCellWidth * 7 * 13)/14; 
      monthDateAreaLocation.Y = monthDataAreaPoint.Y--; 
      while (this.HitTest(monthLastDayOfWeekX, monthDataAreaPoint.Y).HitArea == HitArea.Date) { 
       monthDateAreaLocation.Y = monthDataAreaPoint.Y--; 
       if (monthDateAreaLocation.Y < 0) return Point.Empty; // Error: bail 
      } 

      // Got it 
      this.cachedMonthDateAreaLocation[selectionDate.Month] = monthDateAreaLocation; 
      return monthDateAreaLocation; 

     } 
    } 

    /// <summary> 
    /// Paranoid fudge/wobble of the GetMonthDateAreaMiddle in case 
    /// our first estimate to hit the month misses. 
    /// (Needed? perhaps not.) 
    /// </summary> 
    private static Point[] searchSpiral = { 
     new Point(0, 0), 
     new Point(-1,+1), new Point(+1,+1), new Point(+1,-1), new Point(-1,-1), 
     new Point(-2,+2), new Point(+2,+2), new Point(+2,-2), new Point(-2,-2) 
    }; 

    /// <summary> 
    /// Gets a point somewhere inside the calendar date area of 
    /// the month containing the given selection date. 
    /// </summary> 
    /// <remarks>The point returned will be HitArea.Date, and match the year and 
    /// month of the selection date; otherwise it will be Point.Empty.</remarks> 
    private Point GetMonthDateAreaMiddle(DateTime selectionDate) { 

     // Iterate over all displayed months, and a search spiral (needed? perhaps not) 
     for (int dimX = 1; dimX <= this.CalendarDimensions.Width; dimX++) { 
      for (int dimY = 1; dimY <= this.CalendarDimensions.Height; dimY++) { 
       foreach (Point search in searchSpiral) { 

        Point monthDateAreaMiddle = new Point(
         ((dimX - 1) * 2 + 1) * this.Width/(2 * this.CalendarDimensions.Width) 
          + this.dayCellWidth * search.X, 
         ((dimY - 1) * 2 + 1) * this.Height/(2 * this.CalendarDimensions.Height) 
          + this.dayCellHeight * search.Y); 
        HitTestInfo hitInfo = this.HitTest(monthDateAreaMiddle); 
        if (hitInfo.HitArea == HitArea.Date) { 
         // Got the Date Area of the month 
         if (hitInfo.Time.Year == selectionDate.Year 
           && hitInfo.Time.Month == selectionDate.Month) { 
          // For the correct month 
          return monthDateAreaMiddle; 
         } else { 
          // Keep looking in the other months 
          break; 
         } 
        } 

       } 
      } 
     } 
     return Point.Empty; // Error: not found 

    } 

    /// <summary> 
    /// When this MonthCalendar is resized, recalculate the size of a day cell. 
    /// </summary> 
    private void OnSizeChanged(object sender, EventArgs e) { 

     // Discard previous cached Month Area Location 
     DiscardCachedMonthDateAreaLocations(); 
     this.dayCellWidth = this.dayCellHeight = 0; 

     // Without this, the repaint sometimes does not happen... 
     this.Invalidate(); 

     // Determine Y offset of days area 
     int middle = this.Width/(2 * this.CalendarDimensions.Width); 
     int dateAreaTop = 0; 
     while (this.HitTest(middle, dateAreaTop).HitArea != HitArea.PrevMonthDate 
       && this.HitTest(middle, dateAreaTop).HitArea != HitArea.Date) { 
      dateAreaTop++; 
      if (dateAreaTop > this.ClientSize.Height) return; // Error: bail 
     } 

     // Determine height of a single day box 
     int dayCellHeight = 1; 
     DateTime dayCellTime = this.HitTest(middle, dateAreaTop).Time; 
     while (this.HitTest(middle, dateAreaTop + dayCellHeight).Time == dayCellTime) { 
      dayCellHeight++; 
     } 

     // Determine X offset of days area 
     middle = this.Height/(2 * this.CalendarDimensions.Height); 
     int dateAreaLeft = 0; 
     while (this.HitTest(dateAreaLeft, middle).HitArea != HitArea.Date) { 
      dateAreaLeft++; 
      if (dateAreaLeft > this.ClientSize.Width) return; // Error: bail 
     } 

     // Determine width of a single day box 
     int dayCellWidth = 1; 
     dayCellTime = this.HitTest(dateAreaLeft, middle).Time; 
     while (this.HitTest(dateAreaLeft + dayCellWidth, middle).Time == dayCellTime) { 
      dayCellWidth++; 
     } 

     // Record day box size and actual first day of the month used 
     this.calendarFirstDayOfWeek = dayCellTime.DayOfWeek; 
     this.dayCellWidth = dayCellWidth; 
     this.dayCellHeight = dayCellHeight; 

    } 

} 

我的測試顯示,Windows 7中沒有畫的問題,我想到的是同樣沒有Vista的,所以這只是試圖爲Windows XP修復。

+0

不錯,謝謝。 – 2011-01-06 14:06:09

2

我在上面的Mark Cranness代碼中發現了一個小問題:在完全禁用視覺樣式的XP系統上,即使在調用Application.EnableVisualStyles()時,Application.RenderWithVisualStyles也會設置爲False。

因此,在這種情況下,自定義畫圖代碼根本不運行。爲了解決這個問題,我改變了FixVisualStylesMonthCalendar構造函數的第一行

if (Application.VisualStyleState != System.Windows.Forms.VisualStyles.VisualStyleState.NoneEnabled && 
      Environment.OSVersion.Version < new Version(6, 0)) 

整個代碼是這個答案的底部。

我找不到任何方式來評論答案本身。下面的代碼學分去原作者 - (如果他或任何人都可以驗證這個答案並更新它,我會很樂意刪除此一個)

/// <summary> 
/// When Visual Styles are enabled on Windows XP, the MonthCalendar.SelectionRange 
/// does not paint correctly when more than one date is selected. 
/// See: http://msdn.microsoft.com/en-us/library/5d1acks5(VS.80).aspx 
/// "Additionally, if you enable visual styles on some controls, the control might display incorrectly 
/// in certain situations. These include the MonthCalendar control with a selection range set... 
/// This class fixes that problem. 
/// </summary> 
/// <remarks>Author: Mark Cranness - PatronBase Limited.</remarks> 
public class FixVisualStylesMonthCalendar : System.Windows.Forms.MonthCalendar 
{ 

    /// <summary> 
    /// The width of a single cell (date) in the calendar. 
    /// </summary> 
    private int dayCellWidth; 
    /// <summary> 
    /// The height of a single cell (date) in the calendar. 
    /// </summary> 
    private int dayCellHeight; 

    /// <summary> 
    /// The calendar first day of the week actually used. 
    /// </summary> 
    private DayOfWeek calendarFirstDayOfWeek; 

    /// <summary> 
    /// Only repaint when VisualStyles enabled on Windows XP. 
    /// </summary> 
    private bool repaintSelectionRange = false; 

    /// <summary> 
    /// A MonthCalendar class that fixes SelectionRange painting problems 
    /// on Windows XP when Visual Styles is enabled. 
    /// </summary> 
    public FixVisualStylesMonthCalendar() 
    { 

     if (Application.VisualStyleState != System.Windows.Forms.VisualStyles.VisualStyleState.NoneEnabled && //Application.RenderWithVisualStyles && 
      Environment.OSVersion.Version < new Version(6, 0)) 
     { 
      // If Visual Styles are enabled, and XP, then fix-up the painting of SelectionRange 
      this.repaintSelectionRange = true; 
      this.OnSizeChanged(this, EventArgs.Empty); 
      this.SizeChanged += new EventHandler(this.OnSizeChanged); 
     } 
    } 

    /// <summary> 
    /// The WM_PAINT message is sent to make a request to paint a portion of a window. 
    /// </summary> 
    public const int WM_PAINT = 0x000F; 

    /// <summary> 
    /// Override WM_PAINT to repaint the selection range. 
    /// </summary> 
    [System.Diagnostics.DebuggerStepThroughAttribute()] 
    protected override void WndProc(ref Message m) 
    { 
     base.WndProc(ref m); 
     if (m.Msg == WM_PAINT 
       && !this.DesignMode 
       && this.repaintSelectionRange) 
     { 
      // MonthCalendar is ControlStyles.UserPaint=false => Paint event is not raised 
      this.RepaintSelectionRange(ref m); 
     } 
    } 

    /// <summary> 
    /// Repaint the SelectionRange. 
    /// </summary> 
    private void RepaintSelectionRange(ref Message m) 
    { 

     using (Graphics graphics = this.CreateGraphics()) 
     using (Brush backBrush 
       = new SolidBrush(graphics.GetNearestColor(this.BackColor))) 
     using (Brush selectionBrush 
       = new SolidBrush(graphics.GetNearestColor(SystemColors.ActiveCaption))) 
     { 

      Rectangle todayFrame = Rectangle.Empty; 

      // For each day in SelectionRange... 
      for (DateTime selectionDate = this.SelectionStart; 
        selectionDate <= this.SelectionEnd; 
        selectionDate = selectionDate.AddDays(1)) 
      { 

       Rectangle selectionDayRectangle = this.GetSelectionDayRectangle(selectionDate); 
       if (selectionDayRectangle.IsEmpty) continue; 

       if (selectionDate.Date == this.TodayDate) 
       { 
        todayFrame = selectionDayRectangle; 
       } 

       // Paint as 'selected' a little smaller than the whole rectangle 
       Rectangle highlightRectangle = Rectangle.Inflate(selectionDayRectangle, 0, -2); 
       if (selectionDate == this.SelectionStart) 
       { 
        highlightRectangle.X += 2; 
        highlightRectangle.Width -= 2; 
       } 
       if (selectionDate == this.SelectionEnd) 
       { 
        highlightRectangle.Width -= 2; 
       } 

       // Paint background, selection and day-of-month text 
       graphics.FillRectangle(backBrush, selectionDayRectangle); 
       graphics.FillRectangle(selectionBrush, highlightRectangle); 
       TextRenderer.DrawText(
        graphics, 
        selectionDate.Day.ToString(), 
        this.Font, 
        selectionDayRectangle, 
        SystemColors.ActiveCaptionText, 
        TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter); 

      } 

      if (this.ShowTodayCircle && !todayFrame.IsEmpty) 
      { 
       // Redraw the ShowTodayCircle (square) that we painted over above 
       using (Pen redPen = new Pen(Color.Red)) 
       { 
        todayFrame.Width--; 
        todayFrame.Height--; 
        graphics.DrawRectangle(redPen, todayFrame); 
       } 
      } 

     } 
    } 

    /// <summary> 
    /// When displayed dates changed, clear the cached month locations. 
    /// </summary> 
    private SelectionRange previousDisplayedDates = new SelectionRange(); 

    /// <summary> 
    /// Gets a graphics Rectangle for the area corresponding to a single date on the calendar. 
    /// </summary> 
    private Rectangle GetSelectionDayRectangle(DateTime selectionDateTime) 
    { 

     // Handle the leading and trailing dates from the previous and next months 
     SelectionRange allDisplayedDates = this.GetDisplayRange(false); 
     SelectionRange fullMonthDates = this.GetDisplayRange(true); 
     int adjust1Week; 
     DateTime selectionDate = selectionDateTime.Date; 
     if (selectionDate < allDisplayedDates.Start 
       || selectionDate > allDisplayedDates.End) 
     { 
      // Selection Date is not displayed on calendar 
      return Rectangle.Empty; 
     } 
     else if (selectionDate < fullMonthDates.Start) 
     { 
      // Selection Date is trailing from the previous partial month 
      selectionDate = selectionDate.AddDays(7); 
      adjust1Week = -1; 
     } 
     else if (selectionDate > fullMonthDates.End) 
     { 
      // Selection Date is leading from the next partial month 
      selectionDate = selectionDate.AddDays(-14); 
      adjust1Week = +2; 
     } 
     else 
     { 
      // A mainline date 
      adjust1Week = 0; 
     } 

     // Discard cached month locations when calendar moves 
     if (this.previousDisplayedDates.Start != allDisplayedDates.Start 
       || this.previousDisplayedDates.End != allDisplayedDates.End) 
     { 
      this.DiscardCachedMonthDateAreaLocations(); 
      this.previousDisplayedDates.Start = allDisplayedDates.Start; 
      this.previousDisplayedDates.End = allDisplayedDates.End; 
     } 

     Point monthDateAreaLocation = this.GetMonthDateAreaLocation(selectionDate); 
     if (monthDateAreaLocation.IsEmpty) return Rectangle.Empty; 

     DayOfWeek monthFirstDayOfWeek = (new DateTime(selectionDate.Year, selectionDate.Month, 1)).DayOfWeek; 
     int dayOfWeekAdjust = (int)monthFirstDayOfWeek - (int)this.calendarFirstDayOfWeek; 
     if (dayOfWeekAdjust < 0) dayOfWeekAdjust += 7; 
     int row = (selectionDate.Day - 1 + dayOfWeekAdjust)/7; 
     int col = (selectionDate.Day - 1 + dayOfWeekAdjust) % 7; 
     row += adjust1Week; 

     return new Rectangle(
      monthDateAreaLocation.X + col * this.dayCellWidth, 
      monthDateAreaLocation.Y + row * this.dayCellHeight, 
      this.dayCellWidth, 
      this.dayCellHeight); 

    } 

    /// <summary> 
    /// Cached calendar location from the last lookup. 
    /// </summary> 
    private Point[] cachedMonthDateAreaLocation = new Point[13]; 

    /// <summary> 
    /// Discard the cached month locations when calendar moves. 
    /// </summary> 
    private void DiscardCachedMonthDateAreaLocations() 
    { 
     for (int i = 0; i < 13; i++) this.cachedMonthDateAreaLocation[i] = Point.Empty; 
    } 

    /// <summary> 
    /// Gets the graphics location (x,y point) of the top left of the 
    /// calendar date area for the month containing the specified date. 
    /// </summary> 
    private Point GetMonthDateAreaLocation(DateTime selectionDate) 
    { 

     Point monthDateAreaLocation = this.cachedMonthDateAreaLocation[selectionDate.Month]; 
     HitTestInfo hitInfo; 
     if (!monthDateAreaLocation.IsEmpty 
       && (hitInfo = this.HitTest(monthDateAreaLocation.X, monthDateAreaLocation.Y + this.dayCellHeight)) 
        .HitArea == HitArea.Date 
       && hitInfo.Time.Year == selectionDate.Year 
       && hitInfo.Time.Month == selectionDate.Month) 
     { 

      // Use previously cached lookup 
      return monthDateAreaLocation; 

     } 
     else 
     { 

      // Assume the worst (Error: empty) 
      monthDateAreaLocation = this.cachedMonthDateAreaLocation[selectionDate.Month] = Point.Empty; 

      Point monthDataAreaPoint = this.GetMonthDateAreaMiddle(selectionDate); 
      if (monthDataAreaPoint.IsEmpty) return Point.Empty; 

      // Move left from the middle to find the left edge of the Date area 
      monthDateAreaLocation.X = monthDataAreaPoint.X--; 
      HitTestInfo hitInfo1, hitInfo2; 
      while ((hitInfo1 = this.HitTest(monthDataAreaPoint.X, monthDataAreaPoint.Y)) 
        .HitArea == HitArea.Date 
        && hitInfo1.Time.Month == selectionDate.Month 
       || (hitInfo2 = this.HitTest(monthDataAreaPoint.X, monthDataAreaPoint.Y + this.dayCellHeight)) 
        .HitArea == HitArea.Date 
        && hitInfo2.Time.Month == selectionDate.Month) 
      { 
       monthDateAreaLocation.X = monthDataAreaPoint.X--; 
       if (monthDateAreaLocation.X < 0) return Point.Empty; // Error: bail 
      } 

      // Move up from the last column to find the top edge of the Date area 
      int monthLastDayOfWeekX = monthDateAreaLocation.X + (this.dayCellWidth * 7 * 13)/14; 
      monthDateAreaLocation.Y = monthDataAreaPoint.Y--; 
      while (this.HitTest(monthLastDayOfWeekX, monthDataAreaPoint.Y).HitArea == HitArea.Date) 
      { 
       monthDateAreaLocation.Y = monthDataAreaPoint.Y--; 
       if (monthDateAreaLocation.Y < 0) return Point.Empty; // Error: bail 
      } 

      // Got it 
      this.cachedMonthDateAreaLocation[selectionDate.Month] = monthDateAreaLocation; 
      return monthDateAreaLocation; 

     } 
    } 

    /// <summary> 
    /// Paranoid fudge/wobble of the GetMonthDateAreaMiddle in case 
    /// our first estimate to hit the month misses. 
    /// (Needed? perhaps not.) 
    /// </summary> 
    private static Point[] searchSpiral = { 
    new Point(0, 0), 
    new Point(-1,+1), new Point(+1,+1), new Point(+1,-1), new Point(-1,-1), 
    new Point(-2,+2), new Point(+2,+2), new Point(+2,-2), new Point(-2,-2) 
}; 

    /// <summary> 
    /// Gets a point somewhere inside the calendar date area of 
    /// the month containing the given selection date. 
    /// </summary> 
    /// <remarks>The point returned will be HitArea.Date, and match the year and 
    /// month of the selection date; otherwise it will be Point.Empty.</remarks> 
    private Point GetMonthDateAreaMiddle(DateTime selectionDate) 
    { 

     // Iterate over all displayed months, and a search spiral (needed? perhaps not) 
     for (int dimX = 1; dimX <= this.CalendarDimensions.Width; dimX++) 
     { 
      for (int dimY = 1; dimY <= this.CalendarDimensions.Height; dimY++) 
      { 
       foreach (Point search in searchSpiral) 
       { 

        Point monthDateAreaMiddle = new Point(
         ((dimX - 1) * 2 + 1) * this.Width/(2 * this.CalendarDimensions.Width) 
          + this.dayCellWidth * search.X, 
         ((dimY - 1) * 2 + 1) * this.Height/(2 * this.CalendarDimensions.Height) 
          + this.dayCellHeight * search.Y); 
        HitTestInfo hitInfo = this.HitTest(monthDateAreaMiddle); 
        if (hitInfo.HitArea == HitArea.Date) 
        { 
         // Got the Date Area of the month 
         if (hitInfo.Time.Year == selectionDate.Year 
           && hitInfo.Time.Month == selectionDate.Month) 
         { 
          // For the correct month 
          return monthDateAreaMiddle; 
         } 
         else 
         { 
          // Keep looking in the other months 
          break; 
         } 
        } 

       } 
      } 
     } 
     return Point.Empty; // Error: not found 

    } 

    /// <summary> 
    /// When this MonthCalendar is resized, recalculate the size of a day cell. 
    /// </summary> 
    private void OnSizeChanged(object sender, EventArgs e) 
    { 

     // Discard previous cached Month Area Location 
     DiscardCachedMonthDateAreaLocations(); 
     this.dayCellWidth = this.dayCellHeight = 0; 

     // Without this, the repaint sometimes does not happen... 
     this.Invalidate(); 

     // Determine Y offset of days area 
     int middle = this.Width/(2 * this.CalendarDimensions.Width); 
     int dateAreaTop = 0; 
     while (this.HitTest(middle, dateAreaTop).HitArea != HitArea.PrevMonthDate 
       && this.HitTest(middle, dateAreaTop).HitArea != HitArea.Date) 
     { 
      dateAreaTop++; 
      if (dateAreaTop > this.ClientSize.Height) return; // Error: bail 
     } 

     // Determine height of a single day box 
     int dayCellHeight = 1; 
     DateTime dayCellTime = this.HitTest(middle, dateAreaTop).Time; 
     while (this.HitTest(middle, dateAreaTop + dayCellHeight).Time == dayCellTime) 
     { 
      dayCellHeight++; 
     } 

     // Determine X offset of days area 
     middle = this.Height/(2 * this.CalendarDimensions.Height); 
     int dateAreaLeft = 0; 
     while (this.HitTest(dateAreaLeft, middle).HitArea != HitArea.Date) 
     { 
      dateAreaLeft++; 
      if (dateAreaLeft > this.ClientSize.Width) return; // Error: bail 
     } 

     // Determine width of a single day box 
     int dayCellWidth = 1; 
     dayCellTime = this.HitTest(dateAreaLeft, middle).Time; 
     while (this.HitTest(dateAreaLeft + dayCellWidth, middle).Time == dayCellTime) 
     { 
      dayCellWidth++; 
     } 

     // Record day box size and actual first day of the month used 
     this.calendarFirstDayOfWeek = dayCellTime.DayOfWeek; 
     this.dayCellWidth = dayCellWidth; 
     this.dayCellHeight = dayCellHeight; 

    } 
}