2012-06-01 44 views
0

場景:如何禁用非模態窗體中的所有組件?

  • 一個TActionManager,一個TAction,和一個TButton(與此動作相關)
  • ActionManager不斷使得能夠在其的OnUpdate事件處理程序的動作
  • 在動作事件處理程序代碼啓動一個使用ShellExecAndWait方法(使用絕地武士代碼庫JCL)
  • 要求外部程序:應用程序不應允許啓動應用程序兩次通過單擊按鈕快速另一個時間

問題:

  • 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; 

有沒有更簡單的方法?正如這個問題的標題所說 - 我可以阻止執行外部應用程序的輸入嗎?

回答

2

該解決方案取決於實際的Action或ActionManager組件。仍然太多的「樣板」代碼。同樣非常脆弱,因爲它假定發件人是一個TA實例。

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; 
+1

然後嘗試使用塞爾塔克在他的建議(現在刪除)註釋,使用'TForm.Enabled'屬性。 – TLama

+1

@TLama - 在我評論過之後,我做了一個快速測試,發現禁用控件/表單完全沒有幫助。 –

0

您可以將所有UI元素放置在面板上,並禁用該面板。您的應用程序仍然會響應移動,調整大小,重新繪製等。

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屬性的進一步修改不會影響此表單上的控件。

操作可以與多個控件關聯,並且控件可以駐留在多個表單上。由於動作正在更新,而不是直接控制,所以沒有真正的方法可以使用動作來僅在一個表單上禁用控件。


現在我們就來事的是否形式上禁用所有控制真的到動機這個問題,這個問題的解決方案。問題是關於目標和提出的解決方案有點分散。建議的解決方案是強制性的(禁用表單上的所有內容),以便實際只需要防止調用單個命令。而不應該被調用的命令與該形式無關;無論有多少控件與任何形式的動作相關聯,都應調用該命令。因此,我們應該禁用動作,並隱式禁用與其關聯的任何控件,否則我們應該修改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; 

該功能在隱式返回的接口引用,編譯器店聲明的臨時變量在代碼結束時,該變量被銷燬,並且存儲的接口對象被釋放,返回該標誌到其先前的值。

+0

我刪除了我的評論,因爲禁用表單或與該操作關聯的控件不會阻止該操作通過其快捷方式執行。 –

+1

你說得對,@塞爾塔克。該問題詢問關於禁用組件,但我真的只談到禁用控件。行動是組成部分,我的答案的主要部分(分隔線上方)忽略了這一點。但是,對於我的答案的第二部分來說,這種區分並不是必須的,因爲這個問題可能並不意味着無論如何都禁用所有組件。 –

0

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; 
相關問題