Я использую элемент управления MonthCalendar и хочу программно выбрать диапазон дат. Когда я это делаю, элемент управления не отрисовывается должным образом, если был вызван Application.EnableVisualStyles(). Согласно 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)
Неужели для этого нет другого решения, кроме как не вызывать EnableVisualStyles? Похоже, это делает этот конкретный элемент управления совершенно бесполезным для ряда приложений и, с моей точки зрения, довольно явный надзор.





В поисках решения той же проблемы я впервые столкнулся с этим вопросом здесь, но позже я обнаружил запись в блоге Ник Андерссон. что я нашел очень полезным. Вот что я сделал из примера Ника:
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;
}
}
}
Это не должно быть так сложно. Microsoft необходимо обновить свои элементы управления и предоставить людям современный пользовательский интерфейс, а не тот, который был 20 лет назад.
Вот версия, которая работает, когда отображается более одного месяца (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.
Я обнаружил небольшую проблему в приведенном выше коде Марка Крэннесса: в системах XP, в которых визуальные стили полностью отключены, Application.RenderWithVisualStyles затем устанавливается в значение False, даже когда вызывается Application.EnableVisualStyles ().
Таким образом, пользовательский код рисования в этом случае вообще не запускается. Чтобы исправить это, я изменил первую строку конструктора 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;
}
}
вы можете попробовать этот код:
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/
Кажется, работает, но onMouseLeave прямоугольник перекрашивается в цвет по умолчанию ... Что нужно изменить, чтобы прямоугольник оставался постоянным? Было бы неплохо узнать.