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

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

Запись от Jin X размещена 14.03.2019 в 21:00
Показов 14793 Комментарии 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 вне форума
 
Новые блоги и статьи
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
Фото: Daniel Greenwood
kumehtar 13.11.2025
Расскажи мне о Мире, бродяга
kumehtar 12.11.2025
— Расскажи мне о Мире, бродяга, Ты же видел моря и метели. Как сменялись короны и стяги, Как эпохи стрелою летели. - Этот мир — это крылья и горы, Снег и пламя, любовь и тревоги, И бескрайние. . .
PowerShell Snippets
iNNOKENTIY21 11.11.2025
Модуль PowerShell 5. 1+ : Snippets. psm1 У меня модуль расположен в пользовательской папке модулей, по умолчанию: \Documents\WindowsPowerShell\Modules\Snippets\ А в самом низу файла-профиля. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru