Форум программистов, компьютерный форум, киберфорум
Jin X
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  

DateTimePicker – либо дата, либо время... или нет

Запись от Jin X размещена 14.03.2019 в 21:00
Показов 15441 Комментарии 4

DateTimePicker – либо дата, либо время... или нет?

Что не так?

На днях я столкнулся с такой проблемой. В DelphiC++Builder) имеется компонент класса TDateTimePicker, позволяющий пользователю вполне удобным образом выбирать дату или время. Ключевой момент: дату ИЛИ время. Что именно – определяется опубликованным (published) свойством Kind, которое может принимать значение dtkDate или dtkTime. Однако компонент имеет также опубликованное свойство Format, которое позволяет задавать свой формат отображения даты и времени, причём записать туда можно сразу и дату, и время. К примеру, вот в таком формате: dd-MMM-yyyy, HH:mm:ss (отображаться это будет так: 14-мар-2019, 15:08:37). Прикол в том, что пользователь может менять как дату, так и время (вне зависимости от значения свойства Kind), однако обновляться при этом будет только значение либо свойства Date, либо свойства Time. Неожиданно? Если внимательно читать документацию, то нет. Учитывая, что это стандартный контрол Windows, это поведение абсолютно нормально.

Что же делать, если мы хотим дать пользователю возможность выбирать и дату, и время?
  • Вариант 1: установить два компонента DateTimePicker (один с Kind = dtkDate, другой с Kind = dtkTime) и читать из них отдельно дату (Date) и время (Time) ответственно.
  • Вариант 2: создать собственный компонент на основе DateTimePicker.
  • Вариант 3: "вмешаться" в работу DateTimePicker, создав класс-перехватчик (т.е. класс-потомок с тем же именем, оставив оригинальный класс в покое) и заставить форму использовать его вместо оригинального класса.
Вам решать, какой вариант выбрать. Лично я пошёл по третьему пути (тем более, что у меня уже расставлено несколько DateTimePicker'ов на форме, которые заменять на двойные + менять код было в лом).

Рассказываю подробнее и по шагам
  1. Выбираем на всех компонентах, хранящих дату+время (или только дату), у свойства Kind значение dtkDate (там, где только время, оставляем dtkTime).

  2. Перед(!) объявлением класса формы объявляем класс TDateTimePicker как потомок Vcl.ComCtrls.TDateTimePicker (в Delphi до версий XE включительно родительский класс будет называться ComCtrls.TDateTimePicker). Ещё раз: объявить класс нужно ДО класса формы, на котором расположены компоненты.

    Если у вас несколько форм, на которых используется DateTimePicker, создайте отдельный модуль (unit), а потом добавляйте его в список uses модуля каждой формы, но обязательно после Vcl.ComCtrls (ComCtrls). В общем-то, это можно сделать и для одной формы – тут уж как вам удобнее.

  3. В создаваемом классе создаём метод SetTimeFromCaption, который будет читать время из Caption (т.е. из окна в виде текста), преобразовывать его в TDateTime и записывать в свойство Time.

  4. Перекрываем динамический (dynamic) метод Change. Этот метод вызывается при изменении содержимого компонента. Внутри делаем проверку Kind = dtkDate, и в случае совпадения вызываем метод SetTimeFromCaption и inherited.

Код в студию!

Delphi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
type
  // Класс-перехватчик, позволяющий читать изменение и даты, и времени в TDateTimePicker
  // ВАЖНО: При изменении свойства Format может потребоваться изменение PickerDateTimeSeparator и PickerTimeSeparator.
  TDateTimePicker = class(Vcl.ComCtrls.TDateTimePicker)
    class constructor Create;
    procedure Change; override;
  private
    procedure SetTimeFromCaption;
    const
      PickerDateTimeSeparator = ' ';  // разделитель между датой и временем (строка)
      PickerTimeSeparator = ':';      // разделитель часов, минут и секунд (символ)
    class var FmtSettings: TFormatSettings;
  end;
 
  // Далее идёт объявление класса формы
 
. . .
 
implementation
 
. . .
 
// Конструктор класса: настройка параметров (один раз для всех объектов)
class constructor TDateTimePicker.Create;
begin
  FmtSettings := TFormatSettings.Create;
  FmtSettings.TimeSeparator := PickerTimeSeparator;
end;
 
// Обновление времени, если Kind = dtkDate
procedure TDateTimePicker.Change;
begin
  if Kind = dtkDate then SetTimeFromCaption;
  inherited;
end;
 
// Установить время, исходя из текста в окне DateTimePicker'а
procedure TDateTimePicker.SetTimeFromCaption;
var
  NewTime: TDateTime;
  TimeStr: String;
  SepPos: Integer;
begin
  TimeStr := Caption;
  // Удаляем всё до последнего разделителя PickerDateTimeSeparator (вместе с разделителем)
  repeat
    SepPos := Pos(PickerDateTimeSeparator, TimeStr);
    if SepPos = 0 then Break;
    Delete(TimeStr, 1, SepPos + Length(PickerDateTimeSeparator) - 1);
  until False;
  // Преобразуем строку в формат времени
  if TryStrToTime(TimeStr, NewTime, FmtSettings) then
    Time := NewTime;
end;
Согласен, используемый в SetTimeFromCaption метод выглядит несколько костыльно. Но другого способа получения времени из DateTimePicker при значении Kind = dtkDate я, к сожалению, не знаю.
Знаете? Поделитесь в комментариях!

Несколько нюансов
  • Как написано в комментариях, при изменении формата даты/времени (свойства Format) необходимо скорректировать значения констант PickerDateTimeSeparator и PickerTimeSeparator (здесь важен разделитель между датой и временем, а также между часами, минутами, секундами).

  • Манипуляции с объектом класса TFormatSettings нужны для того, чтобы предотвратить проблемы с преобразованием строки, содержащей время, если системный разделитель времени будет отличаться от символа PickerTimeSeparator (в данном случае двоеточия). Дело в том, что символ двоеточия в DateTimePicker.Format означает именно двоеточие, в отличие, например, от функции DateTimeToString модуля System.SysUtils, в котором двоеточие означает системный разделитель часов и минут (установленный настройках операционной системы).

    Сначала я хотел обойтись без конструктора класса и создания объекта FmtSettings, просто меняя и восстанавливая значение общих настроек FormatSettings.TimeSeparator внутри SetTimeFromCaption. Но потом решил, что это колхоз. Представьте ситуацию, что у вас многопоточное приложение, и дополнительный поток использует функции вроде Format, DateTimeToStr, StrToDateTime и т.п., для преобразования даты/времени в строку или обратно. И в этот самый момент наш код меняет FormatSettings.TimeSeparator. Появляются баги, а вы не понимаете откуда (причём, баги не у вас, а у других пользователей, у которых вместо двоеточия в ОС используется, например, точка). Ситуация маловероятная, но любой нормальный программист должен учитывать и такое

  • В методе SetTimeFromCaption я оставляю строку именно после последнего вхождения PickerDateTimeSeparator в Caption, чтобы можно было использовать тот же разделитель ещё и где-то раньше (например, число и месяц тоже могут отделяться пробелом, либо вы можете добавить день недели, отделённый запятой или пробелом). Всё же, время обычно указывается в самом конце и не содержит символов-разделителей вроде запятой, пробела и т.п.

  • Если всё сделано и настроено правильно TryStrToTime всегда будет возвращать True. Однако, если вы хотите отлавливать проблемные ситуации, создайте ещё один метод (скажем, TimeFromCaptionFailure) и вызывайте его из SetTimeFromCaption в случае ошибки преобразования.

  • В компоненте DateTimePicker ранних версий Delphi изменить время при установленном значении Kind = dtkDate нельзя (пользователь не сможет ввести цифры, а нажатие на стрелки вверх-вниз ничего не изменят). Однако при значении Kind = dtkTime можно менять и дату, и время. В этом случае вам придётся преобразовывать строку, содержащую не время, а дату. Это может оказаться немного сложнее (поскольку строка может содержать название месяцев, а порядок расположения дней, месяцев и лет может отличаться на разных компьютерах). Если есть желание написать такой код, welcome! Начиная с Delphi 2009 таких проблем нет.
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 4
Комментарии
  1. Старый комментарий
    Аватар для Avazart
    Печальная штука, в C++Qt такой проблемы нет и вот интересно есть ли данная проблема в последней версии FireMonkey
    Запись от Avazart размещена 16.03.2019 в 15:13 Avazart вне форума
  2. Старый комментарий
    Аватар для Cepguo
    А как это в Builder будет выглядеть ?
    Запись от Cepguo размещена 30.04.2021 в 02:01 Cepguo вне форума
  3. Старый комментарий
    Аватар для Cepguo
    Выделил в отдельный модуль:
    C++
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    namespace TDatimePicker
    {
    class TDateTimePicker : public Vcl::ComCtrls::TDateTimePicker
    {
    private:
    protected:
    public:
     // __fastcall TDateTimePicker(TComponent* Owner);
    };
    //------------------------------------------------------------------------------
    } // namespace
    //------------------------------------------------------------------------------
    #if !defined(NO_IMPLICIT_NAMESPACE_USE)
    using namespace TDatimePicker;
    #endif
    #endif
    #define TDateTimePicker TDatimePicker::TDateTimePicker
    Компилятор ругается: [bcc32 Error] E2316 'ComCtrls' is not a member of 'Vcl'
    Запись от Cepguo размещена 30.04.2021 в 02:06 Cepguo вне форума
  4. Старый комментарий
    Аватар для Cepguo
    Если же просто
    C++
    1
    
    class TDateTimePicker : public TDateTimePicker
    то [bcc32 Error] E2029 'TDateTimePicker' must be a previously defined class or struct
    Запись от Cepguo размещена 30.04.2021 в 02:09 Cepguo вне форума
 
Новые блоги и статьи
Жизня: рисунок укладки багажа, сделанный клодом
anaschu 21.08.2026
Сделал 15 снимков, он по снимкам сделал схему.
Был там один разговор по поводу свободы в материальном мире.
kumehtar 19.08.2026
Суть: рассматривается живое существо, оказавшееся внутри довольно странной системы (этого мира) и пытающееся обустроить в ней свой кусок пространства. Жизнь действительно предъявляет каждому. . .
Когда логика программы не спасает от человеческих ошибок
Maks 18.08.2026
В последнее время всё чаще и чаще сталкиваюсь с таким явлением, как абсолютная невнимательность (или глупость) пользователей. Проявляется это чаще всего на работе в коллективе. Допустим, человек с. . .
Лето уходит
kumehtar 17.08.2026
Мысли в слух
kumehtar 17.08.2026
Забавно, насколько сейчас стала доступна информация. Например о магии, духовном развитии, медитациях, и других подобных направлениях, ранее зачастую тайных, передаваемых от учителя к ученику. Хотя. . .
Перемещение строк из ТЧ в другой документ с учетом текущего пробега
Maks 17.08.2026
Реализация из решения ниже выполнена на примере нетипового документа "Автозапчасти", с ТЧ "Шины". За основу взят алгоритм отсюда: https:/ / www. cyberforum. ru/ blogs/ 359708/ 10838. html Задача: . . .
Саморегулирующийся социальный контракт для сервера cross-section.
Hrethgir 14.08.2026
С кодом конечно таких глубоких размышлений пока не было, впрочем я уже привык к алгоритмизации. Суть предмета записи: снова в диалоге с нейросетью (я взял пока себе ник для учётки админа - Rector). . . .
Часы электронные
Uhbif79 12.08.2026
Выкладываю программу часов. Программа позволяет: 1. Использовать системное время и дату, 2. Есть возможность вводить время и дату вручную. 3. Реализованы 2 будильника: начало и конец рабочего дня. . . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru