2008. március 5., szerda

How to enter dates into a TDateTimePicker by keyboard only


Problem/Question/Abstract:

We have decided to replace all occurrences of TMaskEdit in our applications with TDateTimePicker's (of course only where they were used for entering dates). The problem is making the transition as easy as possible for the users. TDateTimePicker as it is is not very well-suited for keyboard-only input. The first annoyance is that you have to explicitly enter the separators. TMaskEdit just jumped to the next figure if you entered a number instead of the separator character. It becomes worse still when ShowCheckbox is True. In that case the focus is automatically shifted to the checkbox after having entered the first two digits, essentially making it impossible to enter a date by keyboard only (unless you manually cursor to the every single figure). Does anyone know if it possible at all to overcome these limitations by simply subclassing TDateTimePicker?

Answer:

Here is the routine that I use for date entry edits. Feel free to use it if you just want keyboard entry of dates. Here's the way it works: As the user types in the edit, it's checked against the current ShortDateFormat setting to determine whether it's in the month, day or year portion. If, for instance, they are in the month portion and they type a '3', it knows that it must be the third month and so puts '03' and goes to the next section (if any). If you want to default any portion to the current day, month or year, simply hit the space bar. This gives users a really fast way to fill in dates, especially the current day's. All you need to do is assign the OnKeyPress event of any edit control and make a simple call:

DateKeyPress(self, Key);

{Included because I use it to tab to the next control when the date is complete}

procedure PressTabKey(Shift: boolean = false);
begin
  if Shift then
    keybd_event(VK_SHIFT, 0, 0, 0);
  keybd_event(VK_TAB, 0, 0, 0);
  keybd_event(VK_TAB, 0, KEYEVENTF_KEYUP, 0);
  if Shift then
    keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
end;

procedure DateKeyPress(Sender: TObject; var Key: char);
const
  Zero: char = '0';
  DateParts: array[1..3] of string = ('', '', '');
  SeparatorChar: string = '';

  procedure GetDateParts;
  var
    x, y: integer;
    s: string;
    c: char;
  begin
    s := ShortDateFormat;
    y := 1;
    c := s[1];
    for x := 1 to length(s) do
      if (s[x] <> DateSeparator) then
      begin
        if (s[x] <> c) then
        begin
          c := s[x];
          inc(y);
        end;
        DateParts[y] := DateParts[y] + s[x];
      end
      else
      begin
        inc(y);
        c := s[x + 1];
      end;
    if pos(DateSeparator, s) <> 0 then
      SeparatorChar := DateSeparator
    else
      SeparatorChar := '';
  end;

  function FixDatePart(s: string; Part: integer): string;
  begin
    if (s <> '') and (s[length(s)] = DateSeparator) then
      delete(s, length(s), 1);
    if (s = '') then
      s := FormatDateTime(DateParts[Part], Now);
    if (DateParts[Part][1] in ['m', 'M', 'd', 'D']) then
      result := format('%.' + IntToStr(length(DateParts[Part])) + 'd', [StrToInt(s)])
    else if (length(s) < length(DateParts[Part])) then
      result := copy(FormatDateTime(DateParts[Part], Now), 1,
        length(Dateparts[Part]) - length(s)) + s
    else
      result := s;
  end;

var
  s: string;
  x,
    sepLength: integer;
begin
  if DateParts[1] = '' then
    GetDateParts;
  if ord(Key) in ActionKeys then
    exit;
  s := copy(TEdit(Sender).Text, 1, TEdit(Sender).SelStart);
  x := length(s);
  sepLength := length(SeparatorChar);
  case Key of
    ' ':
      begin
        if (x = length(DateParts[1]) + sepLength) then
          s := s + FixDatePart('', 2) + SeparatorChar
        else if (x = length(DateParts[1] + DateParts[2]) + (sepLength * 2)) then
          s := s + FixDatePart('', 3)
        else if (x = 0) then
          s := FixDatePart('', 1) + SeparatorChar
        else if (x <= length(DateParts[1])) then
          s := FixDatePart(s, 1) + SeparatorChar + FixDatePart('', 2) + SeparatorChar
        else if (x <= (length(DateParts[1] + DateParts[2]) + (sepLength * 2))) then
          s := copy(s, 1, length(DateParts[1]) + sepLength) + FixDatePart(copy(s, length(DateParts[1]) + sepLength + 1, length(s)), 2) + SeparatorChar + FormatDateTime(DateParts[3], Now)
        else
          s := copy(s, 1, length(DateParts[1] + DateParts[2]) + (sepLength * 2)) +
            FixDatePart(copy(s, length(DateParts[1] + DateParts[2]) +
            (sepLength * 2) + 1, length(s)), 3);
        TEdit(Sender).Text := s;
        Key := #0;
        TEdit(Sender).SelStart := length(s);
      end;
    '0'..'9':
      begin
        if (x in [length(DateParts[1]), length(DateParts[1] + DateParts[2]) + sepLength]) then
          s := s + SeparatorChar + Key
        else if (x = 0) and (((DateParts[1][1] in ['m', 'M']) and (Key in ['2'..'9'])) or((DateParts[1][1] in ['d', 'D']) and (Key in ['4'..'9']))) then
          s := FixDatePart(Key, 1) + SeparatorChar
        else if (x = length(DateParts[1]) + sepLength) and (((DateParts[2][1] in ['m', 'M']) and (Key in ['2'..'9'])) or ((DateParts[2][1] in ['d', 'D']) and (Key in ['4'..'9']))) then
          s := s + FixDatePart(Key, 2) + SeparatorChar
        else
          s := s + Key;
        if (length(s) = length(DateParts[1])) or (length(s) = length(DateParts[1] + DateParts[2]) + sepLength) then
          s := s + SeparatorChar;
        TEdit(Sender).Text := s;
        Key := #0;
        TEdit(Sender).SelStart := length(s);
      end;
    { uncomment this to use N/A values
    'n','N':
      begin
        TEdit(Sender).Text := 'N/A';
        TEdit(Sender).SelStart := 0;
        TEdit(Sender).SelLength := 3;
        Key := #0;
      end;}
  else
    begin
      if (Key = DateSeparator) then
      begin
        if s[x] <> DateSeparator then
        begin
          if x = length(DateParts[1]) - 1 then
            s := Zero + s + DateSeparator
          else if x = 4 then
          begin
            insert(Zero, s, 4);
            s := s + '/';
          end;
        end;
        TEdit(Sender).Text := s;
        Key := #0;
        TEdit(Sender).SelStart := length(s);
      end
      else
        Key := #0;
    end;
  end;
  if length(TEdit(Sender).Text) = length(ShortDateFormat) then
    PressTabKey;
end;

Nincsenek megjegyzések:

Megjegyzés küldése