На форме лежит компонент TDBDateTimeEditEh, у которого есть такая особенность: дату можно увеличивать и уменьшать на единичку стрелками вверх-вниз, но TDBDateTimeEditEh.Value изменится только после того, как будет выполнено обновление (например, при переходе фокуса на другой компонент). А ещё на форме есть TBitBtn, у которого свойство Default равно true. И вот тут получается маленькая неприятность...
Допустим, мы стрелками сменили значение даты, но не перешли ни на какой другой компонент, а нажали Enter. Соответственно, начал выполняться обработчик TBitBtn.OnClick, но TDBDateTimeEditEh.Value осталось старым, а не заменилось на значение, изменённое стрелками. Вот как бы мне при это аккуратно обновить значение даты на новое до того, как начнёт выполняться обработчик нажатия на кнопу? Что-то я совсем запутался в том, какие сообщения в какой последовательности будут в этом случае обрабатываться. Теоретический, всегда можно приделать костыль, типа, перехватывать нажатие клавиш для формы, и если нажат Enter, то выполнять TDBDateTimeEditEh.UpdateData, но хотелось бы как-то... э-э-э... поэстетичнее.
Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице. Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.
09-03-2025 07:57 | Сообщение от автора вопроса
Это всё здорово и, наверное, даже правильно. Но у меня речь шла немного о другом. Есть готовый скриптовый движок, который сейчас переделан на другой версии Delphi (и некоторых других библиотеках). И у него выявлена такая вот проблема. Добавлять в движок новый компонент и переделывать все скрипты никто не будет.
Пока не нашёл ничего лучше, чем вставить вот такой костыль
procedure TMyBaseForm.CMDialogKey(var Message: TCMDialogKey);
begin
if GetKeyState(VK_MENU) >= 0 then
with Message do
case CharCode of
VK_RETURN:
begin
if FActiveControl <> nil and (FActiveControl is TDBDateTimeEditEh) then
begin
TDBDateTimeEditEh(FActiveControl).UpdateData;
end;
Exit;
end;
end;
inherited;
end;
>>>Через DataSet не поучится: компонент может просто использоваться для указания даты, но не быть связанным ни с каким DataSet-ом.
Ну это уже заморочки EhLib.
Ведь специально отдельно сделали "обычные компоненты" и на их основе "data-aware компоненты".
Первые хранят данные, либо в классе VCL, либо в недрах ComCtl32.dll. Они предназначены для отображение данных. Что видим, то и храним. Поэтому и нет никаких промежуточных слоёв данных. А вторые - в "источниках данных". При изменении в них данных, вызывается целый каскад событий. Начиная от "обычного компонента", через DataLink, DataSource, DataSet к "источнику данных".
Простота первых - позволяет обращаться непосредственно к отображаемым данным. Более того, данные в них мгновенны. Т.е. это то что мы видим. Например, при нажатии клавиши или при получении строки TEdit.Text, в предке TControl, отправляется сообщение в недра ComCtl32.dll.
А со вторыми всё сложнее. Во-первых, они так же, как и первые, обращаются к отображаемым данным. Но поскольку акцент делается на "источнике данных", то доступ к "отображаемым данным" скрывают в недрах класса. Во-вторых, поскольку механизм передачи данных в "источник данных" бывает достаточно сложен и медлителен, то данные передаются порциями. Например, при завершении редактирования элемента управления (как частный случай потеря фокуса DbEdit). Поэтому поведение этих компонент отличается от "обычных". В-третьих, редактирование содержимого "data-aware компонента" приводит к изменению состояния "источника данных" (изменение, вставка и т.д.). Поэтому их, в отличии от "обычных компонент", нельзя рассматривать обособлено, а только как цепочку компонент (Edit->DbEdit->DataSoutse->...). И данные в них не мгновенны, а протяжённые во времени. Т.е. проходят несколько этапов (и при этом, ещё и хранятся в разных местах, для разных этапов).
Поэтому можно сказать:
- При внешней схожести, "обычные компоненты" и "data-aware компоненты" различны, и по предназначению, и по поведению, и по методике использования;
- Если не нужен "источник данных", то рекомендуется использовать "обычные компоненты";
- EhLib попыталась сделать компонент два в одном. Если подключён DataSource, то это "data-aware компонент", а если нет - "обычный компонент". И даже добавила свойство Value, которое работает, и в том, и в другом случае. Но его поведение, она оставила характерным для "data-aware компонентов". Поэтому, из-за этого, при использовании его как "обычный компонент" и свойства Value, возникает желание, либо доработать сам компонент (до поведения "обычного"), либо изменить стандартное поведение "data-aware компонентов" VCL.
Наверно, можно работать с "data-aware компонентом" как с "обычным". Вопрос только зачем?
Например, так можно получить или установить отображаемое текстовое значение TDBDateTimeEditEh или TDbEdit (до потери ими фокуса) с помощью аналогов методов TControl.GetText и TControl.SetText:
// Получить текст DBDateTimeEditEh1.Value: Memo1.Lines.Add(Control_GetText(DBDateTimeEditEh1))
// Получить текст DbEdit.Text: Memo1.Lines.Add(Control_GetText(DbEdit1))
function Control_GetText(aControl :TControl) :string;
function Control_GetTextLen: Integer;
begin
Result := aControl.Perform(WM_GETTEXTLENGTH, 0, 0);
end;
function Control_GetTextBuf(aBuffer: PChar; aBufSize: Integer): Integer;
begin
Result := aControl.Perform(WM_GETTEXT, aBufSize, Longint(aBuffer));
end;
var
wLen: Integer;
begin
wLen := Control_GetTextLen;
SetString(Result, PChar(nil), wLen);
if wLen <> 0
then Control_GetTextBuf(Pointer(Result), wLen + 1);
end;
06-03-2025 06:41 | Комментарий к предыдущим ответам
>>>Не совсем понимаю суть вопроса.
У вас есть "некий конструктор форм с ограниченным набором компонентов".
Очевидно, в этот "набор компонент" не ходит TDBDateTimeEditEh.
Но в этот "набор компонент" наверняка входит TDbEdit. TDbEdit и TDBDateTimeEditEh достаточно похожи и даже имеют общего предка TCustomMaskEdit. Да и механизм работы с данными у них схож (data-aware). Кроме того, и TDbEdit, и TDBDateTimeEditEh, в случае с BitBtn.Default, ведут себя одинаково (данные передаются после потели фокуса или по DataSet.Post).
Поскольку TDbEdit проще и входит в "набор компонент" "конструктора форм", я и предлагал на его основе проследить механизм работы, для указанного вами случая. А затем, по аналогии, применить его для TDBDateTimeEditEh.
Ведь насколько я понял, вы же хотите не переделывать "некий конструктор форм", а только использовать его возможности.
>>>несколько раз жмём стрелку вверх, увеличивая дату
Можно ещё, для этого, колёсико мышки покрутить.
Есть некая базовая форма — потомок TForm. Почти пустая, но с некоторым количеством дополнительных возможностей (в детали вдаваться не хочется). Есть текст DFM, который заполняет эту форму компонентами с заданными свойствами. Этот DFM формируется в специальном билдере и содержит только предопределённый набор компонент. Кроме того, имеется ещё компонент Скриптер, которые содержит текст скрипта (а-ля Delphi) и умеет его выполнять. Скрипт работает с компонентами, размещёнными на форме, то есть обращения к компонентам, их методам и свойствам внутри скрипта переводятся движком а обращения к компонентам на форме. Специальной функцией эта форма создаётся и запускается (модально или не модально)
Собственно, поэтому я и не могу что-либо добавить в обработчик TBitBtn.OnClick, потому что форма изначально пустая: на ней нет ни TBitBtn, ни метода OnClick. Всё это появится только после загрузки DFM и скрипта. хотелось бы сделать сразу на пустой форме, чтобы не пришлось что-то анализировать и добавлять после загрузки компонент.
Если же про саму проблему, то TDbEdit не подходит, так как не совсем подходит: он не позволяет вводить данные если не подключен к DataSet-у. А с TDBDateTimeEditEh это выглядит примерно так:
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
ShowMessage(DBDateTimeEditEh1.Value);
end;
Здесь BitBtn1.Default = true. Компонент DBDateTimeEditEh1 к данным не подключен, но значение менять можно. А ещё, если содержит фокус, то значение даты можно увеличивать и уменьшать на единицу стрелочками вверх-вниз, но свойство Value от это не изменяется, меняется только текст компонента. Например, запускаем эту форму, выставляем сегодняшнюю дату, а потом, не уводя фокус, несколько раз жмём стрелку вверх, увеличивая дату. А пото, опят-таки не уводя фокус, жмём Enter. Управление передаётся на обработчик события OnClick, но вызванное окно показывает не изменённую дату, так как фокус с DBDateTimeEditEh1 не ушёл, поэтому значение Value не обновилось.
03-03-2025 10:33 | Вопрос к автору: запрос дополнительной информации
>>>...не поучится. В данном случае работает некий конструктор форм с ограниченным набором компонентов ...
Допустим, что вместо TDBDateTimeEditEh используется простой TDbEdit. Как производится его (DbEdit1) обработка в аналогичном случае?
Через DataSet не поучится: компонент может просто использоваться для указания даты, но не быть связанным ни с каким DataSet-ом.
TBitBtn.obClick тоже не совсем подходит. Поясняю... В данном случае работает некий конструктор форм с ограниченным набором компонентов и скриптовой обработкой. Я же занимаюсь системным программированием данного конструктора. Событие OnClick задействовано для прикладного уровня.
Вертится мысль подвесить обработчик на что-нибудь вроде CM_DIALOGKEY, где и обрабатывать VK_RETURN, но хотелось бы света на педмет того, какое сообщение лучше всего использовать (я в них всегда плавал), и как лучше заставить компонент актуализироваться (TDBDateTimeEditEh.UpdateData или что-то более универсальное, вроде TForm(FActiveControl.Parent).SelectNext
>>>Вот как бы мне при этом аккуратно обновить значение даты на новое ...
Другой способ, в обработчике BitBtn.obClick, перед проверкой значения Value, можно переключить фокус на кнопку со свойством Default:
>>>Компонент TDBDateTimeEditEh, у которого есть такая особенность: дату можно увеличивать и уменьшать на единичку стрелками вверх-вниз, но TDBDateTimeEditEh.Value изменится только после того, как будет выполнено обновление (например, при переходе фокуса на другой компонент).
Изменение значения только после потери фокуса, это стандартное поведение data-aware компонентов.
>>>Допустим, мы стрелками сменили значение даты, но не перешли ни на какой другой компонент, а нажали Enter. ... Вот как бы мне при этом аккуратно обновить значение даты на новое ...
Можно, перед проверкой значения, сохранить данные связанного DataSet'а. Например, в обработчике BitBtn.obClick, перед проверкой значения Value, вставьте:
with MyDataSet do
if Active and (State in [dsEdit, dsInsert])
then Post; //или диалог с подтверждением Post, Cancel или Abort
Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Функция может не работать в некоторых версиях броузеров.