日期范围选择的新概念:DateRangeComboBox
选择代表去年的两个日期可能需要11次点击或20次按键。为什么不在组合框中选择“去年”呢?
问题
构建报告组件时最常见的需求之一是指定开始和结束日期,以便您可以查看特定时间段的数据。这通常需要四个控件:两个标签和两个日期时间选择器。从用户的角度来看,这可能需要相当多的操作,选择一个或两个选择器,设置日、月和年。实际上,一些可用的日期时间选择器设计不佳,因此,如果没有提供用户友好的选择器,选择日期的过程可能会很费力且不直观。
在数据编辑的UI开发中也会出现类似的挑战。如果您想根据某种日期条件过滤或搜索数据,您可能需要提供类似的东西。
解决方案概述
当我们公司开发Habanero框架时,我们添加了一个巧妙的新控件,名为DateRangeComboBox
。它为用户提供了一组常见的日期范围,然后计算出适合所选范围的正确日期。该控件的设计前提是用户通常会根据常见的日期范围来筛选数据。
例如,用户思考:“去年的销售情况如何?”以前,他会选择两个日期选择器,并选择2007年1月1日和2007年12月31日。这总共需要大约11次点击,如果你直接输入日期,可能会少一些,大约是20次按键和3次点击。使用DateRangeComboBox
,用户只需选择下拉菜单,选择“去年”,然后点击“运行报告”。只需3次点击。
DateRangeComboBox
配备了许多标准日期范围,这些范围可以在可用选项列表中进行切换。开发人员还可以添加自己的个性化选项。
在其背景中
该控件作为Habanero(一个免费开源的.NET企业应用框架)的一部分发布。Habanero通过使用对象关系映射提供领域对象层,然后将这些对象服务于运行时生成的表示层,从而简化了开发流程。随着版本2的发布,Habanero扩展了其表示层,以支持从同一应用程序为多个环境生成。一篇CodeProject文章描述了如何使用Habanero为多个发布环境开发应用程序,可在此处访问这里。
此上下文的意义在于,逻辑已从控件中提取出来,因此您可以将给定代码调整为任何需要它的控件。提供的代码包括对WinForms ComboBox
和Visual WebGui ComboBox
的支持。
示例用法
我们来看一些简单的用法。以下代码实例化了控件,并说明了如何处理其输出(请注意,开始日期包含在内,结束日期不包含在内)
DateRangeComboBoxWin comboBox = new DateRangeComboBoxWin();
comboBox.SelectionChangeCommitted += delegate
{
DateTime inclusiveStartDate = comboBox.StartDate;
DateTime exclusiveEndDate = comboBox.EndDate;
//do something with the dates - create report, filter grid
};
日期范围的默认列表适用于不考虑时间的系统。您可以轻松指定自定义列表并将其传递给构造函数
List<DateRangeOptions> options = new List<DateRangeOptions>();
options.Add(DateRangeOptions.Today);
options.Add(DateRangeOptions.Current3Years);
DateRangeComboBoxWin comboBox = new DateRangeComboBoxWin(options);
您只需访问Items
属性并将文本字符串添加到ComboBox
即可添加自己的选项
DateRangeComboBoxWin comboBox = new DateRangeComboBoxWin();
string myOption = "Last 30,000 Years";
comboBox.Items.Add(myOption);
comboBox.SelectionChangeCommitted += delegate
{
DateTime start;
DateTime end;
if (comboBox.Text == myOption)
{
start = DateTime.Now.AddYears(-30000);
end = DateTime.Now;
}
else
{
start = comboBox.StartDate;
end = comboBox.EndDate;
}
//do something with the dates
};
附加功能
DateRangeComboBox
旨在适应多种自定义设置。例如,工厂的班次可能是早上6点到早上6点,这意味着“昨天”指的是这个时间段,而不是从午夜到午夜。这可以通过将 MidnightOffset
设置为正的 6 小时 TimeSpan
来实现。类似地,还包括了识别一周、一月或一年的不同起始日的属性。
通过将IgnoreTime
设置为false
,可以构建该控件以忽略时间,这将把所有DateTime
值都四舍五入到最近的午夜。还包含了一个选项,可以将固定的DateTime
作为“Now
”值来工作。虽然这对于测试是必要的,但它在应用程序中也可能有用途——只需将UseFixedNowDate
设置为true
,并将您选择的DateTime
值设置为FixedNowDate
,它将替换所有对DateTime.Now
的调用。
代码视图
本文随附的代码专门为Habanero框架设计。我们将首先查看一些代码,然后回顾在您的项目中如何使用或改编代码的不同方法。其中包含相当多的代码,所以我们暂时只做简要介绍。源代码包含完整的注释,包括所有方法、属性和枚举。
首先,IDateRangeComboBox
提供了基本的命令集。除了上面讨论的功能外,它还提供了修改选项列表或更改选项默认显示文本的实用工具,这显然对非英语受众的编程很有用。
public interface IDateRangeComboBox : IComboBox
{
List<DateRangeOptions> OptionsToDisplay { get; set; }
bool IgnoreTime { get; set; }
TimeSpan MidnightOffset { get; set; }
int WeekStartOffset { get; set; }
int MonthStartOffset { get; set; }
int YearStartOffset { get; set; }
bool UseFixedNowDate { get; set; }
DateTime FixedNowDate { get; set; }
void UseAllDateRangeOptions();
void SetTopComboBoxItem(string displayString);
string GetDateRangeString(DateRangeOptions option);
void SetDateRangeString(DateRangeOptions option, string newDisplayString);
void RemoveDateOption(DateRangeOptions option);
void AddDateOption(DateRangeOptions option);
DateTime StartDate { get; }
DateTime EndDate { get; }
}
DateRangeOptions
枚举中提供了一个标准日期范围列表,所有都带有相关逻辑
public enum DateRangeOptions
{
ThisHour,
PreviousHour,
Current60Minutes,
Today,
Yesterday,
Current24Hours,
ThisWeek,
PreviousWeek,
Previous7Days,
ThisMonth,
PreviousMonth,
Previous30Days,
Previous31Days,
ThisYear,
PreviousYear,
Previous365Days,
Current2Years,
Current3Years,
Current5Years,
Previous2Years,
Previous3Years,
Previous5Years
}
StartDate
和EndDate
属性调用CalculateDates
来计算与所选日期范围对应的日期。以下是计算某些日期范围的代码片段
switch (option)
{
case DateRangeOptions.ThisHour:
{
_startDate = HourStart(Now);
_endDate = Now;
break;
}
case DateRangeOptions.PreviousHour:
{
_startDate = HourStart(Now).AddHours(-1);
_endDate = HourStart(Now);
break;
}
case DateRangeOptions.PreviousMonth:
{
_startDate = MonthStart(Now).AddMonths(-1);
_endDate = MonthStart(Now);
break;
}
case DateRangeOptions.Previous5Years:
{
_startDate = YearStart(Now).AddYears(-5);
_endDate = YearStart(Now);
break;
}
default:
{
_startDate = DateTime.MinValue;
_endDate = DateTime.MaxValue;
break;
}
}
代码调用一个Now
属性,该属性提供DateTime.Now
或开发人员提供的固定now值。Hour
/Day
/Month
/YearStart
方法根据给定的DateTime
和开发人员提供的任何偏移量计算一个时间段的开始。这是一个例子
private DateTime YearStart(DateTime date)
{
DateTime first = new DateTime(date.Year, 1, 1);
first = first.AddMonths(YearStartOffset).
AddDays(MonthStartOffset).Add(MidnightOffset);
if (first > date)
{
first = first.AddYears(-1);
}
if (MidnightOffset < new TimeSpan(0, 0, 0, 0, 0))
{
DateTime closer = new DateTime(date.Year + 1, 1, 1);
closer = closer.AddMonths(YearStartOffset).
AddDays(MonthStartOffset).Add(MidnightOffset);
if (closer < date) return closer;
}
return first;
}
为您的项目适配代码
鉴于DateRangeComboBox
是Habanero特有的控件,有三种方法可以在您的项目中使用它。
首先,最简单的方法是引用此下载中包含的四个Habanero DLL(*.Base*、*.Util*、*.UI.Base*、*.UI.Win*)。此时,您可以简单地使用前面列出的示例用法代码。
其次,如果您的控件不是System.Windows.Forms.ComboBox
,那么您可以创建两个中间类。第一个可以是ComboBoxMine
之类的类,它继承自您的控件和Habanero的IComboBox
。第二个是DateRangeComboBoxMine
,它通过调用管理器密切模仿DateRangeComboBoxWin
,但继承自ComboBoxMine
。
最后,您可以调整给定的源代码,通过将IComboBox
类型替换为您自己的类型来编辑DateRangeComboBoxManager
。
进一步可能的用法
诚然,用户可能希望处理一些未提供的特定日期范围。在这种情况下,开发人员可以同时提供DateRangeComboBox
和开始/结束日期选择器。如果选择了日期范围,则选择器中的值可以自动调整。如果这种方法导致界面混乱,可以在下拉菜单底部添加一个额外的选项,如“自定义日期范围...”,使隐藏的选择器变得可见。这在基本情况下提供了简洁性,但仍允许那些需要全面控制的用户获得完全控制。
进一步改进
如果您足够细心,您会发现当前代码中存在一个潜在的缺陷:开始和结束日期每次调用其属性时都会重新计算。这意味着在获取开始和结束日期的调用之间可能存在一个很小的时间差,这可能会对时间敏感的数据产生影响。一个解决方案是修改代码,让开发人员首先调用一个类似CalculateDates
的方法,该方法会创建由StartDate
和EndDate
读取的固定值。另一种方法可以使用时间差检查来判断是否需要刷新这些值。第三种方法将要求开发人员直接调用CalculateDates
,通过引用传递开始和结束DateTime
参数,并在代码中对其进行修改。
如果您对这个控件有进一步的创新,我们非常乐意将其纳入Habanero未来的版本中。Habanero官方网站提供了论坛和支持功能,以便与开发团队沟通您的更改。