Blog

All Blog Posts  |  Next Post  |  Previous Post

Fixing the VCL TDateTimePicker checkbox handling bug for Windows Vista

Sunday, July 19, 2009

In all Delphi versions before Delphi 2009 update pack 3, there is a bug with the standard TDateTimePicker component with respect to handling the checkbox click when DateTimePicker.ShowCheckBox is set to true. Where on operating systems older than Windows Vista, clicking the checkbox triggers the OnChange event and properly updates the TDateTimePicker public property Checked, it fails to do so on Windows Vista.
Example:
procedure TForm1.DateTimePicker1Change(Sender: TObject);
begin
  if (Sender as TDateTimePicker).Checked then
    listbox1.Items.Add('datetimepicker checked')
  else
    listbox1.Items.Add('datetimepicker unchecked');
end;
This code will fail on Windows Vista and always return Checked = true.
To fix this, two possibilities are: patch COMCTRLS.PAS or write a descendent class that fixes the issue. To avoid having to recompile the VCL, the latter solution is the easiest but requires to use your own class in applications rather than the standard TDateTimePicker.

Solution 1: patching COMCTRLS.PAS:
procedure TDateTimePicker.CNNotify(var Message: TWMNotify);
var
  DT: TDateTime;
  AllowChange: Boolean;
begin
  with Message, NMHdr^ do
  begin
    Result := 0;
    case code of
      DTN_DATETIMECHANGE:
        begin
          with PNMDateTimeChange(NMHdr)^ do
          begin
            if FDroppedDown and (dwFlags = GDT_VALID) then
            begin
              FLastChange := st;
              FDateTime := SystemTimeToDateTime(FLastChange);
            end
            else begin
                if FShowCheckbox  and (dwFlags = GDT_NONE)  then  // patch here
                  FChecked := False
              else if dwFlags = GDT_VALID then
              begin
                FLastChange := st;
                DT := SystemTimeToDateTime(st);
                if Kind = dtkDate then SetDate(DT)
                else SetTime(DT);
                if FShowCheckbox then FChecked := True;
              end;
            end;
            Change;
          end;
        end;
...

Solution 2: creating a descendent class:
  TFixedDateTimePicker = class(TDateTimePicker)
  private
    procedure CNNotify(var Message: TWMNotify); message CN_NOTIFY;
  end;

{ TFixedDateTimePicker }

procedure TFixedDateTimePicker.CNNotify(var Message: TWMNotify);
begin
  with Message, NMHdr^ do
  begin
    if ShowCheckBox then
    begin
       with PNMDateTimeChange(NMHdr)^ do
       begin
         Checked := dwFlags <> GDT_NONE;
       end;
    end;
  end;
  inherited;
end;
Thanks to Roy Sharp for suggesting the COMCTRLS.PAS fix.
Fortunately, this issue was fixed from Delphi 2009 update pack 3.

Bruno Fierens




This blog post has received 1 comment.


1. Tuesday, June 14, 2011 at 12:33:12 AM

In C++ Builder:

void __fastcall TFixedDateTimePicker::CNNotify(TWMNotify& msg)
{
if(ShowCheckbox)
{
if(msg.NMHdr->code == DTN_DATETIMECHANGE)
{
PNMDateTimeChange c = reinterpret_cast<PNMDateTimeChange>(msg.NMHdr);
Checked = (c->dwFlags != GDT_NONE);
}
}

TDateTimePicker::Dispatch(&msg); // Default processing
}


...
private:
void __fastcall CNNotify(TWMNotify& msg);

public:
BEGIN_MESSAGE_MAP
VCL_MESSAGE_HANDLER(CN_NOTIFY, TWMNotify, CNNotify)
END_MESSAGE_MAP(TDateTimePicker)
...


Antony Corbett




Add a new comment

You will receive a confirmation mail with a link to validate your comment, please use a valid email address.
All fields are required.



All Blog Posts  |  Next Post  |  Previous Post