情景:
问题:
所以我可以写这样的
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;
有更简单的方法吗?正如这个问题的标题所说 - 我可以阻止执行外部应用程序的输入吗?
答案 0 :(得分:6)
这取决于您希望程序对用户的友好程度。
问题中显示的方法就足够了,但它可能让用户想知道为什么按钮显示为禁用。如果启用按钮,您的程序可能会更有帮助,但在单击时会更改其行为。它可以通知用户以前的程序仍在运行,甚至可以将焦点设置为该程序,而不是启动该程序的另一个副本。
问题标题询问如何禁用所有表单上的控件。 (表单是否与模态无关;模态处理禁用父表单,而不是模态表单本身。)Mjn's answer通过暂停操作列表来实现。这不会禁用与操作无关的控件,也不会禁用与其他操作列表关联的控件。它还可以禁用与同一操作列表关联的其他表单上的控件。
Marck's answer隐式禁用所有控件,但可能会混淆用户,因为没有任何控件看起来已禁用。这似乎与Sertac apparently mentioned in a comment的想法类似,以禁用整个表单。
要禁用表单上的所有控件,并使它们显示为禁用,您可以使用这样的递归函数:
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
属性的进一步修改不会影响此表单上的控件。
操作可以与多个控件关联,控件可以驻留在多个窗体上。由于正在更新的操作,而不是直接控件,因此实际上没有办法使用操作来禁用一个表单上的控件。
现在我们来讨论在表单上禁用所有 控件是否真的是解决问题的正确解决方案题。对于目标和提出的解决方案,问题有点散乱。对于真正只需要阻止调用单个命令的东西,所提出的解决方案是严厉的(禁用表单上的所有内容)。不应该调用的命令与表单无关;无论有多少控件与任意数量的表单上的操作相关联, none 都应该调用该命令。因此,我们应该禁用操作,并隐式禁用与之关联的任何控件,或者我们应该修改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;
该函数返回一个接口引用,编译器将其存储在一个隐式声明的临时变量中。在代码的最后,该变量被销毁,存储的interfaced对象被释放,并将该标志返回到其先前的值。
答案 1 :(得分:2)
此解决方案取决于实际的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;
答案 2 :(得分:0)
您可以将所有ui元素放在面板上,并禁用面板。您的应用程序仍将响应移动,调整大小,重新绘制等。
答案 3 :(得分:0)
EnableControls
的实现遭遇了一些奇怪的事情:
所以禁用所有(不保存以前的状态)是一个很大的失误。
要正确执行此操作,您需要创建一个组件列表并存储指向该组件和先前启用状态的指针,因此在还原时您可以恢复以前的状态。
最糟糕的是,这种方式也不完全正确,因为某些代码可能会更改某些组件的Enabled状态,并且在恢复该组件时,不能恢复Enabled状态...所以没有简单的方法来禁用/重新启用can完成...更不用说代码是否依赖于某些组件的启用状态。
我现在不记得我过去做过什么......但是有一句话阻止鼠标和键盘的形式(忽略它们)而另一句阻止它......那就是我的意思试图找到我从谷歌来到这里的时间......可能与(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;