lazarus/components/datetimectrls/datetimepicker.pas

4204 lines
112 KiB
ObjectPascal

{
TDateTimePicker control for Lazarus
- - - - - - - - - - - - - - - - - - -
Author: Zoran Vučenović, January and February 2010
Зоран Вученовић, јануар и фебруар 2010.
This unit is part of DateTimeCtrls package for Lazarus.
Delphi's Visual Component Library (VCL) has a control named TDateTimePicker,
which I find very useful for editing dates. Lazarus Component Library (LCL),
however, does not have this control, because VCL wraps native Windows control
and it seems that such control does not exist on other platforms. Given that
LCL is designed to be platform independent, it could not use native Win control.
Instead, for editing dates LCL has a control named TDateEdit, but I prefer
the VCL's TDateTimePicker.
Therefore, I tried to create a custom control which would resemble VCL's
TDateTimePicker as much as possible, but not to rely on native Windows control.
This TDateTimePicker control does not use native Win control. It has been
tested on Windows with win32/64 and qt widgetsets, as well as on Linux with
qt and gtk2 widgetsets.
-----------------------------------------------------------
LICENCE
- - - -
Modified LGPL -- see the file COPYING.modifiedLGPL.
-----------------------------------------------------------
NO WARRANTY
- - - - - -
There is no warranty whatsoever.
-----------------------------------------------------------
BEST REGARDS TO LAZARUS COMMUNITY!
- - - - - - - - - - - - - - - - - -
I do hope this control will be useful.
}
unit DateTimePicker;
{$mode objfpc}{$H+}
interface
uses
{$if defined(UNIX) and not defined(OPENBSD)}
clocale, // needed to initialize default locale settings on Linux.
{$endif}
Types, Classes, SysUtils, Math,
// LCL
LCLType, LCLIntf, LMessages, Controls, Graphics, Buttons, ExtCtrls, Forms, ComCtrls,
Themes,
// LazUtils
LazUTF8, LazMethodList,
// DateTimeCtrls
CalControlWrapper;
const
{ We will deal with the NullDate value the special way. It will be especially
useful for dealing with null values from a database. }
NullDate = TDateTime(1.7e+308);
{ The biggest date a user can enter. }
TheBiggestDate = TDateTime(2958465.0); // 31. dec. 9999.
{ The smallest date a user can enter.
Note:
TCalendar does not accept smaller dates then 14. sep. 1752 on Windows OS
(see the implementation of TCustomCalendar.SetDateTime).
In Delphi help it is documented that Windows controls act weird with dates
older than 24. sep. 1752. Actually, TCalendar control has problems to show
dates before 1. okt. 1752. (try putting one calendar on the form, run the
application and see what september 1752. looks like).
Let's behave uniformly as much as
possible -- we won't allow dates before 1. okt. 1752. on any OS (who cares
about those).
So, this will be the lower limit: }
TheSmallestDate = TDateTime(-53780.0); // 1. okt. 1752.
var
DefaultCalendarWrapperClass: TCalendarControlWrapperClass = nil;
type
TYMD = record
Year, Month, Day: Word;
end;
THMSMs = record
Hour, Minute, Second, MiliSec: Word;
end;
{ Used by DateDisplayOrder property to determine the order to display date
parts -- d-m-y, m-d-y or y-m-d.
When ddoTryDefault is set, the actual order is determined from
ShortDateFormat global variable -- see comments above
AdjustEffectiveDateDisplayOrder procedure }
TDateDisplayOrder = (ddoDMY, ddoMDY, ddoYMD, ddoTryDefault);
TMonthDisplay = (mdShort, mdLong, mdCustom);
TTimeDisplay = (tdHM, // hour and minute
tdHMS, // hour, minute and second
tdHMSMs // hour, minute, second and millisecond
);
TTimeFormat = (tf12, // 12 hours format, with am/pm string
tf24 // 24 hours format
);
{ TDateTimeKind determines if we should display date, time or both: }
TDateTimeKind = (dtkDate, dtkTime, dtkDateTime);
TTextPart = 1..8;
TDateTimePart = (dtpDay, dtpMonth, dtpYear, dtpHour, dtpMinute,
dtpSecond, dtpMiliSec, dtpAMPM);
TDateTimeHidePart = dtpDay..dtpMiliSec; // without AMPM,
// because this set type is used for HideDateTimeParts property,
// where hiding of AMPM part is tied to hiding of hour (and, of
// course, it makes a difference only when TimeFormat is set to tf12)
TDateTimeParts = set of TDateTimeHidePart;
TEffectiveDateTimeParts = set of TDateTimePart;
TArrowShape = (asClassicSmaller, asClassicLarger, asModernSmaller,
asModernLarger, asYetAnotherShape, asTheme);
TDTDateMode = (dmComboBox, dmUpDown, dmNone);
{ calendar alignment - left or right,
dtaDefault means it is determined by BiDiMode }
TDTCalAlignment = (dtaLeft, dtaRight, dtaDefault);
TDateTimePickerOption = (
// The OnChange handler will be called also when date/time is programatically changed.
dtpoDoChangeOnSetDateTime,
// Enable the date time picker if the checkbox is unchecked.
dtpoEnabledIfUnchecked,
// Auto-check an unchecked checkbox when DateTime is changed
// (makes sense only if dtpoEnabledIfUnchecked is set).
dtpoAutoCheck,
// Use flat button for calender picker.
dtpoFlatButton,
// When the control receives focus, the selection is always
// in the first part (the control does not remember which part was previously selected).
dtpoResetSelection
);
TDateTimePickerOptions = set of TDateTimePickerOption;
{ TCustomDateTimePicker }
TCustomDateTimePicker = class(TCustomControl)
private const
cDefOptions = [];
cCheckBoxBorder = 3;
private
FAlignment: TAlignment;
FAutoAdvance: Boolean;
FAutoButtonSize: Boolean;
FCalAlignment: TDTCalAlignment;
FCalendarWrapperClass: TCalendarControlWrapperClass;
FCascade: Boolean;
FCenturyFrom, FEffectiveCenturyFrom: Word;
FChecked: Boolean;
FDateDisplayOrder: TDateDisplayOrder;
FHideDateTimeParts: TDateTimeParts;
FEffectiveHideDateTimeParts: TEffectiveDateTimeParts;
FKind: TDateTimeKind;
FLeadingZeros: Boolean;
FMonthDisplay: TMonthDisplay;
FMonthNames: String;
FInitiallyLoadedMonthNames: String; //remove if MonthNames property is removed
FMonthNamesArray: TMonthNameArray;
FCustomMonthNames: TStrings;
FNullInputAllowed: Boolean;
FDateTime: TDateTime;
FDateSeparator: String;
FReadOnly: Boolean;
FMaxDate, FMinDate: TDate;
FShowMonthNames: Boolean;
FTextForNullDate: TCaption;
FTimeSeparator: String;
FDecimalSeparator: String;
FTimeDisplay: TTimeDisplay;
FTimeFormat: TTimeFormat;
FTrailingSeparator: Boolean;
FUseDefaultSeparators: Boolean;
FUserChangedText: Boolean;
FYearPos, FDayPos, FMonthPos: 1..3;
FTextPart: array[1..3] of String;
FTimeText: array[dtpHour..dtpAMPM] of String;
FUserChanging: Integer;
FDigitWidth: Integer;
FTextHeight: Integer;
FSeparatorWidth: Integer;
FSepNoSpaceWidth: Integer;
FShowCheckBox: Boolean;
FMouseInCheckBox: Boolean;
FTimeSeparatorWidth: Integer;
FDecimalSeparatorWidth: Integer;
FMonthWidth: Integer;
FNullMonthText: String;
FSelectedTextPart: TTextPart;
FRecalculatingTextSizesNeeded: Boolean;
FJumpMinMax: Boolean;
FAMPMWidth: Integer;
FDateWidth: Integer;
FTimeWidth: Integer;
FTextWidth: Integer;
FArrowShape: TArrowShape;
FDateMode: TDTDateMode;
FTextEnabled: Boolean;
FUpDown: TCustomUpDown;
FOnChange: TNotifyEvent;
FOnCheckBoxChange: TNotifyEvent;
FOnChangeHandlers: TMethodList;
FOnCheckBoxChangeHandlers: TMethodList;
FOnDropDown: TNotifyEvent;
FOnCloseUp: TNotifyEvent;
FEffectiveDateDisplayOrder: TDateDisplayOrder;
FArrowButton: TCustomSpeedButton;
FCalendarForm: TCustomForm;
FDoNotArrangeControls: Boolean;
FConfirmedDateTime: TDateTime;
FNoEditingDone: Integer;
FAllowDroppingCalendar: Boolean;
FCorrectedDTP: TDateTimePart;
FCorrectedValue: Word;
FSkipChangeInUpdateDate: Integer;
FOptions: TDateTimePickerOptions;
function AreSeparatorsStored: Boolean;
function GetChecked: Boolean;
function GetDate: TDate;
function GetDateTime: TDateTime;
function GetDroppedDown: Boolean;
function GetTime: TTime;
procedure CustomMonthNamesChange(Sender: TObject);
procedure SetAlignment(AValue: TAlignment);
procedure SetArrowShape(const AValue: TArrowShape);
procedure SetAutoButtonSize(AValue: Boolean);
procedure SetCalAlignment(AValue: TDTCalAlignment);
procedure SetCalendarWrapperClass(AValue: TCalendarControlWrapperClass);
procedure SetCenturyFrom(const AValue: Word);
procedure SetChecked(const AValue: Boolean);
procedure CheckTextEnabled;
procedure SetCustomMonthNames(AValue: TStrings);
procedure SetDateDisplayOrder(const AValue: TDateDisplayOrder);
procedure SetDateMode(const AValue: TDTDateMode);
procedure SetDecimalSeparator(AValue: String);
procedure SetHideDateTimeParts(AValue: TDateTimeParts);
procedure SetKind(const AValue: TDateTimeKind);
procedure SetLeadingZeros(const AValue: Boolean);
procedure SetMonthDisplay(AValue: TMonthDisplay);
procedure SetMonthNames(AValue: String);
procedure SetNullInputAllowed(const AValue: Boolean);
procedure SetDate(const AValue: TDate);
procedure SetDateTime(const AValue: TDateTime);
procedure SetDateSeparator(const AValue: String);
procedure SetMaxDate(const AValue: TDate);
procedure SetMinDate(const AValue: TDate);
procedure SetReadOnly(const AValue: Boolean);
procedure SetShowCheckBox(const AValue: Boolean);
procedure SetShowMonthNames(AValue: Boolean);
procedure SetTextForNullDate(const AValue: TCaption);
procedure SetTime(const AValue: TTime);
procedure SetTimeSeparator(const AValue: String);
procedure SetTimeDisplay(const AValue: TTimeDisplay);
procedure SetTimeFormat(const AValue: TTimeFormat);
procedure SetTrailingSeparator(const AValue: Boolean);
procedure SetUseDefaultSeparators(const AValue: Boolean);
procedure RecalculateTextSizesIfNeeded;
function GetHMSMs(const NowIfNull: Boolean = False): THMSMs;
function GetYYYYMMDD(const TodayIfNull: Boolean = False;
const WithCorrection: Boolean = False): TYMD;
procedure SetHour(const AValue: Word);
procedure SetMiliSec(const AValue: Word);
procedure SetMinute(const AValue: Word);
procedure SetSecond(const AValue: Word);
procedure SetSeparators(const DateSep, TimeSep, DecSep: String);
procedure SetDay(const AValue: Word);
procedure SetMonth(const AValue: Word);
procedure SetYear(const AValue: Word);
procedure SetYYYYMMDD(const AValue: TYMD);
procedure SetHMSMs(const AValue: THMSMs);
procedure UpdateIfUserChangedText;
function GetSelectedText: String;
procedure AdjustSelection;
procedure AdjustEffectiveCenturyFrom;
procedure AdjustEffectiveDateDisplayOrder;
procedure AdjustEffectiveHideDateTimeParts;
procedure SelectDateTimePart(const DateTimePart: TDateTimePart);
procedure MoveSelectionLR(const ToLeft: Boolean);
procedure DestroyCalendarForm;
procedure UpdateShowArrowButton;
procedure DestroyUpDown;
procedure DestroyArrowBtn;
procedure ArrowMouseDown(Sender: TObject; {%H-}Button: TMouseButton;
{%H-}Shift: TShiftState; {%H-}X, {%H-}Y: Integer);
procedure UpDownClick(Sender: TObject; Button: TUDBtnType);
procedure SetFocusIfPossible;
procedure AutoResizeButton;
procedure CheckAndApplyKey(const Key: Char);
procedure CheckAndApplyKeyCode(var Key: Word; const ShState: TShiftState);
procedure SetOptions(const aOptions: TDateTimePickerOptions);
protected
procedure WMKillFocus(var Message: TLMKillFocus); message LM_KILLFOCUS;
procedure WMSize(var Message: TLMSize); message LM_SIZE;
class function GetControlClassDefaultSize: TSize; override;
procedure ConfirmChanges; virtual;
procedure UndoChanges; virtual;
procedure DropDownCalendarForm;
function GetCheckBoxRect(IgnoreRightToLeft: Boolean = False): TRect;
function GetDateTimePartFromTextPart(TextPart: TTextPart): TDateTimePart;
function GetSelectedDateTimePart: TDateTimePart;
procedure FontChanged(Sender: TObject); override;
function GetTextOrigin(IgnoreRightToLeft: Boolean = False): TPoint;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
procedure KeyPress(var Key: char); override;
procedure SelectTextPartUnderMouse(XMouse: Integer);
procedure MouseLeave; override;
procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
function DoMouseWheel({%H-}Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint): Boolean; override;
procedure UpdateDate(const CallChangeFromSetDateTime: Boolean = False); virtual;
procedure DoEnter; override;
procedure DoExit; override;
procedure Click; override;
procedure DblClick; override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
procedure KeyUp(var Key: Word; Shift: TShiftState); override;
procedure UTF8KeyPress(var UTF8Key: TUTF8Char); override;
procedure CalculatePreferredSize(var PreferredWidth,
PreferredHeight: integer; {%H-}WithThemeSpace: Boolean); override;
procedure SetBiDiMode(AValue: TBiDiMode); override;
procedure Loaded; override;
procedure IncreaseCurrentTextPart;
procedure DecreaseCurrentTextPart;
procedure IncreaseMonth;
procedure IncreaseYear;
procedure IncreaseDay;
procedure DecreaseMonth;
procedure DecreaseYear;
procedure DecreaseDay;
procedure IncreaseHour;
procedure IncreaseMinute;
procedure IncreaseSecond;
procedure IncreaseMiliSec;
procedure DecreaseHour;
procedure DecreaseMinute;
procedure DecreaseSecond;
procedure DecreaseMiliSec;
procedure ChangeAMPM;
procedure SelectDay;
procedure SelectMonth;
procedure SelectYear;
procedure SelectHour;
procedure SelectMinute;
procedure SelectSecond;
procedure SelectMiliSec;
procedure SelectAMPM;
procedure SetEnabled(Value: Boolean); override;
procedure SetAutoSize(Value: Boolean); override;
procedure CreateWnd; override;
procedure SetDateTimeJumpMinMax(const AValue: TDateTime);
procedure ArrangeCtrls; virtual;
procedure Change; virtual;
procedure CheckBoxChange; virtual;
procedure DoDropDown; virtual;
procedure DoCloseUp; virtual;
procedure DoAutoCheck; virtual;
procedure DoAutoAdjustLayout(const AMode: TLayoutAdjustmentPolicy;
const AXProportion, AYProportion: Double); override;
procedure AddHandlerOnChange(const AOnChange: TNotifyEvent;
AsFirst: Boolean = False); virtual;
procedure AddHandlerOnCheckBoxChange(const AOnCheckBoxChange: TNotifyEvent;
AsFirst: Boolean = False); virtual;
procedure RemoveHandlerOnChange(AOnChange: TNotifyEvent); virtual;
procedure RemoveHandlerOnCheckBoxChange(AOnCheckBoxChange: TNotifyEvent); virtual;
property EffectiveHideDateTimeParts: TEffectiveDateTimeParts read FEffectiveHideDateTimeParts;
property EffectiveDateDisplayOrder: TDateDisplayOrder read FEffectiveDateDisplayOrder;
property BorderStyle default bsSingle;
property AutoSize default True;
property TabStop default True;
property ParentColor default False;
property CenturyFrom: Word
read FCenturyFrom write SetCenturyFrom;
property DateDisplayOrder: TDateDisplayOrder
read FDateDisplayOrder write SetDateDisplayOrder default ddoTryDefault;
property MaxDate: TDate
read FMaxDate write SetMaxDate;
property MinDate: TDate
read FMinDate write SetMinDate;
property DateTime: TDateTime read GetDateTime write SetDateTime;
property TrailingSeparator: Boolean
read FTrailingSeparator write SetTrailingSeparator;
property ReadOnly: Boolean read FReadOnly write SetReadOnly default False;
property LeadingZeros: Boolean read FLeadingZeros write SetLeadingZeros;
property TextForNullDate: TCaption
read FTextForNullDate write SetTextForNullDate nodefault;
property NullInputAllowed: Boolean
read FNullInputAllowed write SetNullInputAllowed default True;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
property OnCheckBoxChange: TNotifyEvent
read FOnCheckBoxChange write FOnCheckBoxChange;
property OnDropDown: TNotifyEvent read FOnDropDown write FOnDropDown;
property OnCloseUp: TNotifyEvent read FOnCloseUp write FOnCloseUp;
property ShowCheckBox: Boolean
read FShowCheckBox write SetShowCheckBox default False;
property Checked: Boolean read GetChecked write SetChecked default True;
property ArrowShape: TArrowShape
read FArrowShape write SetArrowShape default asTheme;
property Kind: TDateTimeKind
read FKind write SetKind;
property DateSeparator: String
read FDateSeparator write SetDateSeparator stored AreSeparatorsStored;
property TimeSeparator: String
read FTimeSeparator write SetTimeSeparator stored AreSeparatorsStored;
property DecimalSeparator: String
read FDecimalSeparator write SetDecimalSeparator stored AreSeparatorsStored;
property UseDefaultSeparators: Boolean
read FUseDefaultSeparators write SetUseDefaultSeparators;
property TimeFormat: TTimeFormat read FTimeFormat write SetTimeFormat;
property TimeDisplay: TTimeDisplay read FTimeDisplay write SetTimeDisplay;
property Time: TTime read GetTime write SetTime;
property Date: TDate read GetDate write SetDate;
property DateMode: TDTDateMode read FDateMode write SetDateMode;
property Cascade: Boolean read FCascade write FCascade default False;
property AutoButtonSize: Boolean
read FAutoButtonSize write SetAutoButtonSize default False;
property AutoAdvance: Boolean
read FAutoAdvance write FAutoAdvance default True;
property HideDateTimeParts: TDateTimeParts
read FHideDateTimeParts write SetHideDateTimeParts;
property CalendarWrapperClass: TCalendarControlWrapperClass
read FCalendarWrapperClass write SetCalendarWrapperClass;
property MonthDisplay: TMonthDisplay read FMonthDisplay write SetMonthDisplay default mdLong;
property MonthNames: String read FMonthNames write SetMonthNames;
property CustomMonthNames: TStrings read FCustomMonthNames write SetCustomMonthNames;
property ShowMonthNames: Boolean
read FShowMonthNames write SetShowMonthNames default False;
property DroppedDown: Boolean read GetDroppedDown;
property CalAlignment: TDTCalAlignment read FCalAlignment write SetCalAlignment default dtaDefault;
property Alignment: TAlignment read FAlignment write SetAlignment default taLeftJustify;
property Options: TDateTimePickerOptions read FOptions write SetOptions default cDefOptions;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function DateIsNull: Boolean;
procedure SelectDate;
procedure SelectTime;
procedure SendExternalKey(const aKey: Char);
procedure SendExternalKeyCode(const Key: Word);
procedure RemoveAllHandlersOfObject(AnObject: TObject); override;
procedure Paint; override;
procedure EditingDone; override;
published
//
end;
{TDateTimePicker}
TDateTimePicker = class(TCustomDateTimePicker)
public
procedure AddHandlerOnChange(const AOnChange: TNotifyEvent; AsFirst: Boolean = False
); override;
procedure AddHandlerOnCheckBoxChange(const AOnCheckBoxChange: TNotifyEvent;
AsFirst: Boolean = False); override;
procedure RemoveHandlerOnChange(AOnChange: TNotifyEvent); override;
procedure RemoveHandlerOnCheckBoxChange(AOnCheckBoxChange: TNotifyEvent); override;
public
property DateTime;
property CalendarWrapperClass;
property DroppedDown;
published
property ArrowShape;
property ShowCheckBox;
property Checked;
property CenturyFrom;
property DateDisplayOrder;
property MaxDate;
property MinDate;
property ReadOnly;
property AutoSize;
property Font;
property ParentFont;
property TabOrder;
property TabStop;
property BorderStyle;
property BorderSpacing;
property Enabled;
property Color;
property ParentColor;
property DateSeparator;
property TrailingSeparator;
property TextForNullDate;
property LeadingZeros;
property ShowHint;
property ParentShowHint;
property Align;
property Anchors;
property Constraints;
property Cursor;
property PopupMenu;
property Visible;
property NullInputAllowed;
property Kind;
property TimeSeparator;
property DecimalSeparator;
property TimeFormat;
property TimeDisplay;
property DateMode;
property Date;
property Time;
property UseDefaultSeparators;
property Cascade;
property AutoButtonSize;
property AutoAdvance;
property HideDateTimeParts;
property BiDiMode;
property ParentBiDiMode;
property MonthNames; deprecated 'Use MonthDisplay in conjunction with CustomMonthNames property';
property MonthDisplay;
property CustomMonthNames;
property ShowMonthNames;
property CalAlignment;
property Alignment;
property Options;
// events:
property OnChange;
property OnCheckBoxChange;
property OnDropDown;
property OnCloseUp;
property OnChangeBounds;
property OnClick;
property OnContextPopup;
property OnDblClick;
property OnEditingDone;
property OnEnter;
property OnExit;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnMouseDown;
property OnMouseEnter;
property OnMouseLeave;
property OnMouseMove;
property OnMouseUp;
property OnMouseWheel;
property OnMouseWheelDown;
property OnMouseWheelUp;
property OnResize;
property OnShowHint;
property OnUTF8KeyPress;
end;
function EqualDateTime(const A, B: TDateTime): Boolean;
function IsNullDate(DT: TDateTime): Boolean;
function dbgs(const Parts: TDateTimeParts): string;
implementation
uses
DateUtils, LCLCalWrapper;
const
DefaultUpDownWidth = 15;
DefaultArrowButtonWidth = DefaultUpDownWidth + 2;
function NumberOfDaysInMonth(const Month, Year: Word): Word;
begin
Result := 0;
if Month in [1..12] then
Result := MonthDays[IsLeapYear(Year), Month];
end;
{ EqualDateTime
--------------
Returns True when two dates are equal or both are null }
function EqualDateTime(const A, B: TDateTime): Boolean;
begin
if IsNullDate(A) then
Result := IsNullDate(B)
else
Result := (not IsNullDate(B)) and (A = B);
end;
function IsNullDate(DT: TDateTime): Boolean;
begin
Result := IsNan(DT) or IsInfinite(DT) or
(DT > SysUtils.MaxDateTime) or (DT < SysUtils.MinDateTime);
end;
function dbgs(const Parts: TDateTimeParts): string;
var
s: string;
Part: TDateTimePart;
begin
Result:='';
for Part:=low(TDateTimeParts) to high(TDateTimeParts) do
if Part in Parts then
begin
if Result<>'' then Result:=Result+',';
str(Part,s);
Result:=Result+s;
end;
Result:='['+Result+']';
end;
type
{ TDTUpDown }
{ The two buttons contained by UpDown control are never disabled in original
UpDown class. This class is defined here to override this behaviour. }
TDTUpDown = class(TCustomUpDown)
private
DTPicker: TCustomDateTimePicker;
protected
procedure SetEnabled(Value: Boolean); override;
procedure CalculatePreferredSize(var PreferredWidth,
PreferredHeight: integer; WithThemeSpace: Boolean); override;
procedure WndProc(var Message: TLMessage); override;
end;
{ TDTSpeedButton }
TDTSpeedButton = class(TCustomSpeedButton)
private
DTPicker: TCustomDateTimePicker;
protected
procedure CalculatePreferredSize(var PreferredWidth,
PreferredHeight: integer; WithThemeSpace: Boolean); override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
override;
public
procedure Paint; override;
end;
{ TDTCalendarForm }
TDTCalendarForm = class(TForm)
private
DTPicker: TCustomDateTimePicker;
Cal: TCalendarControlWrapper;
Shape: TShape;
RememberedCalendarFormOrigin: TPoint;
FClosing: Boolean;
DTPickersParentForm: TCustomForm;
procedure SetClosingCalendarForm;
procedure AdjustCalendarFormSize;
procedure AdjustCalendarFormScreenPosition;
procedure CloseCalendarForm(const AndSetTheDate: Boolean = False);
procedure CalendarResize(Sender: TObject);
procedure CalendarClick(Sender: TObject);
procedure VisibleOfParentChanged(Sender: TObject);
protected
procedure Deactivate; override;
procedure DoShow; override;
procedure DoClose(var CloseAction: TCloseAction); override;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
procedure WMActivate(var Message: TLMActivate); message LM_ACTIVATE;
public
constructor CreateNewDTCalendarForm(AOwner: TComponent;
ADTPicker: TCustomDateTimePicker);
destructor Destroy; override;
published
end;
{ TDateTimePicker }
procedure TDateTimePicker.AddHandlerOnChange(const AOnChange: TNotifyEvent;
AsFirst: Boolean);
begin
inherited AddHandlerOnChange(AOnChange, AsFirst);
end;
procedure TDateTimePicker.AddHandlerOnCheckBoxChange(
const AOnCheckBoxChange: TNotifyEvent; AsFirst: Boolean);
begin
inherited AddHandlerOnCheckBoxChange(AOnCheckBoxChange, AsFirst);
end;
procedure TDateTimePicker.RemoveHandlerOnChange(AOnChange: TNotifyEvent);
begin
inherited RemoveHandlerOnChange(AOnChange);
end;
procedure TDateTimePicker.RemoveHandlerOnCheckBoxChange(
AOnCheckBoxChange: TNotifyEvent);
begin
inherited RemoveHandlerOnCheckBoxChange(AOnCheckBoxChange);
end;
procedure TDTCalendarForm.SetClosingCalendarForm;
begin
if not FClosing then begin
FClosing := True;
if Assigned(DTPicker) and (DTPicker.FCalendarForm = Self) then
DTPicker.FCalendarForm := nil;
end;
end;
procedure TDTCalendarForm.AdjustCalendarFormSize;
begin
if not FClosing then begin
ClientWidth := Cal.GetCalendarControl.Width + 2;
ClientHeight := Cal.GetCalendarControl.Height + 2;
Shape.SetBounds(0, 0, ClientWidth, ClientHeight);
AdjustCalendarFormScreenPosition;
end;
end;
procedure TDTCalendarForm.AdjustCalendarFormScreenPosition;
var
M: TMonitor;
R: TRect;
P: TPoint;
H, W: Integer;
begin
H := Height;
W := Width;
if (DTPicker.CalAlignment = dtaRight) or
((DTPicker.CalAlignment = dtaDefault) and IsRightToLeft) then
P := DTPicker.ControlToScreen(Point(DTPicker.Width - W, DTPicker.Height))
else
P := DTPicker.ControlToScreen(Point(0, DTPicker.Height));
M := Screen.MonitorFromWindow(DTPicker.Handle);
R := M.WorkareaRect;
// WorkareaRect sometimes is not implemented (gtk2?). Depends on widgetset
// and window manager or something like that. Then it returns (0,0,0,0) and
// the best we can do is use BoundsRect instead:
if (R.Right <= R.Left) or (R.Bottom <= R.Top) then
R := M.BoundsRect;
if P.y > R.Bottom - H then
P.y := P.y - H - DTPicker.Height;
if P.y < R.Top then
P.y := R.Top;
if P.x > R.Right - W then
P.x := R.Right - W;
if P.x < R.Left then
P.x := R.Left;
if (P.x <> RememberedCalendarFormOrigin.x)
or (P.y <> RememberedCalendarFormOrigin.y) then begin
SetBounds(P.x, P.y, W, H);
RememberedCalendarFormOrigin := P;
end;
end;
procedure TDTCalendarForm.CloseCalendarForm(const AndSetTheDate: Boolean);
begin
if not FClosing then
try
SetClosingCalendarForm;
Visible := False;
if Assigned(DTPicker) and DTPicker.IsVisible then begin
if AndSetTheDate then begin
Inc(DTPicker.FUserChanging);
try
DTPicker.SetDate(Cal.GetDate);
DTPicker.DoAutoCheck;
finally
Dec(DTPicker.FUserChanging);
end;
end;
if Screen.ActiveCustomForm = Self then
DTPicker.SetFocusIfPossible;
DTPicker.DoCloseUp;
end;
finally
Release;
end;
end;
procedure TDTCalendarForm.KeyDown(var Key: Word; Shift: TShiftState);
var
ApplyTheDate: Boolean;
begin
inherited KeyDown(Key, Shift);
case Key of
VK_ESCAPE, VK_RETURN, VK_SPACE, VK_TAB:
if Cal.InMonthView then begin
ApplyTheDate := Key in [VK_RETURN, VK_SPACE];
Key := 0;
CloseCalendarForm(ApplyTheDate);
end;
VK_UP:
if Shift = [ssAlt] then begin
Key := 0;
CloseCalendarForm;
end;
// Suppress Alt (not doing so can produce SIGSEGV on Win widgetset ?!)
VK_MENU, VK_LMENU, VK_RMENU:
Key := 0;
end;
end;
procedure TDTCalendarForm.CalendarResize(Sender: TObject);
begin
AdjustCalendarFormSize;
end;
procedure TDTCalendarForm.CalendarClick(Sender: TObject);
var
P: TPoint;
begin
P := Cal.GetCalendarControl.ScreenToClient(Mouse.CursorPos);
if Cal.AreCoordinatesOnDate(P.x, P.y) then
CloseCalendarForm(True);
end;
{ This procedure is added to list of "visible change handlers" of DTPicker's
parent form, so that hiding of DTPicker's parent form does not leave the
calendar form visible. }
procedure TDTCalendarForm.VisibleOfParentChanged(Sender: TObject);
begin
SetClosingCalendarForm;
Release;
end;
procedure TDTCalendarForm.WMActivate(var Message: TLMActivate);
var
PP: HWND;
begin
inherited WMActivate(Message);
PP := LCLIntf.GetParent(Handle);
if PP <> 0 then
SendMessage(PP, LM_NCACTIVATE, Ord(Message.Active <> WA_INACTIVE), 0);
end;
procedure TDTCalendarForm.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (AComponent = DTPickersParentForm) and (Operation = opRemove) then
DTPickersParentForm := nil;
end;
procedure TDTCalendarForm.Deactivate;
begin
inherited Deactivate;
CloseCalendarForm;
end;
procedure TDTCalendarForm.DoShow;
begin
if not FClosing then begin
inherited DoShow;
AdjustCalendarFormSize;
DTPicker.DoDropDown; // calls OnDropDown event handler
end;
end;
procedure TDTCalendarForm.DoClose(var CloseAction: TCloseAction);
begin
SetClosingCalendarForm;
CloseAction := caFree;
inherited DoClose(CloseAction);
end;
constructor TDTCalendarForm.CreateNewDTCalendarForm(AOwner: TComponent;
ADTPicker: TCustomDateTimePicker);
var
P: TPoint;
CalClass: TCalendarControlWrapperClass;
begin
inherited CreateNew(AOwner);
ADTPicker.FAllowDroppingCalendar := False;
FClosing := False;
DTPicker := ADTPicker;
BiDiMode := DTPicker.BiDiMode;
DTPickersParentForm := GetParentForm(DTPicker);
if Assigned(DTPickersParentForm) then begin
DTPickersParentForm.AddHandlerOnVisibleChanged(@VisibleOfParentChanged);
DTPickersParentForm.FreeNotification(Self);
PopupParent := DTPickersParentForm;
PopupMode := pmExplicit;
end else
PopupMode := pmAuto;
P := Point(0, 0);
if ADTPicker.FCalendarWrapperClass = nil then begin
if DefaultCalendarWrapperClass = nil then
CalClass := TLCLCalendarWrapper
else
CalClass := DefaultCalendarWrapperClass;
end else
CalClass := ADTPicker.FCalendarWrapperClass;
Cal := CalClass.Create;
Cal.GetCalendarControl.ParentBiDiMode := True;
Cal.GetCalendarControl.AutoSize := True;
Cal.GetCalendarControl.GetPreferredSize(P.x, P.y);
Cal.GetCalendarControl.Align := alNone;
Cal.GetCalendarControl.SetBounds(1, 1, P.x, P.y);
SetBounds(-8000, -8000, P.x + 2, P.y + 2);
RememberedCalendarFormOrigin := Point(-8000, -8000);
ShowInTaskBar := stNever;
BorderStyle := bsNone;
Shape := TShape.Create(nil);
Shape.Brush.Style := bsClear;
if DTPicker.DateIsNull then
Cal.SetDate(Max(DTPicker.MinDate, Min(SysUtils.Date, DTPicker.MaxDate)))
else if DTPicker.DateTime < DTPicker.MinDate then // These "out of bounds" values
Cal.SetDate(DTPicker.MinDate) // can happen when DateTime was set with
else if DTPicker.DateTime > DTPicker.MaxDate then // "SetDateTimeJumpMinMax" protected
Cal.SetDate(DTPicker.MaxDate) // procedure (used in TDBDateTimePicker control).
else
Cal.SetDate(DTPicker.Date);
Cal.GetCalendarControl.OnResize := @CalendarResize;
Cal.GetCalendarControl.OnClick := @CalendarClick;
if Cal.GetCalendarControl is TWinControl then begin
TWinControl(Cal.GetCalendarControl).TabStop := True;
TWinControl(Cal.GetCalendarControl).SetFocus;
end;
Self.KeyPreview := True;
Shape.Parent := Self;
Cal.GetCalendarControl.Parent := Self;
Cal.GetCalendarControl.BringToFront;
end;
destructor TDTCalendarForm.Destroy;
begin
SetClosingCalendarForm;
if Assigned(DTPickersParentForm) then
DTPickersParentForm.RemoveAllHandlersOfObject(Self);
FreeAndNil(Cal);
FreeAndNil(Shape);
if Assigned(DTPicker) then begin
if Screen.ActiveControl = DTPicker then
DTPicker.Invalidate;
if DTPicker.FCalendarForm = nil then
DTPicker.FAllowDroppingCalendar := True;
end;
inherited Destroy;
end;
{ TCustomDateTimePicker }
procedure TCustomDateTimePicker.SetChecked(const AValue: Boolean);
begin
if (FChecked = AValue) or not FShowCheckBox then
Exit;
FChecked := AValue;
CheckBoxChange;
CheckTextEnabled;
Invalidate;
end;
procedure TCustomDateTimePicker.CheckTextEnabled;
begin
FTextEnabled := Self.Enabled and ((dtpoEnabledIfUnchecked in Options) or GetChecked);
if Assigned(FArrowButton) then
FArrowButton.Enabled := FTextEnabled;
if Assigned(FUpDown) then
FUpDown.Enabled := FTextEnabled;
end;
procedure TCustomDateTimePicker.SetCustomMonthNames(AValue: TStrings);
begin
if (AValue <> nil) then
FCustomMonthNames.Assign(AValue);
end;
procedure TCustomDateTimePicker.SetDateDisplayOrder(
const AValue: TDateDisplayOrder);
var
PreviousEffectiveDDO: TDateDisplayOrder;
begin
if FDateDisplayOrder <> AValue then begin
PreviousEffectiveDDO := FEffectiveDateDisplayOrder;
FDateDisplayOrder := AValue;
AdjustEffectiveDateDisplayOrder;
if FEffectiveDateDisplayOrder <> PreviousEffectiveDDO then begin
AdjustSelection;
UpdateDate;
end;
end;
end;
procedure TCustomDateTimePicker.SetDateMode(const AValue: TDTDateMode);
begin
FDateMode := AValue;
UpdateShowArrowButton;
end;
procedure TCustomDateTimePicker.SetDecimalSeparator(AValue: String);
begin
SetSeparators(FDateSeparator, FTimeSeparator, AValue);
end;
procedure TCustomDateTimePicker.SetHideDateTimeParts(AValue: TDateTimeParts);
begin
if FHideDateTimeParts <> AValue then begin
FHideDateTimeParts := AValue;
AdjustEffectiveHideDateTimeParts;
end;
end;
procedure TCustomDateTimePicker.SetKind(const AValue: TDateTimeKind);
begin
if FKind <> AValue then begin
FKind := AValue;
AdjustEffectiveHideDateTimeParts;
end;
end;
procedure TCustomDateTimePicker.SetLeadingZeros(const AValue: Boolean);
begin
if FLeadingZeros = AValue then Exit;
FLeadingZeros := AValue;
UpdateDate;
end;
procedure TCustomDateTimePicker.SetMonthDisplay(AValue: TMonthDisplay);
var
i: Integer;
begin
if FMonthDisplay = AValue then Exit;
FMonthDisplay := AValue;
case AValue of
mdLong:
begin
for i := Low(TMonthNameArray) to High(TMonthNameArray) do
FMonthNamesArray[i] := AnsiToUtf8(DefaultFormatSettings.LongMonthNames[i]);
end;
mdShort:
begin
for i := Low(TMonthNameArray) to High(TMonthNameArray) do
FMonthNamesArray[i] := AnsiToUtf8(DefaultFormatSettings.ShortMonthNames[i]);
end;
mdCustom:
begin
for i := 0 to Min(FCustomMonthNames.Count - 1,11) do
begin
if (Trim(FCustomMonthNames[i]) <> '') then
FMonthNamesArray[i+1] := FCustomMonthNames[i]
else
//disallow empty names, default to long names in that case
FMonthNamesArray[i+1] := AnsiToUtf8(DefaultFormatSettings.LongMonthNames[i+1]);
end;
for i := FCustomMonthNames.Count to 11 do
FMonthNamesArray[i+1] := AnsiToUtf8(DefaultFormatSettings.LongMonthNames[i+1]);
end;
end;
if FShowMonthNames and not (dtpMonth in FEffectiveHideDateTimeParts) then
begin
FRecalculatingTextSizesNeeded := True;
UpdateDate;
end;
end;
procedure TCustomDateTimePicker.SetMonthNames(AValue: String);
var
i: Integer;
MonthNamesSeparator: String;
Arr: TStringArray;
begin
AValue := TrimRight(AValue);
if (csLoading in ComponentState) then
begin
FInitiallyLoadedMonthNames := AValue;
Exit;
end;
if FMonthNames <> AValue then
begin
FMonthNames := AValue;
if CompareText(AValue, 'SHORT') = 0 then
SetMonthDisplay(mdShort)
else
begin
if Length(AValue) >= 24 then
begin
MonthNamesSeparator := UTF8Copy(AValue, 1, 1);
Arr := AValue.Split([MonthNamesSeparator], TStringSplitOptions.ExcludeEmpty);
//only apply if at least 12 names are specified
if (Length(Arr) >= 12) then
begin
FCustomMonthNames.BeginUpdate;
FCustomMonthNames.Clear;
for i := Low(Arr) to High(Arr) do FCustomMonthNames.Add(Arr[i]);
FCustomMonthNames.EndUpdate;
SetMonthDisplay(mdCustom);
end
else
SetMonthDisplay(mdLong);
end
else
begin
SetMonthDisplay(mdLong);
end;
end;
end;
end;
procedure TCustomDateTimePicker.SetNullInputAllowed(const AValue: Boolean);
begin
FNullInputAllowed := AValue;
end;
procedure TCustomDateTimePicker.SetOptions(
const aOptions: TDateTimePickerOptions);
begin
if FOptions = aOptions then Exit;
FOptions := aOptions;
if FArrowButton <> nil then
FArrowButton.Flat := dtpoFlatButton in Options;
if FUpDown <> nil then
TDTUpDown(FUpDown).Flat := dtpoFlatButton in Options;
CheckTextEnabled;
Invalidate;
end;
procedure TCustomDateTimePicker.SetDate(const AValue: TDate);
begin
if IsNullDate(AValue) then
DateTime := NullDate
else if DateIsNull then
DateTime := Int(AValue)
else
DateTime := ComposeDateTime(AValue, FDateTime);
end;
procedure TCustomDateTimePicker.SetDateTime(const AValue: TDateTime);
begin
if not EqualDateTime(AValue, FDateTime) then begin
if IsNullDate(AValue) then
FDateTime := NullDate
else
FDateTime := AValue;
UpdateDate(dtpoDoChangeOnSetDateTime in FOptions);
end else
UpdateDate;
end;
procedure TCustomDateTimePicker.SetDateSeparator(const AValue: String);
begin
SetSeparators(AValue, FTimeSeparator, FDecimalSeparator);
end;
procedure TCustomDateTimePicker.SetMaxDate(const AValue: TDate);
begin
if not IsNullDate(AValue) then begin
if AValue > TheBiggestDate then
FMaxDate := TheBiggestDate
else if AValue <= FMinDate then
FMaxDate := FMinDate
else
FMaxDate := Int(AValue);
if not DateIsNull then
if FMaxDate < GetDate then
SetDate(FMaxDate);
AdjustEffectiveCenturyFrom;
end;
end;
procedure TCustomDateTimePicker.SetMinDate(const AValue: TDate);
begin
if not IsNullDate(AValue) then begin
if AValue < TheSmallestDate then
FMinDate := TheSmallestDate
else if AValue >= FMaxDate then
FMinDate := FMaxDate
else
FMinDate := Int(AValue);
if not DateIsNull then
if FMinDate > GetDate then
SetDate(FMinDate);
AdjustEffectiveCenturyFrom;
end;
end;
procedure TCustomDateTimePicker.SetReadOnly(const AValue: Boolean);
begin
if FReadOnly <> AValue then begin
if AValue then
ConfirmChanges;
FReadOnly := AValue;
end;
end;
procedure TCustomDateTimePicker.SetShowCheckBox(const AValue: Boolean);
begin
if FShowCheckBox = AValue then
Exit;
FShowCheckBox := AValue;
ArrangeCtrls;
end;
procedure TCustomDateTimePicker.SetShowMonthNames(AValue: Boolean);
begin
if FShowMonthNames <> AValue then begin
FShowMonthNames := AValue;
if not (dtpMonth in FEffectiveHideDateTimeParts) then begin
FRecalculatingTextSizesNeeded := True;
UpdateDate;
end;
end;
end;
procedure TCustomDateTimePicker.SetTextForNullDate(const AValue: TCaption);
begin
if FTextForNullDate = AValue then
Exit;
FTextForNullDate := AValue;
if DateIsNull then
Invalidate;
end;
procedure TCustomDateTimePicker.SetTime(const AValue: TTime);
begin
if IsNullDate(AValue) then
DateTime := NullDate
else if DateIsNull then
DateTime := ComposeDateTime(Max(Min(SysUtils.Date, MaxDate), MinDate), AValue)
else
DateTime := ComposeDateTime(FDateTime, AValue);
end;
procedure TCustomDateTimePicker.SetTimeSeparator(const AValue: String);
begin
SetSeparators(FDateSeparator, AValue, FDecimalSeparator);
end;
procedure TCustomDateTimePicker.SetTimeDisplay(const AValue: TTimeDisplay);
begin
if FTimeDisplay <> AValue then begin
FTimeDisplay:= AValue;
AdjustEffectiveHideDateTimeParts;
end;
end;
procedure TCustomDateTimePicker.SetTimeFormat(const AValue: TTimeFormat);
begin
if FTimeFormat <> AValue then begin
FTimeFormat := AValue;
AdjustEffectiveHideDateTimeParts;
end;
end;
procedure TCustomDateTimePicker.SetTrailingSeparator(const AValue: Boolean);
begin
if FTrailingSeparator <> AValue then begin
FTrailingSeparator := AValue;
FRecalculatingTextSizesNeeded := True;
UpdateIfUserChangedText;
Invalidate;
end;
end;
procedure TCustomDateTimePicker.SetUseDefaultSeparators(const AValue: Boolean);
begin
if FUseDefaultSeparators <> AValue then begin
if AValue then begin
SetSeparators(DefaultFormatSettings.DateSeparator,
DefaultFormatSettings.TimeSeparator,
DefaultFormatSettings.DecimalSeparator);
// Note that here, in SetSeparators procedure,
// the field FUseDefaultSeparators is set to False.
end;
// Therefore, the following line must NOT be moved above.
FUseDefaultSeparators := AValue;
end;
end;
{ RecalculateTextSizesIfNeeded
--------------------------------
In this procedure we measure text and store the values in the following
fields: FDateWidth, FTimeWidth, FTextWidth, FTextHeigth, FDigitWidth,
FSeparatorWidth, FTimeSeparatorWidth, FDecimalSeparatorWidth, FSepNoSpaceWidth.
These fields are used in calculating our preferred size and when painting.
The procedure is called internally when needed (when properties which
influence the appearance change). }
procedure TCustomDateTimePicker.RecalculateTextSizesIfNeeded;
const
NullMonthChar = 'x';
var
C: Char;
N, J: Integer;
S: String;
I: TDateTimePart;
DateParts, TimeParts: Integer;
begin
if HandleAllocated and FRecalculatingTextSizesNeeded then begin
FRecalculatingTextSizesNeeded := False;
FDigitWidth := 0;
for C := '0' to '9' do begin
N := Canvas.GetTextWidth(C);
if N > FDigitWidth then
FDigitWidth := N;
end;
DateParts := 0;
FSepNoSpaceWidth := 0;
FSeparatorWidth := 0;
FMonthWidth := 0;
FDateWidth := 0;
FNullMonthText := '';
S := '';
if FKind in [dtkDate, dtkDateTime] then begin
for I := dtpDay to dtpYear do
if not (I in FEffectiveHideDateTimeParts) then begin
Inc(DateParts);
if I = dtpYear then begin
FDateWidth := FDateWidth + 4 * FDigitWidth;
end else if (I = dtpMonth) and FShowMonthNames then begin
FMonthWidth := FDigitWidth; // Minimal MonthWidth is DigitWidth.
for J := Low(TMonthNameArray) to High(TMonthNameArray) do begin
N := Canvas.GetTextWidth(FMonthNamesArray[J]);
if N > FMonthWidth then
FMonthWidth := N;
end;
N := Canvas.GetTextWidth(NullMonthChar);
if N > 0 then begin
N := (FMonthWidth - 1) div N + 1;
if N > 1 then begin
FNullMonthText := StringOfChar(NullMonthChar, N);
N := Canvas.TextFitInfo(FNullMonthText, FMonthWidth);
if N > 1 then
SetLength(FNullMonthText, N);
end;
end;
if N <= 1 then
FNullMonthText := NullMonthChar;
FDateWidth := FDateWidth + FMonthWidth;
end else
FDateWidth := FDateWidth + 2 * FDigitWidth;
end;
if DateParts > 0 then begin
if FTrailingSeparator then begin
FSepNoSpaceWidth := Canvas.GetTextWidth(TrimRight(FDateSeparator));
Inc(FDateWidth, FSepNoSpaceWidth);
end;
if DateParts > 1 then begin
FSeparatorWidth := Canvas.GetTextWidth(FDateSeparator);
S := FDateSeparator;
FDateWidth := FDateWidth + (DateParts - 1) * FSeparatorWidth;
end;
end;
end;
TimeParts := 0;
FTimeWidth := 0;
FAMPMWidth := 0;
FTimeSeparatorWidth := 0;
FDecimalSeparatorWidth := 0;
if FKind in [dtkTime, dtkDateTime] then begin
for I := dtpHour to dtpMiliSec do
if not (I in FEffectiveHideDateTimeParts) then begin
Inc(TimeParts);
if I = dtpMiliSec then
FTimeWidth := FTimeWidth + 3 * FDigitWidth
else
FTimeWidth := FTimeWidth + 2 * FDigitWidth;
end;
if TimeParts > 1 then begin
FTimeSeparatorWidth := Canvas.GetTextWidth(FTimeSeparator);
FDecimalSeparatorWidth := Canvas.GetTextWidth(FDecimalSeparator);
S := S + FTimeSeparator;
FTimeWidth := FTimeWidth + (TimeParts - 1) * FTimeSeparatorWidth;
end;
if not (dtpAMPM in FEffectiveHideDateTimeParts) then begin
S := S + 'APM';
FAMPMWidth := Max(Canvas.TextWidth('AM'), Canvas.TextWidth('PM'));
FTimeWidth := FTimeWidth + FDigitWidth + FAMPMWidth;
end;
end;
FTextWidth := FDateWidth + FTimeWidth;
if (DateParts > 0) and (TimeParts > 0) then
FTextWidth := FTextWidth + 2 * FDigitWidth;
FTextHeight := Canvas.GetTextHeight('0123456789' + S);
end;
end;
function TCustomDateTimePicker.GetHMSMs(const NowIfNull: Boolean): THMSMs;
begin
if DateIsNull then begin
if NowIfNull then
DecodeTime(SysUtils.Time, Result.Hour, Result.Minute, Result.Second, Result.MiliSec)
else
Result := Default(THMSMs);
end else
DecodeTime(FDateTime, Result.Hour, Result.Minute, Result.Second, Result.MiliSec);
end;
function TCustomDateTimePicker.GetYYYYMMDD(const TodayIfNull: Boolean;
const WithCorrection: Boolean): TYMD;
begin
if DateIsNull then begin
if TodayIfNull then
DecodeDate(SysUtils.Date, Result.Year, Result.Month, Result.Day)
else
Result := Default(TYMD);
end else begin
DecodeDate(FDateTime, Result.Year, Result.Month, Result.Day);
if WithCorrection and (FCorrectedValue > 0) then begin
case FCorrectedDTP of
dtpDay:
Result.Day := FCorrectedValue;
dtpMonth:
Result.Month := FCorrectedValue;
dtpYear:
Result.Year := FCorrectedValue;
otherwise
end;
end;
end;
end;
procedure TCustomDateTimePicker.SetHour(const AValue: Word);
var
HMSMs: THMSMs;
begin
SelectHour;
HMSMs := GetHMSMs(True);
HMSMs.Hour := AValue;
SetHMSMs(HMSMs);
end;
procedure TCustomDateTimePicker.SetMiliSec(const AValue: Word);
var
HMSMs: THMSMs;
begin
SelectMiliSec;
HMSMs := GetHMSMs(True);
HMSMs.MiliSec := AValue;
SetHMSMs(HMSMs);
end;
procedure TCustomDateTimePicker.SetMinute(const AValue: Word);
var
HMSMs: THMSMs;
begin
SelectMinute;
HMSMs := GetHMSMs(True);
HMSMs.Minute := AValue;
SetHMSMs(HMSMs);
end;
procedure TCustomDateTimePicker.SetSecond(const AValue: Word);
var
HMSMs: THMSMs;
begin
SelectSecond;
HMSMs := GetHMSMs(True);
HMSMs.Second := AValue;
SetHMSMs(HMSMs);
end;
procedure TCustomDateTimePicker.SetSeparators(const DateSep, TimeSep, DecSep: String);
var
SeparatorsChanged: Boolean;
begin
FUseDefaultSeparators := False;
SeparatorsChanged := False;
if FDateSeparator <> DateSep then begin
FDateSeparator := DateSep;
SeparatorsChanged := True;
end;
if FTimeSeparator <> TimeSep then begin
FTimeSeparator := TimeSep;
SeparatorsChanged := True;
end;
if FDecimalSeparator <> DecSep then begin
FDecimalSeparator := DecSep;;
SeparatorsChanged := True;
end;
if SeparatorsChanged then begin
FRecalculatingTextSizesNeeded := True;
Invalidate;
end;
end;
procedure TCustomDateTimePicker.SetDay(const AValue: Word);
var
YMD: TYMD;
begin
SelectDay;
YMD := GetYYYYMMDD(True, True);
YMD.Day := AValue;
SetYYYYMMDD(YMD);
end;
procedure TCustomDateTimePicker.SetMonth(const AValue: Word);
var
YMD: TYMD;
N: Word;
begin
SelectMonth;
YMD := GetYYYYMMDD(True, True);
YMD.Month := AValue;
N := NumberOfDaysInMonth(YMD.Month, YMD.Year);
if YMD.Day > N then
YMD.Day := N;
SetYYYYMMDD(YMD);
end;
procedure TCustomDateTimePicker.SetYear(const AValue: Word);
var
YMD: TYMD;
begin
SelectYear;
YMD := GetYYYYMMDD(True, True);
YMD.Year := AValue;
if (YMD.Month = 2) and (YMD.Day > 28) and (not IsLeapYear(YMD.Year)) then
YMD.Day := 28;
SetYYYYMMDD(YMD);
end;
procedure TCustomDateTimePicker.SetYYYYMMDD(const AValue: TYMD);
var
D: TDateTime;
begin
if TryEncodeDate(AValue.Year, AValue.Month, AValue.Day, D) then
SetDate(D)
else
UpdateDate;
end;
procedure TCustomDateTimePicker.SetHMSMs(const AValue: THMSMs);
var
T: TDateTime;
begin
if TryEncodeTime(AValue.Hour, AValue.Minute,
AValue.Second, AValue.MiliSec, T) then
SetTime(T)
else
UpdateDate;
end;
procedure TCustomDateTimePicker.UpdateIfUserChangedText;
var
W: Word;
S: String;
begin
if FUserChangedText then begin
Inc(FUserChanging);
try
FUserChangedText := False;
S := Trim(GetSelectedText);
if FSelectedTextPart = 8 then begin
W := GetHMSMs().Hour;
if upCase(S[1]) = 'A' then begin
if W >= 12 then
Dec(W, 12);
end else begin
if W < 12 then
Inc(W, 12);
end;
SetHour(W);
FSelectedTextPart := 8;
end else begin
W := StrToInt(S);
case GetSelectedDateTimePart of
dtpYear:
begin
if Length(S) <= 2 then begin
// If user entered the year in two digit format (or even only
// one digit), we will set the year according to the CenturyFrom
// property (We actually use FEffectiveCenturyFrom field, which
// is adjusted to take care of MinDate and MaxDate properties,
// besides CenturyFrom).
if W >= (FEffectiveCenturyFrom mod 100) then
W := W + 100 * (FEffectiveCenturyFrom div 100)
else
W := W + 100 * (FEffectiveCenturyFrom div 100 + 1);
end;
SetYear(W);
end;
dtpDay:
SetDay(W);
dtpMonth:
SetMonth(W);
dtpHour:
begin
if FTimeFormat = tf12 then begin
if GetHMSMs().Hour < 12 then begin
if W = 12 then
SetHour(0)
else
SetHour(W);
end else begin
if W = 12 then
SetHour(W)
else
SetHour(W + 12);
end;
end else
SetHour(W);
end;
dtpMinute:
SetMinute(W);
dtpSecond:
SetSecond(W);
dtpMiliSec:
SetMiliSec(W);
otherwise
end;
end;
finally
FCorrectedValue := 0;
Dec(FUserChanging);
end;
end;
end;
function TCustomDateTimePicker.GetSelectedText: String;
begin
if FSelectedTextPart <= 3 then
Result := FTextPart[FSelectedTextPart]
else
Result := FTimeText[TDateTimePart(FSelectedTextPart - 1)];
end;
procedure TCustomDateTimePicker.AdjustSelection;
begin
if GetSelectedDateTimePart in FEffectiveHideDateTimeParts then
MoveSelectionLR(False);
end;
procedure TCustomDateTimePicker.AdjustEffectiveCenturyFrom;
var
Y1, Y2, M, D: Word;
begin
DecodeDate(FMinDate, Y1, M, D);
if Y1 > FCenturyFrom then
FEffectiveCenturyFrom := Y1 // If we use CenturyFrom which is set to value
// below MinDate's year, then when user enters two digit year, the
// DateTime would automatically be set to MinDate value, even though
// we perhaps allow same two-digit year in following centuries. It
// would be less user friendly.
// This is therefore better.
else begin
DecodeDate(FMaxDate, Y2, M, D);
if Y2 < 100 then
Y2 := 0
else
Dec(Y2, 99); // -- We should not use CenturyFrom if it is set to value
// greater then MaxDate's year minus 100 years.
// For example:
// if CenturyFrom = 1941 and MaxDate = 31.12.2025, then if user enters
// Year 33, we could not set the year to 2033 anyway, because of MaxDate
// limit. Note that if we just leave CenturyFrom to effectively remain as
// is, then in case of our example the DateTime would be automatically
// reduced to MaxDate value. Setting the year to 1933 is rather expected
// behaviour, so our internal field FEffectiveCenturyFrom should be 1926.
// Therefore:
if Y2 < FCenturyFrom then
FEffectiveCenturyFrom := Max(Y1, Y2)
else
FEffectiveCenturyFrom := FCenturyFrom; // -- FCenturyFrom has passed all
// our tests, so we'll really use it without any correction.
end;
end;
{ AdjustEffectiveDateDisplayOrder procedure
-------------------------------------------
If date display order ddoTryDefault is set, then we will decide which
display order to use according to ShortDateFormat global variable. This
procedure tries to achieve that by searching through short date format string,
to see which letter comes first -- d, m or y. When it finds any of these
characters, it assumes that date order should be d-m-y, m-d-y, or y-m-d
respectively. If the search through ShortDateFormat is unsuccessful by any
chance, we try the same with LongDateFormat global variable. If we don't
succeed again, we'll assume y-m-d order. }
procedure TCustomDateTimePicker.AdjustEffectiveDateDisplayOrder;
var
S: String;
I, J, Le: Integer;
InQuoteChar: Char;
begin
if FDateDisplayOrder = ddoTryDefault then begin
S := DefaultFormatSettings.ShortDateFormat;
FEffectiveDateDisplayOrder := ddoTryDefault;
repeat
InQuoteChar := Chr(0);
Le := Length(S);
I := 0;
while I < Le do begin
Inc(I);
if InQuoteChar = Chr(0) then begin
case S[I] of
'''', '"':
InQuoteChar := S[I];
'D', 'd':
begin
{ If 3 or more "d"-s are standing together, then it's day
of week, but here we are interested in day of month.
So, we have to see what is following: }
J := I + 1;
while (J <= Le) and (upCase(S[J]) = 'D') do
Inc(J);
if J <= I + 2 then begin
FEffectiveDateDisplayOrder := ddoDMY;
Break;
end;
I := J - 1;
end;
'M', 'm':
begin
FEffectiveDateDisplayOrder := ddoMDY;
Break;
end;
'Y', 'y':
begin
FEffectiveDateDisplayOrder := ddoYMD;
Break;
end;
end;
end else
if S[I] = InQuoteChar then
InQuoteChar := Chr(0);
end;
if FEffectiveDateDisplayOrder = ddoTryDefault then begin
{ We couldn't decide with ShortDateFormat, let's try with
LongDateFormat now. }
S := DefaultFormatSettings.LongDateFormat;
{ But now we must set something to be default. This ensures that the
repeat loop breaks next time. If we don't find anything in
LongDateFormat, we'll leave with y-m-d order. }
FEffectiveDateDisplayOrder := ddoYMD;
end else
Break;
until False;
end else
FEffectiveDateDisplayOrder := FDateDisplayOrder;
case FEffectiveDateDisplayOrder of
ddoDMY:
begin
FDayPos := 1;
FMonthPos := 2;
FYearPos := 3;
end;
ddoMDY:
begin
FDayPos := 2;
FMonthPos := 1;
FYearPos := 3;
end;
otherwise
FDayPos := 3;
FMonthPos := 2;
FYearPos := 1;
end;
end;
procedure TCustomDateTimePicker.AdjustEffectiveHideDateTimeParts;
var
I: TDateTimePart;
PreviousEffectiveHideDateTimeParts: set of TDateTimePart;
begin
PreviousEffectiveHideDateTimeParts := FEffectiveHideDateTimeParts;
FEffectiveHideDateTimeParts := [];
for I := Low(TDateTimeParts) to High(TDateTimeParts) do
if I in FHideDateTimeParts then
Include(FEffectiveHideDateTimeParts, I);
if FKind = dtkDate then
FEffectiveHideDateTimeParts := FEffectiveHideDateTimeParts +
[dtpHour, dtpMinute, dtpSecond, dtpMiliSec, dtpAMPM]
else begin
if FKind = dtkTime then
FEffectiveHideDateTimeParts := FEffectiveHideDateTimeParts +
[dtpDay, dtpMonth, dtpYear];
case FTimeDisplay of
tdHM:
FEffectiveHideDateTimeParts := FEffectiveHideDateTimeParts +
[dtpSecond, dtpMiliSec];
tdHMS:
FEffectiveHideDateTimeParts := FEffectiveHideDateTimeParts +
[dtpMiliSec];
otherwise
end;
if (FTimeFormat = tf24) or (dtpHour in FEffectiveHideDateTimeParts) then
Include(FEffectiveHideDateTimeParts, dtpAMPM);
end;
if FEffectiveHideDateTimeParts
<> PreviousEffectiveHideDateTimeParts then begin
AdjustSelection;
FRecalculatingTextSizesNeeded := True;
UpdateShowArrowButton;
UpdateDate;
end;
end;
procedure TCustomDateTimePicker.SelectDateTimePart(
const DateTimePart: TDateTimePart);
begin
if not (DateTimePart in FEffectiveHideDateTimeParts) then begin
case DateTimePart of
dtpDay:
FSelectedTextPart := FDayPos;
dtpMonth:
FSelectedTextPart := FMonthPos;
dtpYear:
FSelectedTextPart := FYearPos;
else
FSelectedTextPart := 1 + Ord(DateTimePart);
end;
Invalidate;
end;
end;
procedure TCustomDateTimePicker.DestroyCalendarForm;
begin
if Assigned(FCalendarForm) then begin
TDTCalendarForm(FCalendarForm).FClosing := True;
FCalendarForm.Release;
FCalendarForm := nil;
end;
end;
class function TCustomDateTimePicker.GetControlClassDefaultSize: TSize;
begin
Result.cx := 102;
Result.cy := 23;
end;
procedure TCustomDateTimePicker.ConfirmChanges;
begin
UpdateIfUserChangedText;
FConfirmedDateTime := FDateTime;
end;
procedure TCustomDateTimePicker.UndoChanges;
begin
if FDateTime = FConfirmedDateTime then begin
Inc(FSkipChangeInUpdateDate); // prevents calling Change in UpdateDate,
try // but UpdateDate should be called anyway, because user might have
// changed text on screen and it should be updated to what it was.
UpdateDate;
finally
Dec(FSkipChangeInUpdateDate);
end;
end else begin
FDateTime := FConfirmedDateTime;
UpdateDate;
end;
end;
{ GetDateTimePartFromTextPart function
-----------------------------------------------
Returns part of date/time from the position (1-8). }
function TCustomDateTimePicker.GetDateTimePartFromTextPart(
TextPart: TTextPart): TDateTimePart;
begin
Result := TDateTimePart(TextPart - 1);
case FEffectiveDateDisplayOrder of
ddoMDY:
if Result = dtpDay then
Result := dtpMonth
else if Result = dtpMonth then
Result := dtpDay;
ddoYMD:
if Result = dtpDay then
Result := dtpYear
else if Result = dtpYear then
Result := dtpDay;
otherwise
end;
end;
{ GetSelectedDateTimePart function
---------------------------------
Returns part of date/time which is currently selected. }
function TCustomDateTimePicker.GetSelectedDateTimePart: TDateTimePart;
begin
Result := GetDateTimePartFromTextPart(FSelectedTextPart);
end;
procedure TCustomDateTimePicker.FontChanged(Sender: TObject);
begin
FRecalculatingTextSizesNeeded := True;
inherited FontChanged(Sender);
end;
function TCustomDateTimePicker.GetCheckBoxRect(IgnoreRightToLeft: Boolean): TRect;
var
Details: TThemedElementDetails;
CSize: TSize;
begin
Details := ThemeServices.GetElementDetails(tbCheckBoxCheckedNormal);
CSize := ThemeServices.GetDetailSizeForPPI(Details, Font.PixelsPerInch);
CSize.cx := ScaleScreenToFont(CSize.cx);
CSize.cy := ScaleScreenToFont(CSize.cy);
if IsRightToLeft and not IgnoreRightToLeft then begin
Result.Right := ClientWidth - (BorderSpacing.InnerBorder + BorderWidth);
Result.Left := Result.Right - CSize.cx;
end else begin
Result.Left := BorderSpacing.InnerBorder + BorderWidth;
Result.Right := Result.Left + CSize.cx;
end;
Result.Top := (ClientHeight - CSize.cy) div 2;
Result.Bottom := Result.Top + CSize.cy;
end;
{ GetTextOrigin
---------------
Returns upper left corner of the rectangle where the text is written.
Also used in calculating our preferred size. }
function TCustomDateTimePicker.GetTextOrigin(IgnoreRightToLeft: Boolean
): TPoint;
var
Re: TRect;
B: Integer;
XL, XR: Integer;
AuxAlignment: TAlignment;
begin
B := BorderSpacing.InnerBorder + BorderWidth;
Result.y := B;
if IgnoreRightToLeft or AutoSize then
AuxAlignment := taLeftJustify
else begin
AuxAlignment := FAlignment;
if IsRightToLeft then begin
case AuxAlignment of
taRightJustify:
AuxAlignment := taLeftJustify;
taLeftJustify:
AuxAlignment := taRightJustify;
otherwise
end;
end;
end;
if FShowCheckBox then begin
Re := GetCheckBoxRect(IgnoreRightToLeft);
InflateRect(Re, Scale96ToFont(cCheckBoxBorder), 0);
XL := Re.Right;
XR := Re.Left;
end else begin
XL := B;
XR := ClientWidth - B;
end;
if Assigned(FUpDown) then
B := B + FUpDown.Width
else if Assigned(FArrowButton) then
B := B + FArrowButton.Width;
if IgnoreRightToLeft or not IsRightToLeft then begin
XR := ClientWidth - B;
end else begin
XL := B;
end;
case AuxAlignment of
taRightJustify:
Result.x := XR - FTextWidth;
taCenter:
Result.x := (XL + XR - FTextWidth) div 2;
taLeftJustify:
Result.x := XL;
end;
end;
{ MoveSelectionLR
-----------------
Moves selection to left or to right. If parameter ToLeft is true, then the
selection moves to left, otherwise to right. }
procedure TCustomDateTimePicker.MoveSelectionLR(const ToLeft: Boolean);
var
I, SafetyTextPart: TTextPart;
begin
UpdateIfUserChangedText;
SafetyTextPart := Low(TTextPart);
I := FSelectedTextPart;
repeat
if ToLeft then begin
if I <= Low(TTextPart) then
I := High(TTextPart)
else
Dec(I);
end else begin
if I >= High(TTextPart) then
I := Low(TTextPart)
else
Inc(I);
end;
if not (GetDateTimePartFromTextPart(I) in FEffectiveHideDateTimeParts) then
FSelectedTextPart := I;
{ Is it possible that all parts are hidden? Yes it is!
So we need to ensure that this doesn't loop forever.
When this insurance text part gets to high value, break }
Inc(SafetyTextPart);
until (I = FSelectedTextPart) or (SafetyTextPart >= High(TTextPart));
end;
procedure TCustomDateTimePicker.KeyDown(var Key: Word; Shift: TShiftState);
begin
Inc(FUserChanging);
try
if FTextEnabled then
inherited KeyDown(Key, Shift); // calls OnKeyDown event
CheckAndApplyKeyCode(Key, Shift);
finally
Dec(FUserChanging);
end;
end;
procedure TCustomDateTimePicker.KeyPress(var Key: char);
begin
if FTextEnabled then begin
Inc(FUserChanging);
try
inherited KeyPress(Key);
CheckAndApplyKey(Key);
finally
Dec(FUserChanging);
end;
end;
end;
{ SelectTextPartUnderMouse
--------------------------
This procedure determines which text part (date or time part -- day, month,
year, hour, minute...) should be selected in response to mouse message.
Used in MouseDown and DoMouseWheel methods. }
procedure TCustomDateTimePicker.SelectTextPartUnderMouse(XMouse: Integer);
var
I, M, NX: Integer;
InTime: Boolean;
begin
UpdateIfUserChangedText;
SetFocusIfPossible;
if Focused then begin
// Calculating mouse position inside text
// in order to select date part under mouse cursor.
NX := XMouse - GetTextOrigin.x;
InTime := False;
if FTimeWidth > 0 then begin
if FDateWidth > 0 then begin
if NX >= FDateWidth + FDigitWidth then begin
InTime := True;
NX := NX - FDateWidth - 2 * FDigitWidth;
end;
end else
InTime := True;
end;
if InTime then begin
FSelectedTextPart := 8;
if (dtpAMPM in FEffectiveHideDateTimeParts) or
(NX < FTimeWidth - FAMPMWidth - FDigitWidth div 2) then begin
FSelectedTextPart := 7;
I := 4;
M := FTimeSeparatorWidth div 2;
while I <= 6 do begin
if not (GetDateTimePartFromTextPart(I)
in FEffectiveHideDateTimeParts) then begin
Inc(M, 2 * FDigitWidth);
if M > NX then begin
FSelectedTextPart := I;
Break;
end;
Inc(M, FTimeSeparatorWidth);
end;
Inc(I);
end;
end;
end else if FDateWidth > 0 then begin
FSelectedTextPart := 3;
I := 1;
M := FSeparatorWidth div 2;
while I <= 2 do begin
if not (GetDateTimePartFromTextPart(I)
in FEffectiveHideDateTimeParts) then begin
if I = FYearPos then
Inc(M, 4 * FDigitWidth)
else if (I = FMonthPos) and FShowMonthNames then
Inc(M, FMonthWidth)
else
Inc(M, 2 * FDigitWidth);
if M > NX then begin
FSelectedTextPart := I;
Break;
end;
Inc(M, FSeparatorWidth);
end;
Inc(I);
end;
end;
if GetSelectedDateTimePart in FEffectiveHideDateTimeParts then
MoveSelectionLR(True);
Invalidate;
//-------------------------------------------------------
end;
end;
procedure TCustomDateTimePicker.MouseDown(Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if ShowCheckBox and PtInRect(GetCheckBoxRect, Point(X, Y)) then
Checked := not Checked
else if FTextEnabled then
SelectTextPartUnderMouse(X);
SetFocusIfPossible;
inherited MouseDown(Button, Shift, X, Y);
end;
procedure TCustomDateTimePicker.MouseLeave;
begin
inherited MouseLeave;
if FShowCheckBox and FMouseInCheckBox then
begin
FMouseInCheckBox := False;
Invalidate;
end;
end;
procedure TCustomDateTimePicker.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
inherited MouseMove(Shift, X, Y);
if ShowCheckBox and (FMouseInCheckBox xor PtInRect(GetCheckBoxRect, Point(X, Y))) then begin
FMouseInCheckBox := not FMouseInCheckBox;
Invalidate;
end;
end;
function TCustomDateTimePicker.DoMouseWheel(Shift: TShiftState;
WheelDelta: Integer; MousePos: TPoint): Boolean;
begin
Result := False;
if FTextEnabled then begin
SelectTextPartUnderMouse(MousePos.x);
if not FReadOnly then begin
Inc(FUserChanging);
try
if WheelDelta < 0 then
DecreaseCurrentTextPart
else
IncreaseCurrentTextPart;
Result := True;
finally
Dec(FUserChanging);
end;
end;
end;
end;
procedure TCustomDateTimePicker.CalculatePreferredSize(var PreferredWidth,
PreferredHeight: integer; WithThemeSpace: Boolean);
var
TextOrigin: TPoint;
begin
RecalculateTextSizesIfNeeded;
TextOrigin := GetTextOrigin(True);
PreferredHeight := 2 * TextOrigin.y + FTextHeight + Height - ClientHeight;
// We must use TextOrigin's x + y (x is, of course, left margin, but not right
// margin if check box is shown. However, y, which is top margin, always
// equals right margin).
PreferredWidth := TextOrigin.x + TextOrigin.y
+ FTextWidth + Width - ClientWidth;
if Assigned(FUpDown) then
Inc(PreferredWidth, FUpDown.Width)
else if Assigned(FArrowButton) then
Inc(PreferredWidth, FArrowButton.Width);
end;
procedure TCustomDateTimePicker.SetBiDiMode(AValue: TBiDiMode);
begin
inherited SetBiDiMode(AValue);
ArrangeCtrls;
end;
procedure TCustomDateTimePicker.Loaded;
begin
inherited Loaded;
//Since both deprecated MonthNames property and new MonthDisplay property control the same thing
//only apply design time value of MonthNames if:
// - MonthDisplay at design time is he default value (mdLong) and
// - MonthNames at design time <> default value for MonthNames ('Long'), since in that case there is nothing to do
if (FMonthDisplay = mdLong) and (CompareText(TrimRight(FInitiallyLoadedMonthNames), 'LONG') = 0) then
begin
SetMonthNames(FInitiallyLoadedMonthNames);
end;
end;
procedure TCustomDateTimePicker.IncreaseCurrentTextPart;
begin
if DateIsNull then begin
if FSelectedTextPart <= 3 then
SetDateTime(SysUtils.Date)
else
SetDateTime(SysUtils.Now);
end else begin
case GetSelectedDateTimePart of
dtpDay: IncreaseDay;
dtpMonth: IncreaseMonth;
dtpYear: IncreaseYear;
dtpHour: IncreaseHour;
dtpMinute: IncreaseMinute;
dtpSecond: IncreaseSecond;
dtpMiliSec: IncreaseMiliSec;
dtpAMPM: ChangeAMPM;
end;
end;
end;
procedure TCustomDateTimePicker.DecreaseCurrentTextPart;
begin
if DateIsNull then begin
if FSelectedTextPart <= 3 then
SetDateTime(SysUtils.Date)
else
SetDateTime(SysUtils.Now);
end else begin
case GetSelectedDateTimePart of
dtpDay: DecreaseDay;
dtpMonth: DecreaseMonth;
dtpYear: DecreaseYear;
dtpHour: DecreaseHour;
dtpMinute: DecreaseMinute;
dtpSecond: DecreaseSecond;
dtpMiliSec: DecreaseMiliSec;
dtpAMPM: ChangeAMPM;
end;
end;
end;
procedure TCustomDateTimePicker.IncreaseMonth;
var
YMD: TYMD;
N: Word;
begin
SelectMonth;
YMD := GetYYYYMMDD(True);
if YMD.Month >= 12 then begin
YMD.Month := 1;
if Cascade then
Inc(YMD.Year);
end else
Inc(YMD.Month);
N := NumberOfDaysInMonth(YMD.Month, YMD.Year);
if YMD.Day > N then
YMD.Day := N;
SetYYYYMMDD(YMD);
end;
procedure TCustomDateTimePicker.IncreaseYear;
var
YMD: TYMD;
begin
SelectYear;
YMD := GetYYYYMMDD(True);
Inc(YMD.Year);
if (YMD.Month = 2) and (YMD.Day > 28) and (not IsLeapYear(YMD.Year)) then
YMD.Day := 28;
SetYYYYMMDD(YMD);
end;
procedure TCustomDateTimePicker.IncreaseDay;
var
YMD: TYMD;
begin
SelectDay;
if Cascade then begin
if DateIsNull then
SetDate(IncDay(SysUtils.Date))
else
SetDateTime(IncDay(FDateTime));
end else begin
YMD := GetYYYYMMDD(True);
if YMD.Day >= NumberOfDaysInMonth(YMD.Month, YMD.Year) then
YMD.Day := 1
else
Inc(YMD.Day);
SetYYYYMMDD(YMD);
end;
end;
procedure TCustomDateTimePicker.DecreaseMonth;
var
YMD: TYMD;
N: Word;
begin
SelectMonth;
YMD := GetYYYYMMDD(True);
if YMD.Month <= 1 then begin
YMD.Month := 12;
if Cascade then
Dec(YMD.Year);
end else
Dec(YMD.Month);
N := NumberOfDaysInMonth(YMD.Month, YMD.Year);
if YMD.Day > N then
YMD.Day := N;
SetYYYYMMDD(YMD);
end;
procedure TCustomDateTimePicker.DecreaseYear;
var
YMD: TYMD;
begin
SelectYear;
YMD := GetYYYYMMDD(True);
Dec(YMD.Year);
if (YMD.Month = 2) and (YMD.Day > 28) and (not IsLeapYear(YMD.Year)) then
YMD.Day := 28;
SetYYYYMMDD(YMD);
end;
procedure TCustomDateTimePicker.DecreaseDay;
var
YMD: TYMD;
begin
SelectDay;
if Cascade then begin
if DateIsNull then
SetDate(IncDay(SysUtils.Date, -1))
else
SetDateTime(IncDay(FDateTime, -1));
end else begin
YMD := GetYYYYMMDD(True);
if YMD.Day <= 1 then
YMD.Day := NumberOfDaysInMonth(YMD.Month, YMD.Year)
else
Dec(YMD.Day);
SetYYYYMMDD(YMD);
end;
end;
procedure TCustomDateTimePicker.IncreaseHour;
var
HMSMs: THMSMs;
begin
SelectHour;
if Cascade then begin
if DateIsNull then
SetDateTime(IncHour(SysUtils.Now))
else
SetDateTime(IncHour(FDateTime));
end else begin
HMSMs := GetHMSMs(True);
if HMSMs.Hour >= 23 then
HMSMs.Hour := 0
else
Inc(HMSMs.Hour);
SetHMSMs(HMSMs);
end;
end;
procedure TCustomDateTimePicker.IncreaseMinute;
var
HMSMs: THMSMs;
begin
SelectMinute;
if Cascade then begin
if DateIsNull then
SetDateTime(IncMinute(SysUtils.Now))
else
SetDateTime(IncMinute(FDateTime));
end else begin
HMSMs := GetHMSMs(True);
if HMSMs.Minute >= 59 then
HMSMs.Minute := 0
else
Inc(HMSMs.Minute);
SetHMSMs(HMSMs);
end;
end;
procedure TCustomDateTimePicker.IncreaseSecond;
var
HMSMs: THMSMs;
begin
SelectSecond;
if Cascade then begin
if DateIsNull then
SetDateTime(IncSecond(SysUtils.Now))
else
SetDateTime(IncSecond(FDateTime));
end else begin
HMSMs := GetHMSMs(True);
if HMSMs.Second >= 59 then
HMSMs.Second := 0
else
Inc(HMSMs.Second);
SetHMSMs(HMSMs);
end;
end;
procedure TCustomDateTimePicker.IncreaseMiliSec;
var
HMSMs: THMSMs;
begin
SelectMiliSec;
if Cascade then begin
if DateIsNull then
SetDateTime(IncMilliSecond(SysUtils.Now))
else
SetDateTime(IncMilliSecond(FDateTime));
end else begin
HMSMs := GetHMSMs(True);
if HMSMs.MiliSec >= 999 then
HMSMs.MiliSec := 0
else
Inc(HMSMs.MiliSec);
SetHMSMs(HMSMs);
end;
end;
procedure TCustomDateTimePicker.DecreaseHour;
var
HMSMs: THMSMs;
begin
SelectHour;
if Cascade then begin
if DateIsNull then
SetDateTime(IncHour(SysUtils.Now, -1))
else
SetDateTime(IncHour(FDateTime, -1));
end else begin
HMSMs := GetHMSMs(True);
if HMSMs.Hour <= 0 then
HMSMS.Hour := 23
else
Dec(HMSMs.Hour);
SetHMSMs(HMSMs);
end;
end;
procedure TCustomDateTimePicker.DecreaseMinute;
var
HMSMs: THMSMs;
begin
SelectMinute;
if Cascade then begin
if DateIsNull then
SetDateTime(IncMinute(SysUtils.Now, -1))
else
SetDateTime(IncMinute(FDateTime, -1));
end else begin
HMSMs := GetHMSMs(True);
if HMSMs.Minute <= 0 then
HMSMs.Minute := 59
else
Dec(HMSMs.Minute);
SetHMSMs(HMSMs);
end;
end;
procedure TCustomDateTimePicker.DecreaseSecond;
var
HMSMs: THMSMs;
begin
SelectSecond;
if Cascade then begin
if DateIsNull then
SetDateTime(IncSecond(SysUtils.Now, -1))
else
SetDateTime(IncSecond(FDateTime, -1));
end else begin
HMSMs := GetHMSMs(True);
if HMSMs.Second <= 0 then
HMSMs.Second := 59
else
Dec(HMSMs.Second);
SetHMSMs(HMSMs);
end;
end;
procedure TCustomDateTimePicker.DecreaseMiliSec;
var
HMSMs: THMSMs;
begin
SelectMiliSec;
if Cascade then begin
if DateIsNull then
SetDateTime(IncMilliSecond(SysUtils.Now, -1))
else
SetDateTime(IncMilliSecond(FDateTime, -1));
end else begin
HMSMs := GetHMSMs(True);
if HMSMs.MiliSec <= 0 then
HMSMs.MiliSec := 999
else
Dec(HMSMs.MiliSec);
SetHMSMs(HMSMs);
end;
end;
procedure TCustomDateTimePicker.ChangeAMPM;
var
HMSMs: THMSMs;
begin
SelectAMPM;
HMSMs := GetHMSMs(True);
if HMSMs.Hour >= 12 then
Dec(HMSMS.Hour, 12)
else
Inc(HMSMS.Hour, 12);
SetHMSMs(HMSMs);
end;
procedure TCustomDateTimePicker.UpdateDate(const CallChangeFromSetDateTime: Boolean);
var
W: Array[1..3] of Word;
WT: Array[dtpHour..dtpAMPM] of Word;
DTP: TDateTimePart;
begin
FCorrectedValue := 0;
FUserChangedText := False;
if not (DateIsNull or FJumpMinMax) then begin
if Int(FDateTime) > FMaxDate then
FDateTime := ComposeDateTime(FMaxDate, FDateTime);
if FDateTime < FMinDate then
FDateTime := ComposeDateTime(FMinDate, FDateTime);
end;
if (FSkipChangeInUpdateDate = 0) then begin
// we'll skip the next part if called from UndoChanges
// and in recursive calls which could be made through calling Change
Inc(FSkipChangeInUpdateDate);
try
if (FUserChanging > 0) // the change is caused by user interaction
or CallChangeFromSetDateTime // call from SetDateTime with option dtpoDoChangeOnSetDateTime
then
try
Change;
except
UndoChanges;
raise;
end;
if FUserChanging = 0 then
FConfirmedDateTime := FDateTime;
finally
Dec(FSkipChangeInUpdateDate);
end;
end;
if DateIsNull then begin
if dtpYear in FEffectiveHideDateTimeParts then
FTextPart[FYearPos] := ''
else
FTextPart[FYearPos] := '0000';
if dtpMonth in FEffectiveHideDateTimeParts then
FTextPart[FMonthPos] := ''
else
FTextPart[FMonthPos] := '00';
if dtpDay in FEffectiveHideDateTimeParts then
FTextPart[FDayPos] := ''
else
FTextPart[FDayPos] := '00';
for DTP := dtpHour to dtpAMPM do begin
if DTP in FEffectiveHideDateTimeParts then
FTimeText[DTP] := ''
else if DTP = dtpAMPM then
FTimeText[DTP] := 'XX'
else if DTP = dtpMiliSec then
FTimeText[DTP] := '999'
else
FTimeText[DTP] := '99';
end;
end else begin
DecodeDate(FDateTime, W[3], W[2], W[1]);
if dtpYear in FEffectiveHideDateTimeParts then
FTextPart[FYearPos] := ''
else if FLeadingZeros then
FTextPart[FYearPos] := RightStr('000' + IntToStr(W[3]), 4)
else
FTextPart[FYearPos] := IntToStr(W[3]);
if dtpMonth in FEffectiveHideDateTimeParts then
FTextPart[FMonthPos] := ''
else if FShowMonthNames then
FTextPart[FMonthPos] := FMonthNamesArray[W[2]]
else if FLeadingZeros then
FTextPart[FMonthPos] := RightStr('0' + IntToStr(W[2]), 2)
else
FTextPart[FMonthPos] := IntToStr(W[2]);
if dtpDay in FEffectiveHideDateTimeParts then
FTextPart[FDayPos] := ''
else if FLeadingZeros then
FTextPart[FDayPos] := RightStr('0' + IntToStr(W[1]), 2)
else
FTextPart[FDayPos] := IntToStr(W[1]);
DecodeTime(FDateTime, WT[dtpHour], WT[dtpMinute], WT[dtpSecond], WT[dtpMiliSec]);
if dtpAMPM in FEffectiveHideDateTimeParts then
FTimeText[dtpAMPM] := ''
else begin
if WT[dtpHour] < 12 then begin
FTimeText[dtpAMPM] := 'AM';
if WT[dtpHour] = 0 then
WT[dtpHour] := 12;
end else begin
FTimeText[dtpAMPM] := 'PM';
if WT[dtpHour] > 12 then
Dec(WT[dtpHour], 12);
end;
end;
for DTP := dtpHour to dtpMiliSec do begin
if DTP in FEffectiveHideDateTimeParts then
FTimeText[DTP] := ''
else if (DTP = dtpHour) and (not FLeadingZeros) then
FTimeText[DTP] := IntToStr(WT[dtpHour])
else if DTP = dtpMiliSec then
FTimeText[DTP] := RightStr('00' + IntToStr(WT[DTP]), 3)
else
FTimeText[DTP] := RightStr('0' + IntToStr(WT[DTP]), 2);
end;
end;
if HandleAllocated then
Invalidate;
end;
procedure TCustomDateTimePicker.DoEnter;
begin
if dtpoResetSelection in Options then begin
FSelectedTextPart := High(TTextPart);
MoveSelectionLR(False);
end;
inherited DoEnter;
Invalidate;
end;
procedure TCustomDateTimePicker.DoExit;
begin
inherited DoExit;
Invalidate;
end;
procedure TCustomDateTimePicker.Click;
begin
if FTextEnabled then
inherited Click;
end;
procedure TCustomDateTimePicker.DblClick;
begin
if FTextEnabled then
inherited DblClick;
end;
procedure TCustomDateTimePicker.MouseUp(Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if FTextEnabled then
inherited MouseUp(Button, Shift, X, Y);
end;
procedure TCustomDateTimePicker.KeyUp(var Key: Word; Shift: TShiftState);
begin
if FTextEnabled then
inherited KeyUp(Key, Shift);
end;
procedure TCustomDateTimePicker.UTF8KeyPress(var UTF8Key: TUTF8Char);
begin
if FTextEnabled then
inherited UTF8KeyPress(UTF8Key);
end;
procedure TCustomDateTimePicker.SelectDay;
begin
SelectDateTimePart(dtpDay);
end;
procedure TCustomDateTimePicker.SelectMonth;
begin
SelectDateTimePart(dtpMonth);
end;
procedure TCustomDateTimePicker.SelectYear;
begin
SelectDateTimePart(dtpYear);
end;
procedure TCustomDateTimePicker.SendExternalKey(const aKey: Char);
var
K: Word;
begin
if FTextEnabled then begin
if aKey in ['n', 'N'] then begin
K := VK_N;
CheckAndApplyKeyCode(K, []);
end else
CheckAndApplyKey(aKey);
end;
end;
procedure TCustomDateTimePicker.SendExternalKeyCode(const Key: Word);
var
Ch: Char;
K: Word;
begin
if Key in [Ord('0')..Ord('9'), Ord('a'), Ord('A'), Ord('p'), Ord('P')] then begin
if FTextEnabled then begin
Ch := Char(Key);
CheckAndApplyKey(Ch);
end;
end else begin
K := Key;
CheckAndApplyKeyCode(K, []);
end;
end;
procedure TCustomDateTimePicker.RemoveAllHandlersOfObject(AnObject: TObject);
begin
inherited RemoveAllHandlersOfObject(AnObject);
if Assigned(FOnChangeHandlers) then
FOnChangeHandlers.RemoveAllMethodsOfObject(AnObject);
if Assigned(FOnCheckBoxChangeHandlers) then
FOnCheckBoxChangeHandlers.RemoveAllMethodsOfObject(AnObject);
end;
procedure TCustomDateTimePicker.SelectHour;
begin
SelectDateTimePart(dtpHour);
end;
procedure TCustomDateTimePicker.SelectMinute;
begin
SelectDateTimePart(dtpMinute);
end;
procedure TCustomDateTimePicker.SelectSecond;
begin
SelectDateTimePart(dtpSecond);
end;
procedure TCustomDateTimePicker.SelectMiliSec;
begin
SelectDateTimePart(dtpMiliSec);
end;
procedure TCustomDateTimePicker.SelectAMPM;
begin
SelectDateTimePart(dtpAMPM);
end;
procedure TCustomDateTimePicker.SetEnabled(Value: Boolean);
begin
if GetEnabled <> Value then begin
inherited SetEnabled(Value);
CheckTextEnabled;
Invalidate;
end;
end;
procedure TCustomDateTimePicker.SetAutoSize(Value: Boolean);
begin
if AutoSize <> Value then begin
if Value then
InvalidatePreferredSize;
inherited SetAutoSize(Value);
end;
end;
// I had to override CreateWnd, because in design time on Linux Lazarus crashes
// if we try to do anchoring of child controls in constructor.
// Therefore, I needed to ensure that controls anchoring does not take place
// before CreateWnd has done. So, I moved all anchoring code to a procedure
// ArrangeCtrls and introduced a boolean field FDoNotArrangeControls which
// prevents that code from executing before CreateWnd.
//!!! Later, I simplified the arranging procedure, so maybe it can be done now
// before window creation is done. It's better to leave this delay system,
// anyway -- we might change anchoring code again for some reason.
procedure TCustomDateTimePicker.CreateWnd;
begin
inherited CreateWnd;
if FDoNotArrangeControls then begin { This field is set to True in constructor.
Its purpose is to prevent control anchoring until this point. That's because
on Linux Lazarus crashes when control is dropped on form in designer if
particular anchoring code executes before CreateWnd has done its job. }
FDoNotArrangeControls := False;
ArrangeCtrls;
end;
end;
procedure TCustomDateTimePicker.SetDateTimeJumpMinMax(const AValue: TDateTime);
begin
FJumpMinMax := True;
try
SetDateTime(AValue);
finally
FJumpMinMax := False;
end;
end;
procedure TCustomDateTimePicker.ArrangeCtrls;
var
C: TControl;
begin
if not FDoNotArrangeControls then begin //Read the note above CreateWnd procedure.
DisableAlign;
try
if Assigned(FUpDown) then
C := FUpDown
else
C := FArrowButton; // might be nil.
if Assigned(C) then begin
if IsRightToLeft then
C.Align := alLeft
else
C.Align := alRight;
C.BringToFront;
end;
CheckTextEnabled;
InvalidatePreferredSize;
AdjustSize;
Invalidate;
finally
EnableAlign;
end;
end;
end;
procedure TCustomDateTimePicker.Change;
begin
if Assigned(FOnChange) then
FOnChange(Self);
if FOnChangeHandlers <> nil then
FOnChangeHandlers.CallNotifyEvents(Self);
end;
procedure TCustomDateTimePicker.SelectDate;
begin
if (FSelectedTextPart > 3)
or (GetSelectedDateTimePart in FEffectiveHideDateTimeParts) then
FSelectedTextPart := 1;
AdjustSelection;
Invalidate;
end;
procedure TCustomDateTimePicker.SelectTime;
begin
if (FSelectedTextPart < 4)
or (GetSelectedDateTimePart in FEffectiveHideDateTimeParts) then
FSelectedTextPart := 4;
AdjustSelection;
Invalidate;
end;
procedure TCustomDateTimePicker.Paint;
var
I, M, N, K, L: Integer;
DD: Array[1..8] of Integer;
R: TRect;
SelectStep: 0..8;
TextStyle: TTextStyle;
DTP: TDateTimePart;
S: String;
const
CheckStates: array[Boolean, Boolean, Boolean] of TThemedButton = (
((tbCheckBoxUncheckedDisabled, tbCheckBoxUncheckedDisabled),
(tbCheckBoxCheckedDisabled, tbCheckBoxCheckedDisabled)),
((tbCheckBoxUncheckedNormal, tbCheckBoxUncheckedHot),
(tbCheckBoxCheckedNormal, tbCheckBoxCheckedHot)));
begin
if ClientRectNeedsInterfaceUpdate then // In Qt widgetset, this solves the
DoAdjustClientRectChange; // problem of dispositioned client rect.
if FRecalculatingTextSizesNeeded then begin
if AutoSize then begin
InvalidatePreferredSize;
AdjustSize;
end;
RecalculateTextSizesIfNeeded;
end;
TextStyle := Canvas.TextStyle;
Canvas.Brush.Style := bsSolid;
Canvas.Brush.Color := Color;
Canvas.FillRect(ClientRect);
R.TopLeft := GetTextOrigin;
if not AutoSize then
R.Top := (ClientHeight - FTextHeight) div 2;
R.Bottom := R.Top + FTextHeight;
TextStyle.Layout := tlCenter;
TextStyle.Wordbreak := False;
TextStyle.Opaque := False;
TextStyle.RightToLeft := IsRightToLeft;
if DateIsNull and (FTextForNullDate <> '')
and (not (FTextEnabled and Focused)) then begin
if IsRightToLeft then begin
TextStyle.Alignment := taRightJustify;
R.Right := R.Left + FTextWidth;
R.Left := 0;
end else begin
TextStyle.Alignment := taLeftJustify;
R.Right := Width;
end;
if FTextEnabled then
Canvas.Font.Color := Font.Color
else
Canvas.Font.Color := clGrayText;
Canvas.TextRect(R, R.Left, R.Top, FTextForNullDate, TextStyle);
end else begin
TextStyle.Alignment := taRightJustify;
SelectStep := 0;
if FTextEnabled then begin
Canvas.Font.Color := Font.Color;
if Focused then
SelectStep := FSelectedTextPart;
end else begin
Canvas.Font.Color := clGrayText;
end;
if dtpYear in FEffectiveHideDateTimeParts then begin
DD[FYearPos] := 0;
M := 4;
L := 0;
end else begin
DD[FYearPos] := 4 * FDigitWidth;
M := FYearPos;
L := FYearPos;
end;
if dtpMonth in FEffectiveHideDateTimeParts then
DD[FMonthPos] := 0
else begin
if FShowMonthNames then
DD[FMonthPos] := FMonthWidth
else
DD[FMonthPos] := 2 * FDigitWidth;
if FMonthPos < M then
M := FMonthPos;
if FMonthPos > L then
L := FMonthPos;
end;
if dtpDay in FEffectiveHideDateTimeParts then
DD[FDayPos] := 0
else begin
DD[FDayPos] := 2 * FDigitWidth;
if FDayPos < M then
M := FDayPos;
if FDayPos > L then
L := FDayPos;
end;
N := L;
K := 0;
for DTP := dtpHour to dtpAMPM do begin
I := Ord(DTP) + 1;
if DTP in FEffectiveHideDateTimeParts then
DD[I] := 0
else if DTP = dtpAMPM then begin
DD[I] := FAMPMWidth;
N := I;
end else begin
if DTP = dtpMiliSec then
DD[I] := 3 * FDigitWidth
else
DD[I] := 2 * FDigitWidth;
K := I;
end;
end;
if N < K then
N := K;
for I := M to N do begin
if DD[I] <> 0 then begin
R.Right := R.Left + DD[I];
if I <= 3 then begin
if (I = FMonthPos) and FShowMonthNames then begin
TextStyle.Alignment := taCenter;
if DateIsNull then
S := FNullMonthText
else
S := FTextPart[I];
end else
S := FTextPart[I];
end else
S := FTimeText[TDateTimePart(I - 1)];
if I = SelectStep then begin
TextStyle.Opaque := True;
Canvas.Brush.Color := clHighlight;
Canvas.Font.Color := clHighlightText;
Canvas.TextRect(R, R.Left, R.Top, S, TextStyle);
TextStyle.Opaque := False;
Canvas.Brush.Color := Color;
Canvas.Font.Color := Self.Font.Color;
end else
Canvas.TextRect(R, R.Left, R.Top, S, TextStyle);
TextStyle.Alignment := taRightJustify;
R.Left := R.Right;
if I < L then begin
R.Right := R.Left + FSeparatorWidth;
if not ((I = FMonthPos) and FShowMonthNames) then
Canvas.TextRect(R, R.Left, R.Top, FDateSeparator, TextStyle);
end else if I > L then begin
if I = K then begin
R.Right := R.Left + FDigitWidth;
end else if I < K then begin
if I = Ord(dtpMiliSec) then begin
R.Right := R.Left + FDecimalSeparatorWidth;
Canvas.TextRect(R, R.Left, R.Top, FDecimalSeparator, TextStyle);
end else begin
R.Right := R.Left + FTimeSeparatorWidth;
Canvas.TextRect(R, R.Left, R.Top, FTimeSeparator, TextStyle);
end;
end;
end else
begin
if FTrailingSeparator then begin
R.Right := R.Left + FSepNoSpaceWidth;
Canvas.TextRect(R, R.Left, R.Top,
TrimRight(FDateSeparator), TextStyle);
end;
if FTimeWidth > 0 then
R.Right := R.Right + 2 * FDigitWidth;
end;
R.Left := R.Right;
end;
end;
end;
if ShowCheckBox then
ThemeServices.DrawElement(Canvas.Handle,
ThemeServices.GetElementDetails(CheckStates[Enabled, Checked, FMouseInCheckBox]),
GetCheckBoxRect);
inherited Paint;
end;
procedure TCustomDateTimePicker.EditingDone;
begin
if FNoEditingDone <= 0 then begin
ConfirmChanges;
inherited EditingDone;
end;
end;
procedure TCustomDateTimePicker.ArrowMouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
SetFocusIfPossible;
if FAllowDroppingCalendar then
DropDownCalendarForm
else begin
DestroyCalendarForm;
FAllowDroppingCalendar := True;
end;
end;
procedure TCustomDateTimePicker.UpDownClick(Sender: TObject;
Button: TUDBtnType);
begin
SetFocusIfPossible;
if not FReadOnly then begin
Inc(FUserChanging);
try
if Button = btNext then
IncreaseCurrentTextPart
else
DecreaseCurrentTextPart;
finally
Dec(FUserChanging);
end;
end;
end;
procedure TCustomDateTimePicker.DoDropDown;
begin
if Assigned(FOnDropDown) then
FOnDropDown(Self);
end;
procedure TCustomDateTimePicker.DoCloseUp;
begin
if Assigned(FOnCloseUp) then
FOnCloseUp(Self);
end;
function TCustomDateTimePicker.GetChecked: Boolean;
begin
Result := (not FShowCheckBox) or FChecked;
end;
function TCustomDateTimePicker.AreSeparatorsStored: Boolean;
begin
Result := not FUseDefaultSeparators;
end;
function TCustomDateTimePicker.GetDate: TDate;
begin
if DateIsNull then
Result := NullDate
else
Result := Int(FDateTime);
end;
function TCustomDateTimePicker.GetDateTime: TDateTime;
begin
if DateIsNull then
Result := NullDate
else
Result := FDateTime;
end;
function TCustomDateTimePicker.GetDroppedDown: Boolean;
begin
Result := Assigned(FCalendarForm);
end;
function TCustomDateTimePicker.GetTime: TTime;
begin
if DateIsNull then
Result := NullDate
else
Result := Abs(Frac(FDateTime));
end;
procedure TCustomDateTimePicker.CustomMonthNamesChange(Sender: TObject);
begin
if (FMonthDisplay = mdCustom) then
begin
//trick the control to re-apply the custom names
FMonthDisplay := mdLong;
SetMonthDisplay(mdCustom);
end;
end;
procedure TCustomDateTimePicker.SetAlignment(AValue: TAlignment);
begin
if FAlignment <> AValue then begin
FAlignment := AValue;
Invalidate;
end;
end;
procedure TCustomDateTimePicker.SetArrowShape(const AValue: TArrowShape);
begin
if FArrowShape = AValue then Exit;
FArrowShape := AValue;
if FArrowButton <> nil then
FArrowButton.Invalidate;
end;
procedure TCustomDateTimePicker.SetAutoButtonSize(AValue: Boolean);
begin
if FAutoButtonSize <> AValue then begin
FAutoButtonSize := AValue;
if AValue then
AutoResizeButton
else begin
if Assigned(FUpDown) then
FUpDown.Width := Scale96ToFont(DefaultUpDownWidth)
else if Assigned(FArrowButton) then
FArrowButton.Width := Scale96ToFont(DefaultArrowButtonWidth);
end;
end;
end;
procedure TCustomDateTimePicker.SetCalAlignment(AValue: TDTCalAlignment);
begin
if FCalAlignment = AValue then Exit;
FCalAlignment := AValue;
end;
procedure TCustomDateTimePicker.SetCalendarWrapperClass(
AValue: TCalendarControlWrapperClass);
begin
if FCalendarWrapperClass = AValue then Exit;
FCalendarWrapperClass := AValue;
end;
procedure TCustomDateTimePicker.SetCenturyFrom(const AValue: Word);
begin
if FCenturyFrom = AValue then Exit;
FCenturyFrom := AValue;
AdjustEffectiveCenturyFrom;
end;
procedure TCustomDateTimePicker.CheckBoxChange;
begin
if Assigned(FOnCheckBoxChange) then
FOnCheckBoxChange(Self);
if FOnCheckBoxChangeHandlers <> nil then
FOnCheckBoxChangeHandlers.CallNotifyEvents(Self);
end;
procedure TCustomDateTimePicker.SetFocusIfPossible;
var
F: TCustomForm;
begin
Inc(FNoEditingDone);
try
F := GetParentForm(Self);
if Assigned(F) and F.CanFocus and CanTab then
SetFocus;
finally
Dec(FNoEditingDone);
end;
end;
procedure TCustomDateTimePicker.AutoResizeButton;
begin
if Assigned(FArrowButton) then
FArrowButton.Width := MulDiv(ClientHeight, 9, 10)
else if Assigned(FUpDown) then
FUpDown.Width := MulDiv(ClientHeight, 79, 100);
end;
procedure TCustomDateTimePicker.CheckAndApplyKey(const Key: Char);
var
S: String;
DTP: TDateTimePart;
N, L: Integer;
YMD: TYMD;
HMSMs: THMSMs;
D, T: TDateTime;
Finished, ForceChange: Boolean;
begin
FCorrectedValue := 0;
if not FReadOnly then begin
Finished := False;
ForceChange := False;
if FSelectedTextPart = 8 then begin
case upCase(Key) of
'A': S := 'AM';
'P': S := 'PM';
else
Finished := True;
end;
ForceChange := True;
end else if Key in ['0'..'9'] then begin
DTP := GetSelectedDateTimePart;
if DTP = dtpYear then
N := 4
else if DTP = dtpMiliSec then
N := 3
else
N := 2;
S := Trim(GetSelectedText);
if FUserChangedText and (Length(S) < N) then
S := S + Key
else
S := Key;
if Length(S) >= N then begin
L := StrToInt(S);
if DTP < dtpHour then begin
YMD := GetYYYYMMDD(True);
case DTP of
dtpDay:
YMD.Day := L;
dtpMonth:
YMD.Month := L;
otherwise
YMD.Year := L;
end;
if AutoAdvance and (YMD.Day <= 31) and
(YMD.Day > NumberOfDaysInMonth(YMD.Month, YMD.Year)) then begin
FCorrectedDTP := dtpAMPM;
case DTP of
dtpDay:
case FEffectiveDateDisplayOrder of
ddoDMY:
FCorrectedDTP := dtpMonth;
ddoMDY:
FCorrectedDTP := dtpYear;
otherwise
end;
dtpMonth:
case FEffectiveDateDisplayOrder of
ddoDMY:
FCorrectedDTP := dtpYear;
otherwise
FCorrectedDTP := dtpDay;
FCorrectedValue := NumberOfDaysInMonth(YMD.Month, YMD.Year);
YMD.Day := FCorrectedValue;
end;
otherwise
if (FEffectiveDateDisplayOrder = ddoYMD) and (YMD.Month = 2)
and (YMD.Day = 29) and not IsLeapYear(YMD.Year) then
FCorrectedDTP := dtpMonth;
end;
case FCorrectedDTP of
dtpMonth:
begin
FCorrectedValue := YMD.Month + 1;
YMD.Month := FCorrectedValue;
end;
dtpYear:
if (YMD.Day = 29) and (YMD.Month = 2) then begin
FCorrectedValue := ((YMD.Year + 3) div 4) * 4;
if (FCorrectedValue mod 100 = 0) and (FCorrectedValue mod 400 <> 0) then
FCorrectedValue := FCorrectedValue + 4;
YMD.Year := FCorrectedValue;
end;
otherwise
end;
end;
if TryEncodeDate(YMD.Year, YMD.Month, YMD.Day, D)
and (D >= MinDate) and (D <= MaxDate) then
ForceChange := True
else if N = 4 then begin
UpdateDate;
Finished := True;
end else
S := Key;
end else begin
if (DTP = dtpHour) and (FTimeFormat = tf12) then begin
if not (L in [1..12]) then
S := Key
else
ForceChange := True;
end else begin
HMSMs := GetHMSMs(True);
case DTP of
dtpHour: HMSMs.Hour := L;
dtpMinute: HMSMs.Minute := L;
dtpSecond: HMSMs.Second := L;
dtpMiliSec: HMSMs.MiliSec := L;
otherwise
end;
if not TryEncodeTime(HMSMs.Hour, HMSMs.Minute, HMSMs.Second,
HMSMs.MiliSec, T) then
S := Key
else
ForceChange := True;
end;
end;
end;
end else
Finished := True;
if (not Finished) and (GetSelectedText <> S) then begin
if (not FUserChangedText) and DateIsNull then begin
Inc(FSkipChangeInUpdateDate); // do not call Change here
try
if FSelectedTextPart <= 3 then
DateTime := SysUtils.Date
else
DateTime := SysUtils.Now;
finally
Dec(FSkipChangeInUpdateDate);
end;
end;
if (not FLeadingZeros) and (FSelectedTextPart <= 4) then
while (Length(S) > 1) and (S[1] = '0') do
Delete(S, 1, 1);
if FSelectedTextPart <= 3 then
FTextPart[FSelectedTextPart] := S
else
FTimeText[TDateTimePart(FSelectedTextPart - 1)] := S;
FUserChangedText := True;
if ForceChange then begin
if FAutoAdvance then begin
MoveSelectionLR(False);
Invalidate;
end else
UpdateIfUserChangedText;
end else
Invalidate;
DoAutoCheck;
end;
FCorrectedValue := 0;
end;
end;
procedure TCustomDateTimePicker.CheckAndApplyKeyCode(var Key: Word;
const ShState: TShiftState);
var
K: Word;
begin
if (Key = VK_SPACE) then begin
if ShowCheckBox then
Checked := not Checked;
end else if FTextEnabled then begin
case Key of
VK_LEFT, VK_RIGHT, VK_OEM_COMMA, VK_OEM_PERIOD, VK_DIVIDE,
VK_OEM_MINUS, VK_SEPARATOR, VK_DECIMAL, VK_SUBTRACT:
begin
K := Key;
Key := 0;
MoveSelectionLR(K = VK_LEFT);
Invalidate;
end;
VK_UP:
begin
Key := 0;
UpdateIfUserChangedText;
if not FReadOnly then
begin
IncreaseCurrentTextPart;
DoAutoCheck;
end;
end;
VK_DOWN:
begin
Key := 0;
if (ShState = [ssAlt]) and Assigned(FArrowButton) then
DropDownCalendarForm
else begin
UpdateIfUserChangedText;
if not FReadOnly then
begin
DecreaseCurrentTextPart;
DoAutoCheck;
end;
end;
end;
VK_RETURN:
if not FReadOnly then
EditingDone;
VK_ESCAPE:
if not FReadOnly then begin
UndoChanges;
EditingDone;
end;
VK_N:
if (not FReadOnly) and FNullInputAllowed then
SetDateTime(NullDate);
end;
end;
end;
procedure TCustomDateTimePicker.WMKillFocus(var Message: TLMKillFocus);
begin
// On Qt it seems that WMKillFocus happens even when focus jumps to some other
// form. This behaviour differs from win and gtk 2 (where it happens only when
// focus jumps to other control on the same form) and we should prevent it at
// least for our calendar, because it triggers EditingDone.
if Screen.ActiveCustomForm <> FCalendarForm then
inherited WMKillFocus(Message);
end;
procedure TCustomDateTimePicker.WMSize(var Message: TLMSize);
begin
inherited WMSize(Message);
if FAutoButtonSize then
AutoResizeButton;
end;
procedure TCustomDateTimePicker.DropDownCalendarForm;
begin
if FAllowDroppingCalendar and FTextEnabled and Assigned(FArrowButton) then
if not (FReadOnly or Assigned(FCalendarForm)
or (csDesigning in ComponentState))
then begin
FCalendarForm := TDTCalendarForm.CreateNewDTCalendarForm(nil, Self);
FCalendarForm.Show;
end;
end;
{ TDTUpDown }
{ When our UpDown control gets enabled/disabled, the two its buttons' Enabled
property is set accordingly. }
procedure TDTUpDown.SetEnabled(Value: Boolean);
procedure SetEnabledForAllChildren(AWinControl: TWinControl);
var
I: Integer;
C: TControl;
begin
for I := 0 to AWinControl.ControlCount - 1 do begin
C := AWinControl.Controls[I];
C.Enabled := Value;
if C is TWinControl then
SetEnabledForAllChildren(TWinControl(C));
end;
end;
begin
inherited SetEnabled(Value);
SetEnabledForAllChildren(Self);
end;
{ Our UpDown control is always aligned, but setting its PreferredHeight
unconditionally to 1 prevents the UpDown to mess with our PreferredHeight.
The problem is that if we didn't do this, when our Height is greater than
really preferred, UpDown prevents it to be set correctly when we set AutoSize
to True. }
procedure TDTUpDown.CalculatePreferredSize(var PreferredWidth, PreferredHeight:
integer; WithThemeSpace: Boolean);
begin
inherited CalculatePreferredSize(PreferredWidth, PreferredHeight,
WithThemeSpace);
PreferredHeight := 1;
end;
{ We don't want to let EditingDone event to fire each time up-down buttons get
clicked. That is why WndProc is overridden. }
procedure TDTUpDown.WndProc(var Message: TLMessage);
begin
if ((Message.msg >= LM_MOUSEFIRST) and (Message.msg <= LM_MOUSELAST))
or ((Message.msg >= LM_MOUSEFIRST2) and (Message.msg <= LM_MOUSELAST2)) then begin
Inc(DTPicker.FNoEditingDone);
try
inherited WndProc(Message);
finally
Dec(DTPicker.FNoEditingDone);
end
end else
inherited WndProc(Message);
end;
{ TDTSpeedButton }
{ See the comment above TDTUpDown.CalculatePreferredSize }
procedure TDTSpeedButton.CalculatePreferredSize(var PreferredWidth,
PreferredHeight: integer; WithThemeSpace: Boolean);
begin
inherited CalculatePreferredSize(PreferredWidth, PreferredHeight,
WithThemeSpace);
PreferredHeight := 1;
end;
{ Prevent EditingDone to fire whenever the SpeedButton gets clicked }
procedure TDTSpeedButton.MouseDown(Button: TMouseButton; Shift: TShiftState; X,
Y: Integer);
begin
Inc(DTPicker.FNoEditingDone);
try
inherited MouseDown(Button, Shift, X, Y);
finally
Dec(DTPicker.FNoEditingDone);
end;
end;
procedure TDTSpeedButton.Paint;
procedure DrawThemedDropDownArrow;
var
Details: TThemedElementDetails;
ArrowState: TThemedToolBar;
ASize: TSize;
ARect: TRect;
begin
if Enabled then
ArrowState := ttbSplitButtonDropDownNormal
else
ArrowState := ttbSplitButtonDropDownDisabled;
Details := ThemeServices.GetElementDetails(ArrowState);
ASize := ThemeServices.GetDetailSizeForPPI(Details, Font.PixelsPerInch);
ARect := Rect(0, 0, Width, Height);
InflateRect(ARect, -(ARect.Right - ARect.Left - ASize.cx) div 2, 0);
ThemeServices.DrawElement(Canvas.Handle, Details, ARect);
end;
const
ArrowColor = TColor($8D665A);
var
X, Y: Integer;
begin
inherited Paint;
if DTPicker.FArrowShape = asTheme then
DrawThemedDropDownArrow
else begin
// First I meant to put arrow images in a lrs file. In my opinion, however, that
// wouldn't be an elegant option for so simple shapes.
Canvas.Brush.Style := bsSolid;
Canvas.Pen.Color := ArrowColor;
Canvas.Brush.Color := Canvas.Pen.Color;
X := (Width - 9) div 2;
Y := (Height - 6) div 2;
{ Let's draw shape of the arrow on the button: }
case DTPicker.FArrowShape of
asClassicLarger:
{ triangle: }
Canvas.Polygon([Point(X + 0, Y + 1), Point(X + 8, Y + 1),
Point(X + 4, Y + 5)]);
asClassicSmaller:
{ triangle -- smaller variant: }
Canvas.Polygon([Point(X + 1, Y + 2), Point(X + 7, Y + 2),
Point(X + 4, Y + 5)]);
asModernLarger:
{ modern: }
Canvas.Polygon([Point(X + 0, Y + 1), Point(X + 1, Y + 0),
Point(X + 4, Y + 3), Point(X + 7, Y + 0), Point(X + 8, Y + 1), Point(X + 4, Y + 5)]);
asModernSmaller:
{ modern -- smaller variant: }
Canvas.Polygon([Point(X + 1, Y + 2), Point(X + 2, Y + 1),
Point(X + 4, Y + 3), Point(X + 6, Y + 1), Point(X + 7, Y + 2), Point(X + 4, Y + 5)]);
otherwise // asYetAnotherShape:
{ something in between, not very pretty: }
Canvas.Polygon([Point(X + 0, Y + 1), Point(X + 1, Y + 0),
Point(X + 2, Y + 1), Point(X + 6, Y + 1),Point(X + 7, Y + 0), Point(X + 8, Y + 1), Point(X + 4, Y + 5)]);
end;
end;
end;
procedure TCustomDateTimePicker.UpdateShowArrowButton;
procedure CreateArrowBtn;
begin
if not Assigned(FArrowButton) then begin
DestroyCalendarForm;
DestroyUpDown;
FArrowButton := TDTSpeedButton.Create(Self);
FArrowButton.ControlStyle := FArrowButton.ControlStyle +
[csNoFocus, csNoDesignSelectable];
FArrowButton.Flat := dtpoFlatButton in Options;
TDTSpeedButton(FArrowButton).DTPicker := Self;
FArrowButton.SetBounds(0, 0, Scale96ToFont(DefaultArrowButtonWidth), 1);
FArrowButton.Parent := Self;
FAllowDroppingCalendar := True;
TDTSpeedButton(FArrowButton).OnMouseDown := @ArrowMouseDown;
end;
end;
procedure CreateUpDown;
begin
if not Assigned(FUpDown) then begin
DestroyArrowBtn;
FUpDown := TDTUpDown.Create(Self);
FUpDown.ControlStyle := FUpDown.ControlStyle +
[csNoFocus, csNoDesignSelectable];
TDTUpDown(FUpDown).DTPicker := Self;
TDTUpDown(FUpDown).Flat := dtpoFlatButton in Options;
FUpDown.SetBounds(0, 0, Scale96ToFont(DefaultUpDownWidth), 1);
FUpDown.Parent := Self;
TDTUpDown(FUPDown).OnClick := @UpDownClick;
end;
end;
var
ReallyShowArrowButton: Boolean;
begin
if FDateMode = dmNone then begin
DestroyArrowBtn;
DestroyUpDown;
end else begin
ReallyShowArrowButton := (FDateMode = dmComboBox) and
not (dtpDay in FEffectiveHideDateTimeParts);
if (ReallyShowArrowButton <> Assigned(FArrowButton)) or
(Assigned(FArrowButton) = Assigned(FUpDown)) then begin
DisableAlign;
try
if ReallyShowArrowButton then
CreateArrowBtn
else
CreateUpDown;
ArrangeCtrls;
finally
EnableAlign;
end;
end;
end;
end;
procedure TCustomDateTimePicker.DestroyUpDown;
begin
if Assigned(FUpDown) then begin
TDTUpDown(FUPDown).OnClick := nil;
FreeAndNil(FUpDown);
end;
end;
procedure TCustomDateTimePicker.DoAutoCheck;
begin
if dtpoAutoCheck in Options then
SetChecked(True);
end;
procedure TCustomDateTimePicker.DoAutoAdjustLayout(
const AMode: TLayoutAdjustmentPolicy;
const AXProportion, AYProportion: Double);
begin
inherited;
if AMode in [lapAutoAdjustWithoutHorizontalScrolling, lapAutoAdjustForDPI] then
begin
if (not FAutoButtonSize) then begin
if Assigned(FArrowButton) then
FArrowButton.Width := Scale96ToFont(DefaultArrowButtonWidth);
if Assigned(FUpDown) then
FUpDown.Width := Scale96ToFont(DefaultUpdownWidth);
end;
end;
end;
procedure TCustomDateTimePicker.DestroyArrowBtn;
begin
if Assigned(FArrowButton) then begin
TDTSpeedButton(FArrowButton).OnMouseDown := nil;
DestroyCalendarForm;
FreeAndNil(FArrowButton);
end;
end;
constructor TCustomDateTimePicker.Create(AOwner: TComponent);
var
I: Integer;
DTP: TDateTimePart;
begin
inherited Create(AOwner);
with GetControlClassDefaultSize do
SetInitialBounds(0, 0, cx, cy);
FAlignment := taLeftJustify;
FCalAlignment := dtaDefault;
FCorrectedDTP := dtpAMPM;
FCorrectedValue := 0;
FSkipChangeInUpdateDate := 0;
FNoEditingDone := 0;
FArrowShape := asTheme;
FAllowDroppingCalendar := True;
FChecked := True;
FOnDropDown := nil;
FOnCloseUp := nil;
ParentColor := False;
FArrowButton := nil;
FUpDown := nil;
FKind := dtkDate;
FNullInputAllowed := True;
FOptions := cDefOptions;
{ Thanks to Luiz Américo for this:
Lazarus ignores empty string when saving to lrs. Therefore, the problem
is, when TextForNullDate is set to an empty string and after the project
is saved and opened again, then, this property gets default value NULL
instead of empty string. The following condition seems to be a workaround
for this. }
{$if fpc_fullversion < 030200}
// This hack is no more needed since FPC 3.2 (see bug report 31985)
if (AOwner = nil) or not (csReading in Owner.ComponentState) then
{$endif}
FTextForNullDate := 'NULL';
FCenturyFrom := 1941;
FRecalculatingTextSizesNeeded := True;
FOnChange := nil;
FOnChangeHandlers := nil;
FOnCheckBoxChange := nil;
FOnCheckBoxChangeHandlers := nil;
FSeparatorWidth := 0;
FSepNoSpaceWidth := 0;
FDigitWidth := 0;
FTimeSeparatorWidth := 0;
FAMPMWidth := 0;
FDateWidth := 0;
FTimeWidth := 0;
FTextWidth := 0;
FTextHeight := 0;
FMonthWidth := 0;
FHideDateTimeParts := [];
FShowMonthNames := False;
FNullMonthText := '';
for I := Low(FTextPart) to High(FTextPart) do
FTextPart[I] := '';
for DTP := dtpHour to dtpAMPM do
FTimeText[DTP] := '';
FTimeDisplay := tdHMS;
FTimeFormat := tf24;
FLeadingZeros := True;
FUserChanging := 0;
FReadOnly := False;
FDateTime := SysUtils.Now;
FConfirmedDateTime := FDateTime;
FMinDate := TheSmallestDate;
FMaxDate := TheBiggestDate;
FTrailingSeparator := False;
FDateDisplayOrder := ddoTryDefault;
FSelectedTextPart := 1;
FUseDefaultSeparators := True;
FDateSeparator := DefaultFormatSettings.DateSeparator;
FTimeSeparator := DefaultFormatSettings.TimeSeparator;
FDecimalSeparator := DefaultFormatSettings.DecimalSeparator;
FEffectiveCenturyFrom := FCenturyFrom;
FJumpMinMax := False;
ParentColor := False;
TabStop := True;
BorderWidth := 2;
BorderStyle := bsSingle;
ParentFont := True;
AutoSize := True;
FTextEnabled := True;
FCalendarForm := nil;
FDoNotArrangeControls := True;
FCascade := False;
FAutoButtonSize := False;
FAutoAdvance := True;
FCalendarWrapperClass := nil;
FEffectiveHideDateTimeParts := [];
AdjustEffectiveDateDisplayOrder;
AdjustEffectiveHideDateTimeParts;
FCustomMonthNames := TStringList.Create;
TStringList(FCustomMonthNames).SkipLastLineBreak := True;
TStringList(FCustomMonthNames).OnChange := @CustomMonthNamesChange;
FInitiallyLoadedMonthNames := '';
FMonthNames := 'Long';
SetMonthDisplay(mdLong);
SetDateMode(dmComboBox);
end;
procedure TCustomDateTimePicker.AddHandlerOnChange(
const AOnChange: TNotifyEvent; AsFirst: Boolean);
begin
if FOnChangeHandlers = nil then
FOnChangeHandlers := TMethodList.Create;
FOnChangeHandlers.Add(TMethod(AOnChange), not AsFirst);
end;
procedure TCustomDateTimePicker.AddHandlerOnCheckBoxChange(
const AOnCheckBoxChange: TNotifyEvent; AsFirst: Boolean);
begin
if FOnCheckBoxChangeHandlers = nil then
FOnCheckBoxChangeHandlers := TMethodList.Create;
FOnCheckBoxChangeHandlers.Add(TMethod(AOnCheckBoxChange), not AsFirst);
end;
procedure TCustomDateTimePicker.RemoveHandlerOnChange(AOnChange: TNotifyEvent);
begin
if Assigned(FOnChangeHandlers) then
FOnChangeHandlers.Remove(TMethod(AOnChange));
end;
procedure TCustomDateTimePicker.RemoveHandlerOnCheckBoxChange(
AOnCheckBoxChange: TNotifyEvent);
begin
if Assigned(FOnCheckBoxChangeHandlers) then
FOnCheckBoxChangeHandlers.Remove(TMethod(AOnCheckBoxChange));
end;
destructor TCustomDateTimePicker.Destroy;
begin
FDoNotArrangeControls := True;
DestroyArrowBtn;
DestroyUpDown;
SetShowCheckBox(False);
FOnChangeHandlers.Free;
FOnCheckBoxChangeHandlers.Free;
FCustomMonthNames.Free;
inherited Destroy;
end;
function TCustomDateTimePicker.DateIsNull: Boolean;
begin
Result := IsNullDate(FDateTime);
end;
end.