0

Is there any way of explicitly setting the focus of a TDateTimePicker (set as a time picker) to the "seconds" field when the control receives focus for the first time? The default seems to be the "hours" field. The control seems to remember which of H, M, S had focus when you tab between controls, and that's OK, but I want to be able to explicitly set the focus to the seconds field when the dialog with the control first shows.

This answer suggests toggling the format (which initialises focus to the "hours" field), and I could then send two cursor right messages to move to the seconds field, but I'm hoping for something a bit less Heath Robinson.

rossmcm
  • 5,493
  • 10
  • 55
  • 118
  • 4
    There is no message you can send to the control to select a specific field, according to [MSDN](https://msdn.microsoft.com/en-us/library/windows/desktop/ff485908(v=vs.85).aspx), which typically means there isn't a direct way to do it. – Ken White Jan 23 '18 at 03:39

1 Answers1

2

There's no direct way of doing this, but if you include the following code, you can manipulate the location of the focus in a TDateTimePicker control:

{$IF CompilerVersion >= 17 }
{$DEFINE D2005UP }
{$ELSE }
{$UNDEF D2005UP }
{$ENDIF }

{$IF CompilerVersion >= 20 }
{$DEFINE D2009UP }
{$ELSE }
{$UNDEF D2009UP }
{$ENDIF }

PROCEDURE SendKeys(CONST Keys : ARRAY OF WORD);
  VAR
    {$IFDEF D2009UP }
    InputEvents : TArray<TInput>;
    {$ELSE }
    InputEvents : ARRAY OF TInput;
    {$ENDIF }
    {$IFNDEF D2005UP }
    I           : INTEGER;
    {$ENDIF }
    Key         : WORD;

  PROCEDURE Add(Key : WORD ; Action : WORD = 0);
    VAR
      INP       : TInput;

    BEGIN
      FillChar(INP,SizeOf(TInput),0);
      INP.Itype:=INPUT_KEYBOARD;
      INP.ki.wVk:=Key;
      INP.ki.wScan:=0;
      INP.ki.dwFlags:=Action;
      INP.ki.time:=0;
      INP.ki.dwExtraInfo:=0;
      {$IFDEF D2009UP }
        InputEvents:=InputEvents+[INP]
      {$ELSE }
        SetLength(InputEvents,SUCC(LENGTH(InputEvents)));
        InputEvents[HIGH(InputEvents)]:=INP
      {$ENDIF }
    END;

  PROCEDURE AddKeyDown(Key : WORD);
    BEGIN
      Add(Key)
    END;

  PROCEDURE AddKeyUp(Key : WORD);
    BEGIN
      Add(Key,KEYEVENTF_KEYUP)
    END;

  PROCEDURE AddKeyPress(Key : WORD);
    BEGIN
      AddKeyDown(Key);
      AddKeyUp(Key)
    END;

  BEGIN
    IF LENGTH(Keys)=0 THEN EXIT;
    {$IFDEF D2005UP }
      FOR Key IN Keys DO AddKeyPress(Key);
    {$ELSE }
      FOR I:=LOW(Keys) TO HIGH(Keys) DO BEGIN
        Key:=Keys[I];
        AddKeyPress(Key)
      END;
    {$ENDIF }
    SendInput(LENGTH(InputEvents),InputEvents[LOW(InputEvents)],SizeOf(TInput));
    Application.ProcessMessages
  end;

PROCEDURE SetDateTimePickerFocus(DTP : TDateTimePicker ; FocusTo : CHAR);
  VAR
    S   : STRING;

  BEGIN
    DTP.SetFocus;
    Application.ProcessMessages;
    S:=DTP.Format; DTP.Format:='HH';
    Application.ProcessMessages;
    DTP.Format:=S;
    Application.ProcessMessages;
    CASE UpCase(FocusTo) OF
      'H' : ; // NOTHING //
      'M' : SendKeys([VK_RIGHT]);
      'S' : SendKeys([VK_RIGHT,VK_RIGHT])
    ELSE // OTHERWISE //
      RAISE ERangeError.Create('Unsupported FocusTo value in SetDateTimePickerFocus: "'+FocusTo+'"')
    END
  END;

{$IFDEF D2005UP }
TYPE
  TDateTimePickerHelper = CLASS HELPER FOR TDateTimePicker
                            PROCEDURE   SetFocusTo(C : CHAR);
                          END;

{ TDateTimePickerHelper }

PROCEDURE TDateTimePickerHelper.SetFocusTo(C : CHAR);
  BEGIN
    SetDateTimePickerFocus(Self,C)
  END;
{$ENDIF }

If you use Delphi 2005 and up, there's also a class helper for the TDateTimePicker control.

Usage (before Delphi 2005):

SetDateTimePickerFocus(DateTimePicker1,'S'); // 'H', 'M' or 'S' to select field

Usage (Delphi 2005+):

DateTimePicker1.SetFocusTo('S'); // 'H', 'M' or 'S' to select field
HeartWare
  • 7,464
  • 2
  • 26
  • 30
  • 3
    Why `Application.ProcessMessages` is needed? – kobik Jan 23 '18 at 09:01
  • My experience tells me that in most cases where you change a property and want to make sure it has taken effect, you need to allow Windows to see your changes. I haven't tested it specifically in this case, but in many others, I have seen that if I omit a ProcessMessages at certain points, my following code doesn't work as expected, because it is based on the assumption that the previous operation has completed fully (and without ProcessMessages, it hasn't). – HeartWare Jan 23 '18 at 09:26
  • I can assure you the `Application.ProcessMessages` is not needed. and you can call `RecreateWnd` on the DTP to reset the focus to the hours field instead of the `Format` trick. – kobik Jan 25 '18 at 07:01
  • I keep warning people not to use SetFocus without CanFocus! You do this in your code. You need to call first CanFocus otherwise your code could raise an exception if the control cannot be focused. But even that will not work in all cases. CanFocus is **BROKEN** (or incomplete in Delphi). Please check this article on how to [fix it](https://gabrielmoraru.com/setfocus-is-broken-in-delphi/) – Gabriel Mar 29 '23 at 14:25