日期、周和月日历控件
Windows 窗体日历控件,

引言
此控件集是适用于 Windows Forms .NET 应用程序的原型日历控件集。它们被创建为概念验证,表明可以在不使用第三方库的开销的情况下,将 Outlook 风格的日历集成到大型 Windows Forms 应用程序中。
包含三个控件 - DayScheduleControl
、WeekScheduleControl
和 MonthScheduleControl
。这些控件以不同的布局呈现一系列约会。我可能在不久的将来没有时间来增强它,所以我将发布它们,希望它们对其他人有用。
DayScheduleControl
在外观上最接近 Outlook。它支持一天视图和五天视图。WeekScheduleControl
显示一周(7 天)。与 DayScheduleControl
不同,它不显示小时槽。MonthScheduleControl
显示一个月的约会。
Using the Code
包含的测试项目中的 Form1
类包含控件的示例用法。首先,它包含创建随机约会列表的代码,并将控件设置为显示当前日期。CreateRandomAppointments
方法加载了几个月内的随机虚拟数据。
DateTime weekstart = DateTime.Now;
AppointmentList appts = CreateRandomAppointments(weekstart);
weekView1.Date = weekstart;
weekView1.Appointments = appts;
monthView1.Date = weekstart;
monthView1.Appointments = appts;
dayView1.Date = weekstart;
dayView1.Appointments = appts;
dayView2.Date = weekstart;
dayView2.Appointments = appts;
在正常应用中,您会创建 AppointmentList
并添加 Appointment
对象,而不是使用 CreateRandomAppointments
,如下所示,以您自己的数据作为来源。
var appts = new AppointmentList();
ExtendedAppointment app = new ExtendedAppointment();
app.ColorBlockBrush = Brushes.Red;
app.Subject = "A sample appointment";
app.DateStart = DateTime.Now.AddMinutes(30);
app.DateEnd = DateTime.Now.AddMinutes(60);
appts.Add(app);
其次,创建、移动和编辑事件被连接到演示方法。
weekView1.AppointmentCreate += calendar_AppointmentAdd;
monthView1.AppointmentCreate += calendar_AppointmentAdd;
dayView1.AppointmentCreate += calendar_AppointmentAdd;
dayView2.AppointmentCreate += calendar_AppointmentAdd;
weekView1.AppointmentMove += calendar_AppointmentMove;
monthView1.AppointmentMove += calendar_AppointmentMove;
dayView1.AppointmentMove += calendar_AppointmentMove;
dayView2.AppointmentMove += calendar_AppointmentMove;
weekView1.AppointmentEdit += calendar_AppointmentEdit;
monthView1.AppointmentEdit += calendar_AppointmentEdit;
dayView1.AppointmentEdit += calendar_AppointmentEdit;
dayView2.AppointmentEdit += calendar_AppointmentEdit;
当用户与日历约会交互时,会触发事件,因此我们会弹出一个自定义对话框来处理它。在这种情况下,NewAppointment
是一个用于输入约会标题、开始日期和结束日期的对话框。MoveAppointment
和 EditAppointment
对话框在功能上非常相似。
private void calendar_AppointmentAdd(object sender, AppointmentCreateEventArgs e)
{
//show a dialog to add an appointment
using (NewAppointment dialog = new NewAppointment())
{
if (e.Date != null)
{
dialog.AppointmentDateStart = e.Date.Value;
dialog.AppointmentDateEnd = e.Date.Value.AddMinutes(15);
}
DialogResult result = dialog.ShowDialog();
if (result == DialogResult.OK)
{
//if the user clicked 'save', save the new appointment
string title = dialog.AppointmentTitle;
DateTime dateStart = dialog.AppointmentDateStart;
DateTime dateEnd = dialog.AppointmentDateEnd;
e.Control.Appointments.Add(new ExtendedAppointment() {
Subject = title, DateStart = dateStart, DateEnd = dateEnd });
//have to tell the controls to refresh appointment display
weekView1.RefreshAppointments();
monthView1.RefreshAppointments();
dayView1.RefreshAppointments();
dayView2.RefreshAppointments();
//get the controls to repaint
weekView1.Invalidate();
monthView1.Invalidate();
dayView1.Invalidate();
dayView2.Invalidate();
}
}
}
ExtendedAppointment
类用于为约会添加附加属性。由于它与创建/移动/编辑对话框一起位于测试应用程序中,因此您可以向对话框添加更多字段,而无需深入了解 SheduleControls
程序集。
工作原理
这些控件的要求是
- 支持 Windows 7 风格
- 支持键盘访问
- 对残疾人可访问
- 不要占用太多内存
这三个控件的大部分代码都是相似的,它们都继承自 BaseScheduleControl
。此基控件处理拖放、约会的隐藏 DataGridView
以及鼠标单击事件。大部分代码用于将隐藏的 DataGridView
中的键盘操作链接到 UI 显示(例如,选定的约会),反之亦然,使用鼠标操作。
namespace Syd.ScheduleControls
{
/// <summary>
/// The BaseScheduleControl defines properties common to
/// the three schedule controls.
/// </summary>
public partial class BaseScheduleControl : Control,
System.ComponentModel.ISupportInitialize
{
BaseScheduleControl
有一个 DataGridView
控件,以节省设置键盘访问和可访问性功能的时间。grid
对象是 HiddenGrid
类型,它扩展了 DataGridView
但重写了 OnPaint
和 OnPaintBackground
事件,因此控件不可见。网格通过 AppointmentGrid
属性暴露给子控件。
internal class HiddenGrid : DataGridView
{
protected override void OnPaintBackground(PaintEventArgs pevent)
{
//Don't paint anything
}
protected override void OnPaint(PaintEventArgs e)
{
//Don't paint anything
}
}
VisualStyleRenderer
用于获取当前的 Windows 主题,并使用该主题的颜色进行显示。如果禁用了视觉样式,则使用常规的 Windows 颜色。所有渲染操作都被包装在 RendererCache
类中,该类绘制一切,包括日期、它们的标题以及约会。RendererCache
保存一系列 IRenderer
对象,这些对象是能够绘制带文本或边框的框的对象(控件上绘制的所有内容基本上都是一个框)。
internal class RendererCache
{
//...
private readonly IRenderer bigHeaderRenderWrap = null;
private readonly IRenderer headerRenderWrap = null;
private readonly IRenderer headerRenderSelWrap = null;
private readonly IRenderer appointmentRenderWrap = null;
private readonly IRenderer appointmentRenderSelWrap = null;
private readonly IRenderer controlRenderWrap = null;
private readonly IRenderer bodyRenderWrap = null;
private readonly IRenderer bodyLightRenderWrap = null;
根据是否启用了视觉样式,有不同的 IRenderer
实现。如果代码是以视觉样式开启的方式运行的,则标题的 IRenderer
将如下初始化(VisualStyleWrapper
实现了 IRenderer
)
private readonly VisualStyleRenderer headerRender = null;
private readonly VisualStyleElement headerElement =
VisualStyleElement.ExplorerBar.NormalGroupHead.Normal;
//...
headerRender = new VisualStyleRenderer(headerElement);
headerRenderWrap = new VisualStyleWrapper
(headerRender, SystemPens.ControlDarkDark);
但是,如果视觉样式被关闭,则会使用一个更简单的 IRenderer
实现,称为 NonVisualStyleWrapper
。NonVisualStyleWrapper
仅使用系统颜色画笔和一些渐变填充来渲染控件上的日期和约会。
headerRenderWrap = new NonVisualStyleWrapper(SystemColors.ControlText,
SystemBrushes.ControlText,
SystemColors.Control,
SystemBrushes.Control,
SystemColors.ControlLightLight,
SystemBrushes.ControlLightLight,
SystemPens.ControlText);
((NonVisualStyleWrapper)headerRenderWrap).NoGradientBlend=true;
RendererCache
是一个单例,在 OnPaint
事件中被所有三个控件使用。以下是如何使用它来绘制一天标题框的示例。
RendererCache.Current.Header.DrawBox
(e.Graphics, Font, day.TitleBounds, day.FormattedName);
速度是应用程序设计的主要考虑因素,因此事件的使用并不多。同样,日期和约会项本身并不是控件——渲染起来会很慢。
不使用大量控件,而是一次性计算日期/小时和约会的屏幕区域,并将它们全部由父控件绘制。日期被包装在 DayRegion
对象中,这些对象包含日期的名称及其边界。约会被包装在 AppointmentRegion
对象中,这些对象包含 Appointment
及其边界。IRegion
接口定义了所有这些区域的共同属性 - 边界。
internal interface IRegion
{
Rectangle Bounds { get; set; }
}
日期和小时区域的大小和形状由 CalculateTimeSlotBounds
方法确定。此方法仅在 BoundsValidTimeSlot
属性设置为 false
时(在控件大小调整或显示日期更改等情况下,此属性设置为 false
)从 OnPaint
调用。它在所有三个控件中都被重写,因为它们都有不同的日期布局。
protected override void CalculateTimeSlotBounds(Graphics g)
约会的大小和形状由 CalculateAppointmentBounds
方法确定。这确保了所有约会都尽可能适合其所属的日期或小时,并处理其他计算,例如重叠。此方法仅在 BoundsValidAppointment
属性设置为 false
时(在控件大小调整或约会列表更改等情况下,此属性设置为 false
)从 OnPaint
调用。
protected override void CalculateAppointmentBounds(Graphics g)
控件不包含创建/移动/编辑约会的默认对话框,但演示项目包含连接到这三个事件的示例对话框。
未来的增强
当前版本中缺少功能
- 控件上的更多内容应可在属性中配置
- 支持全天或多天约会
- 鼠标悬停在约会上方时显示工具提示(以显示完整主题)
- XP、高对比度模式、高 DPI 支持
DayScheduleControl
不支持周末、非工作时间约会或滚动- 控件未在屏幕阅读器下进行测试,
DataGridView
可能未设置可读属性 DayScheduleControl
处理重叠的约会,但其使用的数学运算效果不佳- 使用键盘或鼠标进行时间段导航/选择
- 更好地突出显示当前日期
历史
- 初始版本