使用 EnableVisualStyles 的 MonthCalendar 控件选择范围?

发布于 2024-07-07 00:17:53 字数 574 浏览 9 评论 0原文

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

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

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

I'm using the MonthCalendar control and want to programmatically select a date range. When I do so the control doesn't paint properly if Application.EnableVisualStyles() has been called. This is a known issue according to MSDN.

Using the MonthCalendar with visual
styles enabled will cause a selection
range for the MonthCalendar control to
not paint correctly
(from: http://msdn.microsoft.com/en-us/library/system.windows.forms.monthcalendar.aspx)

Is there really no fix for this other than not calling EnableVisualStyles? This seems to make that particular control entirely useless for a range of applications and a rather glaring oversight from my perspective.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

奢欲 2024-07-14 00:17:56

您可以尝试此代码:

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-视觉样式-of-net-application/

you can try this code:

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

for more information:

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/

风筝在阴天搁浅。 2024-07-14 00:17:56

我在上面的 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;

    }
}

I found a small problem in Mark Cranness's code above: On XP systems that have visual styles disabled entirely, Application.RenderWithVisualStyles is then set to False even when Application.EnableVisualStyles() is called.

So the custom paint code does not run at all in that case. To fix it, I changed the first line of the FixVisualStylesMonthCalendar constructor to

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

Entire code is at the bottom of this answer.

I could not find any way to comment on the answer itself. Credits for below code go to the original author - (If he or anyone can verify this answer and update it I would be happy to remove this one)

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

    }
}
塔塔猫 2024-07-14 00:17:56

这是一个在显示超过一个月时有效的版本 (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。

Here is a version that does work when more than one month is displayed (CalendarDimensions != (1,1)), and fixes some other problems also:

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

    }

}

My testing shows that Windows 7 does not have the painting problem and I expect that neither does Vista, so this only attempts a fix for Windows XP.

假装不在乎 2024-07-14 00:17:54

在寻找同一问题的解决方案时,我第一次在这里遇到这个问题,但后来我发现了 尼克·安德森。 我发现这非常有帮助。
这是我根据尼克的例子所做的:

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

While looking for a solution to the same problem, I first encountered this question here, but later I discovered a blog entry by Nicke Andersson. which I found very helpful.
Here is what I made of Nicke's example:

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;
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文