Как я могу отключить все компоненты в немодальной форме?

Сценарий:

  • TActionManager, TAction и TButton (связанные с этим действием)
  • ActionManager постоянно включает действие в своем обработчике события OnUpdate
  • код в обработчике событий действия запускает внешнюю программу с помощью метода ShellExecAndWait (с использованием JCL Code Library Jedi)
  • требование: приложение не должно позволять запускать приложение дважды, быстро нажимая кнопку в другой раз

Проблема:

  • ShellExecAndWait не блокирует цикл сообщений приложения, поэтому пользователь может щелкнуть, пока внешнее приложение еще открыто
  • если метод обработчика действия отключит действие перед вызовом ShellExecAndWait, метод обновления немедленно повторно включит его

Чтобы я мог написать так

procedure TMyForm.OnMyAction(Sender: TObject);
begin
  try  
    // notify Action Manager that the Action is temporarily disabled
    SomeGlobalFlag := True;

    // disable the action 
    (Sender as TAction).Enabled := False;

    // do the call
    ShellExecAndWait( ... );

  finally

    // enable the action 
    (Sender as TAction).Enabled := True;

    // allow ActionManager to control the action again
    SomeGlobalFlag := False; 

  end;
end;

Есть ли более простой способ? Как гласит заголовок этого вопроса - могу ли я заблокировать ввод для выполнения внешнего приложения?

4 ответа

Это зависит от того, насколько дружелюбно вы хотите, чтобы ваша программа была для ее пользователей.

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

В заголовке вопроса спрашивается, как отключить все элементы управления в форме. (Является ли форма модальной, не имеет значения; модальность имеет дело с отключением родительской формы (форм), а не самой модальной формы.) Ответ Mjn делает это путем приостановки списка действий. Это не отключит элементы управления, которые не связаны с действиями, и не отключит элементы управления, связанные с другим списком действий. Он также может отключить элементы управления в других формах, связанных с тем же списком действий.

Ответ Марка неявно отключает все элементы управления, но, вероятно, запутает пользователя, поскольку ни один из элементов управления не будет выглядеть отключенным. Это похоже на идею, которую Sertac явно упомянул в комментарии, чтобы отключить всю форму.

Чтобы отключить все элементы управления в форме и отключить их, вы можете использовать рекурсивную функцию, например:

procedure EnableControls(Parent: TWinControl; Enabled: Boolean);
var
  i: Integer;
  Ctl: TControl;
begin
  for i := 0 to Pred(Parent.ControlCount) do begin
    Ctl := Parent.Controls[i];
    Ctl.Enabled := Enabled;
    if Ctl is TWinControl then
      EnableControls(TWinControl(Ctl), Enabled);
  end;
end;

Используйте это так:

procedure TMyForm.OnMyAction(Sender: TObject);
begin
  EnableControls(Self, False);
  try
    ShellExecAndWait(...);
  finally
    EnableControls(Self, True);
  end;
end;

Поскольку мы напрямую модифицируем Enabled свойства элементов управления, эти свойства будут отделены от Enabled свойства любых связанных действий. Это решает насущную необходимость, но имеет нежелательный побочный эффект, который приводит к дальнейшим изменениям действия Enabled свойство не повлияет на элементы управления в этой форме.

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


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

Решение, показанное в вопросе, - это способ отключить действие. Установите флаг, чтобы указать, что действие выполняется, и очистите его, когда выполнение завершится. Проверьте этот флаг в OnUpdate обработчик события. Нет необходимости вручную отключать действие в OnExecute обработчик, хотя; действия уже обновляются в их Execute методы. Итак, у нас есть этот код:

var
  ActionIsExecuting: Boolean = False;

procedure TMyForm.OnMyAction(Sender: TObject);
begin
  // notify Action Manager that the Action is temporarily disabled
  ActionIsExecuting := True;
  try  
    // do the call
    ShellExecAndWait( ... );
  finally
    // allow ActionManager to control the action again
    ActionIsExecuting := False; 
  end;
end;

procedure TSomeModule.ActionUpdate(Sender: TObject);
begin
  (Sender as TAction).Enabled := not ActionIsExecuting and ...
end;

