這取決於您希望程序對用戶的友好程度。
問題中顯示的方法就足夠了,但它可能會讓用戶想知道爲什麼按鈕顯示爲禁用。如果它啓用了按鈕,您的程序可能會更有幫助,但在點擊時會改變它的行爲。它可以通知用戶前一個程序仍在運行,而不是啓動該程序的另一個副本,甚至可能會將焦點設置到該程序。
問題標題詢問如何禁用表格上的控件全部。 (表單是否爲模態無關緊要;形式涉及禁用父表格表單,而不是表單模式本身。)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
屬性的進一步修改不會影響此表單上的控件。
操作可以與多個控件關聯,並且控件可以駐留在多個表單上。由於動作正在更新,而不是直接控制,所以沒有真正的方法可以使用動作來僅在一個表單上禁用控件。
現在我們就來事的是否形式上禁用所有的控制真的到動機這個問題,這個問題的解決方案。問題是關於目標和提出的解決方案有點分散。建議的解決方案是強制性的(禁用表單上的所有內容),以便實際只需要防止調用單個命令。而不應該被調用的命令與該形式無關;無論有多少控件與任何形式的動作相關聯,無都應調用該命令。因此,我們應該禁用動作,並隱式禁用與其關聯的任何控件,否則我們應該修改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;
該功能在隱式返回的接口引用,編譯器店聲明的臨時變量在代碼結束時,該變量被銷燬,並且存儲的接口對象被釋放,返回該標誌到其先前的值。
然後嘗試使用塞爾塔克在他的建議(現在刪除)註釋,使用'TForm.Enabled'屬性。 – TLama
@TLama - 在我評論過之後,我做了一個快速測試,發現禁用控件/表單完全沒有幫助。 –