我正在为FMX TGrid编写列和单元类,这些类将在每个单元格中包含TCalendarEdit和TTimeEdit实例。除了正确处理这些子控件中所做的更改外,一切都正常工作。
type
TFMTValue<T> = record
FieldValue: T;
Modified: boolean;
Appended: boolean;
Deleted: boolean;
end;
TDateTimeCell = class(TStyledControl)
private
FDate_Time: TFMTValue<TDateTime>;
procedure SetDateTime(const Value: TFMTValue<TDateTime>);
function GetDateTime: TFMTValue<TDateTime>;
protected
procedure SetData(const Value: TValue); override;
public
property Date_Time: TFMTValue<TDateTime> read GetDateTime write SetDateTime;
...
end;
...
function TDateTimeCell.GetDateTime: TFMTValue<TDateTime>;
begin
FDate_Time.Modified := (FDate_Time.Modified) or
(FDate_Time.FieldValue <> FCalendarEdit.Date +
+ FTimeEdit.Time);
FDate_Time.FieldValue := FCalendarEdit.Date + FTimeEdit.Time;
Result := FDate_Time;
end;
procedure TDateTimeCell.SetData(const Value: TValue);
begin
Date_Time := Value.AsType<TFMTValue<TDateTime>>;
inherited SetData(TValue.From<TDateTime>(FDate_Time.FieldValue));
ApplyStyling;
end;
procedure TDateTimeCell.SetDateTime(const Value: TFMTValue<TDateTime>);
begin
FDate_Time := Value;
FCalendarEdit.Date := DateOf(FDate_Time.FieldValue);
FTimeEdit.Time := TimeOF(FDate_Time.FieldValue);
FDate_Time.FieldValue:=FCalendarEdit.Date + FTimeEdit.Time; //this line helps but not in all cases
end;其思想是通过TGrid OnGetValue事件处理程序分配数据。同时显示日期和时间。捕获用户活动并设置Modified标志。问题是,即使没有任何用户活动,也会将此标志设置为true。我怀疑这是由于TDateTime时间的四舍五入造成的。代码没有其他方法将值分配给FCalendarEdit.Date和FTimeEdit.Time。
如何正确地比较存储在FCalendarEdit.Date和FTimeEdit.Time中的数据和存储在FDate_Time.FieldValue中的数据
附录
以这种方式设置标志并不能解决问题。
FDate_Time.Modified := (FDate_Time.Modified) or
(DateOf(FDate_Time.FieldValue) <> FCalendarEdit.Date) or
(TimeOf(FDate_Time.FieldValue)<> FTimeEdit.Time);附加了2.关于@Ken的宝贵建议。如果我们把比较线替换成
FDate_Time.Modified := (FDate_Time.Modified) or
(not SameDateTime(FDate_Time.FieldValue,
FCalendarEdit.Date + FTimeEdit.Time));效果很好。因此,TDataTime比较必须仅由该函数进行。
发布于 2015-10-06 19:26:33
TDateTime是type Double,这意味着它是一个浮点值,因此在不指定可接受的增量(差异)的情况下进行等式比较时会遇到二进制表示的常见问题。
特别是对于TDateTime值,您可以使用DateUtils.SameDateTime将等式降低到小于1毫秒:
FDate_Time.Modified := (FDate_Time.Modified) or
(not SameDateTime(FDate_Time.FieldValue,
FCalendarEdit.Date + FTimeEdit.Time));发布于 2015-10-07 01:09:02
TCalendarEdit中有一个bug (实际上有几个),这是您的问题的根本原因,但是您只需要对代码进行小的更改就可以修复它。
问题
TCalendarEdit在应用新的日期值时会产生许多关键错误。
TDate类型实际上只是一个普通的TDateTime,其中您应该忽略时间部分。类似地,TTime是一个TDateTime,您应该忽略日期部分。
但是,您必须在代码中正确地使用这些类型--没有什么可以神奇地使TTime忽略日期或TDate忽略时间。
例如,如果检查TCalendarEdit的构造函数,您将看到它将内部日期/时间初始化为当前系统的日期和时间,但截断它以消除time元素:
Date := Trunc(Now);到目前一切尚好。
但是,当您通过Date属性应用新值时,它执行以下操作(简化):
if Date <> Value then
FDateTime := Value + Time;这两行代码都包含严重的bug:
第一个bug导致对内部属性进行不必要的更改,但在其他方面相对无害。然而,第二个bug要严重得多,并且是导致您的问题的原因。
我假定控件的作者的意图是保持内部日期/时间值的时间部分不变。但是,值没有被截断,因此它保留了分配给属性中指定的时间值。更糟糕的是,该控件上没有 time 属性,因此这实际上将当前系统时间添加到值中指定的任何时间。
这如何影响您的代码和测试用例
因为您的测试用例涉及中午时间-- 12小时--结果是当您在下午运行此代码时,您的TCalendarEdit的Date实际上被设置为25-9.2015+ 12小时+控件初始化的时间。
如果您在早上运行代码,它似乎工作,因为时间增加的结果是一个值仍然是在9月25日。
但是,当您在下午运行代码时,将12个小时添加到当前时间,因此日期将在上滚动到第二天!
使用更有用的诊断错误消息,或者如果您通过调试器检查了代码中的属性,就会看到这种情况发生。
DT := EncodeDate(2015, 9, 25) + EncodeTime(12, 0, 0, 0);
CalendarEdit1.Date := DT;
ShowMessage(DateTimeToString(CalendarEdit1.Date));
// When executed at e.g. 9am, displays: 25 Sep 2015
// When executed at e.g. 1pm, displays: 26 Sep 2015因此,您的比较失败的原因是因为日期实际上是完全不同的!
如果您尝试简单地使用SameDateTime()进行比较,那么如果您在早上测试它,它可能已经成功了,但是您的问题会在下午再次出现!!
解决方案
您可以通过确保自己尊重属性值的预期使用来解决TCalendarEdit中的这些bug,只在每种情况下酌情分配DT日期/时间值的这些部分:
TimeEdit1.Time := TimeOf(DT);
CalendarEdit1.Date := DateOf(DT);虽然对于TCalendarEdit中的TTimeEdit来说,这并不是绝对必要的,但这将防止这些bug导致这些问题,并在代码中清楚地表明您知道需要什么(如果您愿意的话,可以考虑自己记录代码)。:)
如果在您的Delphi版本中没有TimeOf()和DateOf()函数,那么以下内容是等效的:
TimeEdit1.Time := DT - Trunc(DT);
CalendarEdit1.Date := Trunc(DT);当然,您可以在此基础上编写自己版本的TimeOf()和DateOf(),以使意图更加清晰。
备注
Delphi中日期/时间值的浮点性质可能会导致与日期和时间的某些特定值的直接比较出现问题,因此,强烈建议您使用SameDateTime()函数来执行这种比较。
但在这种情况下,这绝对是,而不是,是问题的原因,而做的不是,而是解决您的问题。
SameDateTime()消除了因日期/时间值小于1毫秒而产生的问题。这个案子的差别是24小时!
值得注意的是,TCalendarEdit控件在XE7中不受欢迎,并且已经从XE8中完全删除。
https://stackoverflow.com/questions/32975902
复制相似问题