Создание динамической формы Delphi — обеспечение правильной обработки сообщений мыши

У меня есть макет приложения, основанный на дереве слева и панели справа. На панели размещается другой класс TForm в зависимости от выбранного узла дерева (своего рода «проводник форм»). Одновременно отображается только одна форма, которая предоставляет базовые данные, хранящиеся в другом месте, и экземпляр формы создается и уничтожается при каждом щелчке нового узла дерева.

Все это работает нормально, за исключением следующего сценария. Нажмите кнопку в форме, которая запускает действие, которое занимает секунду или около того. Во время этого действия может быть вызов Application.ProcessMessages. Теперь, непосредственно перед тем, как это действие действительно завершится, Пользователь щелкает новый узел дерева. Это сообщение wmMousedown обрабатывается, вызывая немедленное освобождение формы. Затем код действия возвращается к коду формы, чтобы обнаружить, что self изменился, и вызывает AV.

Мой вопрос: есть ли способ узнать, что все сообщения формы были обработаны и завершены, прежде чем я позволю освободить форму? Модальные формы, кажется, делают это, когда нажимается кнопка закрытия, потому что они приостанавливаются перед закрытием, если заняты...

Спасибо Брайан


person Brian Frost    schedule 09.09.2009    source источник


Ответы (5)


Чтобы ответить на фактический вопрос в последнем абзаце... :)

Если вы вызываете Release в форме, это отправляет сообщение в форму, что приводит к ее освобождению, когда она получает это сообщение.

Поскольку сообщение отправлено в очередь сообщений, оно будет доставлено только после того, как будут обработаны любые/все другие текущие сообщения для этой формы, аккуратно выполняя именно то, что вы просите. Я думаю.

person Deltics    schedule 09.09.2009

Избегайте использования Application.ProcessMessages любой ценой!

Наиболее распространенное ненадлежащее использование, которое я вижу для этого, - разрешить перерисовку графического интерфейса, например. после обновления заголовка ярлыка. Самый безопасный способ обеспечить обновление графического интерфейса — явно перерисовать все затронутые элементы управления с помощью метода Update (который обходит сообщения об отрисовке и заставляет элемент управления перерисовываться напрямую). Или это может быть метод Обновить, а может быть и то, и другое! К сожалению, я никогда не могу вспомнить навскидку!

Application.ProcessMessages вызывает проблемы с "повторным входом", как вы обнаружили, эффективно создавая потенциально новые, недолговечные, "основные циклы сообщений" в вашем коде, которые могут привести к трудностям в диагностике и воспроизведении. проблемы.

Я бы проверил использование Application.ProcessMessages в этом случае и посмотрел, нельзя ли разработать альтернативный подход, чтобы исключить его использование из вашего кода, вместо того, чтобы пытаться исправить множество других< /strong> просто для того, чтобы он справлялся с проблемами, которые вызывает Application.ProcessMessages.

ПРИМЕЧАНИЕ. Единственным исключением из этого правила является то, что использование Application.ProcessMessages для поддержания отклика кнопки «Отмена» в сообщении/диалоговом окне «Ход выполнения» относительно безопасно, если это сообщение/диалоговое окно хода выполнения является модальным, и остальная часть вашего приложения фактически отключена, пока отображается это диалоговое окно, поэтому только эта кнопка "Отмена" могла возможно реагировать на любые сообщения

person Deltics    schedule 09.09.2009

Реализовать свойство IsBusy в каждом классе размещенной формы (использовать наследование - реализовать это свойство в родительской форме). Перед удалением формы из панели хостинга (неважно, вызовом Free или просто перемещением из панели) проверьте, не верно ли IsBusy. Если ваша размещенная форма занята, подождите, пока она не закончится, а затем удалите ее. Вы даже можете добавить какой-то способ уведомить размещенную форму о том, что она должна прервать свою длительную задачу.

Это не только поможет решить вашу текущую проблему, но и позволит вам очистить некоторую бизнес-логику внутри ваших форм.

Таким образом, код изменения формы в вашем TreeView должен содержать следующий код:

    {FCurrentForm is a reference to currently placed form on panel}
    if (FCurrentForm.IsBusy) do
    begin
      {remember some information that will be used to create new form}
      FNewFormToBeAdded := ... 
    end
    else
    begin
      FreeCurrentForm();
      PlaceNewFormOnPanel();
    end;

Поэтому у вас должна быть такая рутина, как:

   procedure THostForm.NotifyMeAboutTaskFinished;
   begin
     if FNewFormToBeAdded <> 0 than 
     begin
       FreeCurrentForm();
       PlaceNewFormOnPanel();
     end; 
   end;

И в вашей форме HOSTED вы можете иметь