Для этого требуется совместная работа нескольких разделов кода относительно того, как обрабатывать выполнение этого действия. Код обновления действия должен знать, что действие должно быть в состоянии временно отключить себя.

Для более автономного решения мы можем оставить OnUpdate событие в одиночку и просто держать действие включенным все время. Вместо этого мы будем отслеживать повторный вход локально и уведомлять пользователя:

procedure TMyForm.OnMyAction(Sender: TObject);
{$J+} // a.k.a. $WRITABLECONST ON
const
  ActionIsExecuting: Boolean = False;
{$J-}
begin
  if ActionIsExecuting then begin
    ShowMessage('The program is still running. Please wait.');
    exit;
  end;

  ActionIsExecuting := True;
  try
    ShellExecAndWait(...);
  finally
    ActionIsExecuting := False;
  end;
end;

Ответ Mjn называет пять строк кода для установки состояния и управления блоком try-finally "слишком много шаблонного". Вы можете уменьшить его до одной строки с помощью интерфейсного объекта и вспомогательной функции:

type
  TTemporaryFlag = class(TInterfacedObject)
  private
    FFlag: PBoolean;
  public
    constructor Create(Flag: PBoolean);
    destructor Destroy; override;
  end;

function TemporaryFlag(Flag: PBoolean): IUnknown;
begin
  Result := TTemporaryFlag.Create(FFlag);
end;

constructor TTemporaryFlag.Create;
begin
  inherited;
  FFlag := Flag;
  FFlag^ := True;
end;

destructor TTemporaryFlag.Destroy;
begin
  FFlag^ := False;
  inherited;
end;

Используйте это так:

begin
  TemporaryFlag(@ActionIsExecuting);
  ShellExecAndWait(...);
end;

Функция возвращает ссылку на интерфейс, которую компилятор хранит в неявно объявленной временной переменной. В конце кода эта переменная уничтожается, и сохраненный интерфейсный объект освобождается, возвращая флагу его предыдущее значение.

Это решение зависит от фактического компонента Action или ActionManager. Все еще слишком много "стандартного" кода. Также очень хрупкий, поскольку предполагает, что Отправитель является экземпляром TAction.

procedure TMyForm.OnMyAction(Sender: TObject);
begin
  try  
    // disable all actions 
    (Sender as TAction).ActionList.State := asSuspended; 


    // do the call
    ShellExecAndWait( ... );

  finally

    // enable all actions 
    (Sender as TAction).ActionList.State := asNormal; 

  end;
end;

Вы можете разместить все элементы пользовательского интерфейса на панели и отключить панель. Ваше приложение будет по-прежнему реагировать на ходы, изменения размера, перерисовки и т. Д.

Реализация EnableControls страдает от чего-то странного:

  • Не все компоненты формы включены одновременно

Так что отключение всех (без сохранения предыдущего состояния) - большая ошибка.

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

Хуже всего то, что этот способ также не совсем корректен, так как некоторый код может изменить состояние "Включено" некоторых компонентов, и при восстановлении этого компонента состояние "Включено" не должно быть восстановлено... поэтому простой способ отключения / повторного включения невозможен... не говоря уже о том, зависит ли код от состояния "Включено" некоторых компонентов.

Я не помню сейчас, как я делал в прошлом... но есть одно предложение, которое блокирует форму от мыши и клавиатуры (игнорировать их), и другое, чтобы разблокировать его... это то, что я пытался найти когда я пришел сюда из гугла... может это связано с (TMouseActivate), но я помню, что это также блокировка клавиатуры, так что, возможно, этого не достаточно:

procedure TMyForm.FormMouseActivate(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y, HitTest: Integer; var MouseActivate: TMouseActivate);
begin
     if YourCondition
     then beign
               MouseActivate:=maNoActivateAndEat;
          end;
end;

Я помню, я не использовал следующее, но это также может быть использовано:

procedure BlockInput(ABlockInput:Boolean);stdcall;external 'USER32.DLL';
...
procedure TMyForm.MyEvent(Sender:TObject);
begin
     BlockInput(True);
     // Your code, long loop, etc
     BlockInput(False);
end;
Другие вопросы по тегам