【问题标题】:MonthCalendar control selection range with EnableVisualStyles?使用 EnableVisualStyles 的 MonthCalendar 控件选择范围?
【发布时间】:2008-10-16 03:07:00
【问题描述】:

我正在使用 MonthCalendar 控件并希望以编程方式选择日期范围。当我这样做时,如果 Application.EnableVisualStyles() 已被调用,则控件无法正确绘制。根据 MSDN,这是一个已知问题。

将 MonthCalendar 与可视化结合使用 启用的样式将导致选择 MonthCalendar 控件的范围为 没有正确绘画 (来自:http://msdn.microsoft.com/en-us/library/system.windows.forms.monthcalendar.aspx

除了不打电话给EnableVisualStyles,真的没有解决办法吗?从我的角度来看,这似乎使该特定控件对于一系列应用程序完全无用,并且是一个相当明显的疏忽。

【问题讨论】:

    标签: c# .net winforms .net-2.0


    【解决方案1】:

    在寻找相同问题的解决方案时,我首先在这里遇到了这个问题,但后来我发现了Nicke Andersson的博客条目。我发现这很有帮助。 这是我对 Nicke 的例子所做的:

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

    【讨论】:

    • 似乎可以工作,但是 onMouseLeave 矩形被重新绘制为默认颜色......需要改变什么,矩形保持不变?很高兴知道。
    • 不应该这么复杂。 Microsoft 需要更新他们的控件并为人们提供现代 UI,而不是 20 年前的。
    【解决方案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;
    
        }
    }
    

    【讨论】:

      【解决方案3】:

      这是一个在显示超过一个月时确实有效的版本(CalendarDimensions != (1,1)),并且还修复了一些其他问题:

      /// <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。

      【讨论】:

        【解决方案4】:

        你可以试试这个代码:

        Dim StartDate As Date = New DateTime(2011, 9, 21)
        Dim EndDate As Date = New DateTime(2011, 9, 25)
        MonthCalendar1.SelectionRange = New SelectionRange(StartDate, EndDate)
        

        更多信息:

        http://www.authorcode.com/how-to-select-a-range-of-dates-in-the-monthcalendar-control/

        http://www.authorcode.com/how-to-enable-windows-xp-visual-styles-of-net-application/

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-02-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-01-05
          相关资源
          最近更新 更多