procedure TSomeHostedForm.btnDoLongTaskClick(Sender : TObject);
begin
  IsBusy := true;
  try
    {... do some tikme taking task ...}
  finally
    IsBusy :=false;
    NotifyHostingFormIAMNotBusyAnymore();
  end;
end;
person smok1    schedule 09.09.2009
comment
Ваш пример кода не имеет особого смысла. То код выполняется в методе IsBusy(), то метод имеет неправильное имя, и Sleep() нужно удалить. Или код выполняется в другом методе, тогда он вообще не вызывается. - person mghie; 09.09.2009
comment
Я думаю об использовании этого кода в событии TreeView.OnClick (или OnMouseDown). Когда пользователь щелкает новый узел в TreeVew, мы должны проверить, можем ли мы вызвать Free в FCurrentForm. Если FCurrentForm действительно занят, мы должны подождать, пока FCurrentForm закончит свою работу, затем освободить FCurrentForm, создать новую форму (соответствующую выбору) и установить FCurrentForm так, чтобы она указывала на только что созданную форму. Поэтому я думаю просто НЕ менять текущую форму, если текущая форма занята. - person smok1; 09.09.2009
comment
Где в вашем коде FCurrentForm собирается закончить свою работу? - person mghie; 09.09.2009
comment
Вы должны изменить свои формы (каждую форму), чтобы поддерживать это свойство. Итак, представьте, что у вас есть размещенная форма, которая делает что-то в событии btnDoLongTaskClick. Когда вы запускаете эту процедуру события, вы устанавливаете IsBusy этой формы в значение true, а при завершении этой процедуры вы устанавливаете IsBusy в значение false (хорошо, чтобы это свойство было установлено в конце). Надеюсь, это понятно - если нет, я опубликую более длинный фрагмент кода. - person smok1; 09.09.2009
comment
Хорошо, я попытаюсь объяснить лучше: если FCurrentForm.IsBusy только проверяет, занята ли форма, но не выполняет никакой реальной работы, и вы используете Sleep(500) в цикле - как форма вообще может стать не занятой? Длительная задача не будет выполняться, занятость остается True — вы создали бесконечный цикл. См. мой первый комментарий: Либо IsBusy() может сбросить состояние занятости, либо спать неправильно, и имя вводит в заблуждение. Или IsBusy() только проверяет, тогда бесконечный цикл. - person mghie; 09.09.2009
comment
@mghie - да, это может работать в текущем виде, только если длительная задача выполняется в другом потоке. Я обновлю это. - person smok1; 09.09.2009
comment
Я так и думал. Однако, если длительная задача находится в другом потоке, больше нет необходимости вызывать Application.ProcessMessages вообще, поэтому это не очень актуально в контексте этого вопроса. Если вы используете несколько потоков, нет необходимости опрашивать до тех пор, пока форма не перестанет быть занятой, есть лучшие способы уведомить поток GUI из рабочего потока. - person mghie; 09.09.2009

Ваш TreeView.OnClick может вызывать метод CloseQuery текущей активной формы. Если CloseQuery возвращает false, не меняйте формы местами. Затем вы можете обрабатывать стандартный CloseQuery для форм, которые в нем нуждаются.

В формах, показанных на панели, вам нужно будет отслеживать некоторое состояние, чтобы знать, действительно ли вы можете закрыть или нет. Мои длительные процессы также проверяют условие остановки. Обычно у меня также есть кнопка отмены, но любой вызов CloseQuery также приведет к прерыванию длительного процесса. Мой CloseQuery обычно выглядит так:

procedure TBatchPoster.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin inherited;

  if FProcessRunning = true then
  begin
    FStop := true;
    CanClose := false;
  end;
end;

Обычно, если пользователь щелкает один раз и ничего не меняется, к тому времени, когда он пытается щелкнуть второй раз, длительный процесс останавливается, и второй щелчок успешно меняет форму.

На самом деле это то же самое, что и ответ smok1, но поскольку вы уже используете форму, вам не нужно добавлять новое свойство.

person Mark Elder    schedule 09.09.2009

Хотя я согласен с обоими ответами «Дельтики», есть еще один вариант — в зависимости от того, нужно ли вам освобождать форму.

В событии формы FormClose установите действие на caHide. Это скроет, а не уничтожит форму. Вам нужно будет отслеживать, какие формы вы назначили (вероятно, используя указатель «Данные» в TTreeNode).

person Gerry Coll    schedule 09.09.2009
comment
Не очень хорошее решение, потому что элементы управления и таймеры формы все еще работают. Моя архитектура заключается в использовании форм только для доступа пользователей к базовым данным. Где преобразовываются формы и элементы управления Если вы не видите этого, его там нет. - person Brian Frost; 11.09.2009