Managing Multiple GUIs: Difference between revisions

From AutoIt Wiki
Jump to navigation Jump to search
No edit summary
 
(OnEvent Mode: Case $GUI_EVENT_NONE ; it will not be necessary as we already check: If Not IsHWnd($aMsg[1]) Then)
 
(15 intermediate revisions by 4 users not shown)
Line 1: Line 1:
Having several GUIs on the screen at the same time is fairly common - but structuring your code to deal with this can seem quite daunting. However, as I hope this tutorial will demonstrate, it is nowhere near as difficult as it first appears.   
=Introduction=
Having several GUIs on the screen at the same time is fairly common but structuring your code to deal with this can seem quite daunting. However, as I hope this tutorial will demonstrate, it is nowhere near as difficult as it first appears.   


[[MessageLoop Mode]]
=MessageLoop Mode=


Let us start with ''MessageLoop'' mode as this is where most new coders run into difficulties with multiple GUIS.  This example script illustrates the problem - it exits when the '''[X]''' is clicked on either GUI:
Let us start with ''MessageLoop'' mode as this is where most new coders run into difficulties with multiple GUIs.  This example script illustrates the problem - it exits when the '''[X]''' is clicked on either GUI:
-----------------------------------
<syntaxhighlight lang="autoit">
<syntaxhighlight lang="autoit">
#include <GUIConstantsEx.au3>
#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
Global $hButton3 = 9999
 
Global $g_idButton3
gui1()
 
gui1()
Func gui1()
 
Func gui1()
    $hGUI1 = GUICreate("Gui 1", 200, 200, 100, 100)
Local $hGUI1 = GUICreate("GUI 1", 200, 200, 100, 100)
    $hButton1 = GUICtrlCreateButton("Msgbox 1", 10, 10, 80, 30)
Local $idButton1 = GUICtrlCreateButton("Msgbox 1", 10, 10, 80, 30)
    $hButton2 = GUICtrlCreateButton("Show Gui 2", 10, 60, 80, 30)
Local $idButton2 = GUICtrlCreateButton("Show GUI 2", 10, 60, 80, 30)
    GUISetState()
GUISetState(@SW_SHOW, $hGUI1)
 
    While 1
While 1
        Switch GUIGetMsg()
Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
Case $GUI_EVENT_NONE
                ExitLoop
ContinueLoop
            Case $hButton1
Case $GUI_EVENT_CLOSE
                MsgBox("", "MsgBox 1", "Test from Gui 1")
ExitLoop
            Case $hButton2
Case $idButton1
                GUICtrlSetState($hButton2, $GUI_DISABLE)
MsgBox($MB_OK, "MsgBox 1", "Test from GUI 1")
                gui2()
Case $idButton2
            Case $hButton3
GUICtrlSetState($idButton2, $GUI_DISABLE)
                MsgBox("", "MsgBox 2", "Test from Gui 2")  
gui2()
        EndSwitch
Case $g_idButton3
    WEnd
MsgBox($MB_OK, "MsgBox 2", "Test from GUI 2")
EndSwitch
EndFunc  ;==>gui1
WEnd
EndFunc  ;==>gui1
Func gui2()
 
Func gui2()
    $hGUI2 = GUICreate("Gui 2", 200, 200, 350, 350)
Local $hGUI2 = GUICreate("GUI 2", 200, 200, 350, 350)
    $hButton3 = GUICtrlCreateButton("MsgBox 2", 10, 10, 80, 30)
$g_idButton3 = GUICtrlCreateButton("MsgBox 2", 10, 10, 80, 30)
    GUISetState()
GUISetState(@SW_SHOW, $hGUI2)
EndFunc  ;==>gui2
EndFunc  ;==>gui2
</syntaxhighlight>
</syntaxhighlight>
-----------------------------------
 
The script exits because it has a single ''GUIGetMsg'' loop and the ''$GUI_EVENT_CLOSE'' message is received when either '''[X]''' is clicked - we have no way of telling the messages from the 2 GUIs apart.
The script exits because it has a single ''GUIGetMsg'' loop and the ''$GUI_EVENT_CLOSE'' message is received when either '''[X]''' is clicked - we have no way of telling the messages from the two GUIs apart.


The simplest solution is to disable the first GUI while the second is displayed:
The simplest solution is to disable the first GUI while the second is displayed:
-----------------------------------
<syntaxhighlight lang="autoit">
<syntaxhighlight lang="autoit">
#include <GUIConstantsEx.au3>
#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
gui1()
 
gui1()
Func gui1()
 
Func gui1()
    $hGUI1 = GUICreate("Gui 1", 200, 200, 100, 100)
Local $hGUI1 = GUICreate("GUI 1", 200, 200, 100, 100)
    $hButton1 = GUICtrlCreateButton("Msgbox 1", 10, 10, 80, 30)
Local $idButton1 = GUICtrlCreateButton("Msgbox 1", 10, 10, 80, 30)
    $hButton2 = GUICtrlCreateButton("Show Gui 2", 10, 60, 80, 30)
Local $idButton2 = GUICtrlCreateButton("Show GUI 2", 10, 60, 80, 30)
    GUISetState()
GUISetState()
 
    While 1
While 1
        Switch GUIGetMsg()
Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
Case $GUI_EVENT_NONE
                ExitLoop
ContinueLoop
            Case $hButton1
Case $GUI_EVENT_CLOSE
                MsgBox("", "MsgBox 1", "Test from Gui 1")
ExitLoop
            Case $hButton2
Case $idButton1
                ; Disable the first GUI
MsgBox($MB_OK, "MsgBox 1", "Test from GUI 1")
                GUISetState(@SW_DISABLE, $hGUI1)
Case $idButton2
                gui2()
; Disable the first GUI
                ; Re-enable the first GUI
GUISetState(@SW_DISABLE, $hGUI1)
                GUISetState(@SW_ENABLE, $hGUI1)
gui2()
        EndSwitch
; Re-enable the first GUI
    WEnd
GUISetState(@SW_ENABLE, $hGUI1)
EndSwitch
EndFunc  ;==>gui1
WEnd
EndFunc  ;==>gui1
Func gui2()
 
Func gui2()
    $hGUI2 = GUICreate("Gui 2", 200, 200, 350, 350)
Local $hGUI2 = GUICreate("GUI 2", 200, 200, 350, 350)
    $hButton3 = GUICtrlCreateButton("MsgBox 2", 10, 10, 80, 30)
Local $idButton3 = GUICtrlCreateButton("MsgBox 2", 10, 10, 80, 30)
    GUISetState()
GUISetState()
 
    While 1
While 1
        ; We can only get messages from the second GUI
; We can only get messages from the second GUI
        Switch GUIGetMsg()
Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
Case $GUI_EVENT_NONE
                GUIDelete($hGUI2)
ContinueLoop
                ExitLoop
Case $GUI_EVENT_CLOSE
            Case $hButton3
GUIDelete($hGUI2)
                MsgBox("", "MsgBox 2", "Test from Gui 2")
ExitLoop
EndSwitch
Case $idButton3
WEnd
MsgBox($MB_OK, "MsgBox 2", "Test from GUI 2")
EndSwitch
EndFunc  ;==>gui2
WEnd
 
EndFunc  ;==>gui2
</syntaxhighlight>
</syntaxhighlight>
-----------------------------------
 
This may well be all you need, but it does mean that we cannot action any of the controls on the first GUI until we close the second.  And importantly we remain blocked in the ''While...WEnd'' loop within the ''gui2'' function - go and read the "''Interrupting a running function''" tutorial to see why this is less than ideal.
This may well be all you need, but it does mean that we cannot action any of the controls on the first GUI until we close the second.  And importantly we remain blocked in the ''While...WEnd'' loop within the ''gui2'' function - go and read the [[Interrupting a running function]] tutorial to see why this is less than ideal.


So how can we deal with multiple GUIs visible at the same time?  Fortunately AutoIt offers us a simple way to differentiate between GUIs in ''MessageLoop'' mode.  Normally we use code like this in our idle loop to detect the messages sent by our GUI and its controls:
So how can we deal with multiple GUIs visible at the same time?  Fortunately AutoIt offers us a simple way to differentiate between GUIs in ''MessageLoop'' mode.  Normally we use code like this in our idle loop to detect the messages sent by our GUI and its controls:
-----------------------------------
<syntaxhighlight lang="autoit">
<syntaxhighlight lang="autoit">
Switch GUIGetMsg()
Switch GUIGetMsg()
    Case $GUI_EVENT_CLOSE
Case $GUI_EVENT_NONE
        ; Code
ContinueLoop
    Case $hButton1
Case $GUI_EVENT_CLOSE
        ; Code
; Code
EndSwitch
Case $idButton1
</syntaxhighlight>
; Code
-----------------------------------
EndSwitch
But when dealing with multiple GUIs, we need to use the "''advanced''" parameter when we call ''GUIGetMsg''.  As explained in the Help file, the function then returns an array instead of a single value.  This array includes information on what exactly triggered the message, just what we need to distinguish the message that was sent (element['''0'''] of the array) and which GUI sent it (element['''1''']).  We can then amend our simple Switch statement above to read like this:
</syntaxhighlight>  
-----------------------------------
 
But when dealing with multiple GUIs, we need to use the "''advanced''" parameter when we call ''GUIGetMsg''.  As explained in the Help file, the function then returns an array instead of a single value.  This array includes information on what exactly triggered the message, just what we need to distinguish the message that was sent (element['''0'''] of the array) and which GUI sent it (element['''1''']).  We can then amend our simple Switch statement above to read like this:  
<syntaxhighlight lang="autoit">
<syntaxhighlight lang="autoit">
$aMsg = GUIGetMsg(1) ; Use advanced parameter to get an array returned
$aMsg = GUIGetMsg(1) ; Use advanced parameter to get an array returned
Switch $aMsg[1] ; First check which GUI sent the message
If Not IsHWnd($aMsg[1]) Then ContinueLoop ; preventing subsequent lines from processing when nothing happens
    Case $hGUI1
 
        Switch $aMsg[0] ; Now check for the messages sent from $hGUI1
Switch $aMsg[1] ; First check which GUI sent the message
            Case $GUI_EVENT_CLOSE  
Case $hGUI1
                ; Code
Switch $aMsg[0] ; Now check for the messages sent from $hGUI1
            Case $hControl
Case $GUI_EVENT_NONE ; it will not be necessary as we already check: If Not IsHWnd($aMsg[1]) Then
                ; Code
ContinueLoop
        EndSwitch
Case $GUI_EVENT_CLOSE
    Case $hGUI2
; Code
        Switch $aMsg[0] ; Now check for the messages sent from $hGUI2
Case $hControl
            Case $GUI_EVENT_CLOSE
; Code
                ; Code
EndSwitch
            Case $hButton3
Case $hGUI2
                ; Code
Switch $aMsg[0] ; Now check for the messages sent from $hGUI2
        EndSwitch
Case $GUI_EVENT_NONE ; it will not be necessary as we already check: If Not IsHWnd($aMsg[1]) Then
EndSwitch
ContinueLoop
Case $GUI_EVENT_CLOSE
; Code
Case $idButton3
; Code
EndSwitch
EndSwitch
</syntaxhighlight>
</syntaxhighlight>
-----------------------------------
Although this looks complicated, if you take a moment to study it and you will quickly realise it is simply two ''Switch'' structures within an outer ''Switch''.  You have already dealt with a single ''Switch'' structure for a single GUI. All you are doing here is determining which ''Switch'' structure you want to use, and that depends on the GUI which sent the message which is why we need the outer ''Switch'' structure as a wrapper.
Although this looks complicated, if you take a moment to study it and you will quickly realise it is simply 2 ''Switch'' structures within an outer ''Switch''.  You have already dealt with a single ''Switch'' structure for a single GUI - all you are doing here is determining which ''Switch'' structure you want to use.  And that depends on the GUI which sent the message, which is why we need the outer ''Switch'' structure as a wrapper.


So here an example of how to manage 2 GUIs simultaneously using the "''advanced''" parameter with ''GUIGetMsg'':
So here is an example of how to manage two GUIs simultaneously using the "''advanced''" parameter with ''GUIGetMsg'':
-----------------------------------
<syntaxhighlight lang="autoit">
<syntaxhighlight lang="autoit">
#include <GUIConstantsEx.au3>
#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
Global $hGUI2 = 9999, $hButton3 = 9999 ; Predeclare the variables with dummy values to prevent firing the Case statements
 
Global $g_hGUI1, $g_idButton1, $g_idButton2, $g_hGUI2, $g_idButton3
gui1()
 
example()
Func gui1()
 
Func example()
    $hGUI1 = GUICreate("Gui 1", 200, 200, 100, 100)
gui1()
    $hButton1 = GUICtrlCreateButton("Msgbox 1", 10, 10, 80, 30)
Local $aMsg
    $hButton2 = GUICtrlCreateButton("Show Gui 2", 10, 60, 80, 30)
 
    GUISetState()
While 1
$aMsg = GUIGetMsg(1) ; Use advanced parameter to get array
    While 1
If Not IsHWnd($aMsg[1]) Then ContinueLoop ; preventing subsequent lines from processing when nothing happens
        $aMsg = GUIGetMsg(1) ; Use advanced parameter to get array
 
        Switch $aMsg[1] ; check which GUI sent the message
Switch $aMsg[1] ; check which GUI sent the message
            Case $hGUI1
Case $g_hGUI1
                Switch $aMsg[0] ; Now check for the messages for $hGUI1
Switch $aMsg[0] ; Now check for the messages for $g_hGUI1
                    Case $GUI_EVENT_CLOSE ; If we get the CLOSE message from this GUI - we exit <<<<<<<<<<<<<<<
Case $GUI_EVENT_CLOSE ; If we get the CLOSE message from this GUI: $g_hGUI1 ...
                        ExitLoop
ExitLoop ;  ... exit the loop and thus exit the program
                    Case $hButton1
Case $g_idButton1
                        MsgBox("", "MsgBox 1", "Test from Gui 1")
MsgBox($MB_OK, "MsgBox 1", "Test from GUI 1")
                    Case $hButton2
Case $g_idButton2
                        GUICtrlSetState($hButton2, $GUI_DISABLE)
GUICtrlSetState($g_idButton2, $GUI_DISABLE)
                        gui2()
gui2()
                EndSwitch
EndSwitch
            Case $hGUI2
Case $g_hGUI2
                Switch $aMsg[0] ; Now check for the messages for $hGUI2
Switch $aMsg[0] ; Now check for the messages for $g_hGUI2
                    Case $GUI_EVENT_CLOSE ; If we get the CLOSE message from this GUI - we just delete the GUI <<<<<<<<<<<<<<<
Case $GUI_EVENT_CLOSE ; If we get the CLOSE message from this GUI : $g_hGUI2 ...
                        GUIDelete($hGUI2)
GUIDelete($g_hGUI2) ; ... just delete the GUI ...
                        GUICtrlSetState($hButton2, $GUI_ENABLE)
GUICtrlSetState($g_idButton2, $GUI_ENABLE) ; ... enable button (previously disabled)
                    Case $hButton3
Case $g_idButton3
                        MsgBox("", "MsgBox", "Test from Gui 2")
MsgBox($MB_OK, "MsgBox", "Test from GUI 2")
                EndSwitch
EndSwitch
        EndSwitch
EndSwitch
    WEnd
WEnd
 
EndFunc  ;==>gui1
EndFunc  ;==>example
 
Func gui2()
Func gui1()
$g_hGUI1 = GUICreate("GUI 1", 200, 200, 100, 100)
    $hGUI2 = GUICreate("Gui 2", 200, 200, 350, 350)
$g_idButton1 = GUICtrlCreateButton("Msgbox 1", 10, 10, 80, 30)
    $hButton3 = GUICtrlCreateButton("MsgBox 2", 10, 10, 80, 30)
$g_idButton2 = GUICtrlCreateButton("Show GUI 2", 10, 60, 80, 30)
    GUISetState()
GUISetState()
EndFunc  ;==>gui1
EndFunc  ;==>gui2
 
Func gui2()
$g_hGUI2 = GUICreate("GUI 2", 200, 200, 350, 350)
$g_idButton3 = GUICtrlCreateButton("MsgBox 2", 10, 10, 80, 30)
GUISetState()
EndFunc  ;==>gui2
</syntaxhighlight>
</syntaxhighlight>
-----------------------------------
As you can see, we have a single ''While...WEnd'' loop which distinguishes between the 2 GUIs, both GUIs and their controls remain active and we stay in the main idle loop while we wait (you did read that other tutorial I hope!).


[[OnEvent Mode]]
As you can see, we have a single ''While...WEnd'' loop which distinguishes between the two GUIs, both GUIs and their controls remain active and we stay in the main idle loop while we wait (you did read that other tutorial I hope!).
 
=OnEvent Mode=


Coders using ''OnEvent'' mode do not usually find the same problem with multiple GUIs as they can code separate functions for each ''$GUI_EVENT_CLOSE'' as shown here:
Coders using ''OnEvent'' mode do not usually find the same problem with multiple GUIs as they can code separate functions for each ''$GUI_EVENT_CLOSE'' as shown here:
-----------------------------------
<syntaxhighlight lang="autoit">
<syntaxhighlight lang="autoit">
#include <GUIConstantsEx.au3>
#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
Opt("GUIOnEventMode", 1)
 
Opt("GUIOnEventMode", 1)
Global $hGUI2, $hButton2 ; Predeclare these variables
 
Global $g_hGUI2, $g_idButton2
gui1()
 
gui1()
Func gui1()
 
Func gui1()
    $hGUI1 = GUICreate("Gui 1", 200, 200, 100, 100)
Local $hGUI1 = GUICreate("GUI 1", 200, 200, 100, 100)
    GUISetOnEvent($GUI_EVENT_CLOSE, "On_Close_Main") ; Run this function when the main GUI [X] is clicked
GUISetOnEvent($GUI_EVENT_CLOSE, "On_Close_Main") ; Run this function when the main GUI [X] is clicked
    $hButton1 = GUICtrlCreateButton("Msgbox 1", 10, 10, 80, 30)
Local $idButton1 = GUICtrlCreateButton("Msgbox 1", 10, 10, 80, 30)
    GUICtrlSetOnEvent(-1, "On_Button1")
GUICtrlSetOnEvent($idButton1, "On_Button1")
    $hButton2 = GUICtrlCreateButton("Show Gui 2", 10, 60, 80, 30)
$g_idButton2 = GUICtrlCreateButton("Show GUI 2", 10, 60, 80, 30)
    GUICtrlSetOnEvent(-1, "On_Button2")
GUICtrlSetOnEvent(-1, "On_Button2")
    GUISetState()
GUISetState(@SW_SHOW, $hGUI1)
 
    While 1
While 1
        Sleep(10)
Sleep(10)
    WEnd
WEnd
EndFunc  ;==>gui1
EndFunc  ;==>gui1
 
Func gui2()
Func gui2()
$g_hGUI2 = GUICreate("GUI 2", 200, 200, 350, 350)
GUISetOnEvent($GUI_EVENT_CLOSE, "On_Close_Secondary") ; Run this function when the secondary GUI [X] is clicked
    $hGUI2 = GUICreate("Gui 2", 200, 200, 350, 350)
Local $idButton3 = GUICtrlCreateButton("MsgBox 2", 10, 10, 80, 30)
    GUISetOnEvent($GUI_EVENT_CLOSE, "On_Close_Secondary") ; Run this function when the secondary GUI [X] is clicked
GUICtrlSetOnEvent($idButton3, "On_Button3")
    $hButton3 = GUICtrlCreateButton("MsgBox 2", 10, 10, 80, 30)
GUISetState(@SW_SHOW, $g_hGUI2)
    GUICtrlSetOnEvent(-1, "On_Button3")
EndFunc  ;==>gui2
    GUISetState()
 
Func On_Close_Main()
EndFunc  ;==>gui2
Exit
EndFunc   ;==>On_Close_Main
Func On_Close_Main()
 
Func On_Close_Secondary()
    Exit
GUIDelete($g_hGUI2)
GUICtrlSetState($g_idButton2, $GUI_ENABLE)
EndFunc
EndFunc   ;==>On_Close_Secondary
 
Func On_Close_Secondary()
Func On_Button1()
MsgBox($MB_OK, "MsgBox 1", "Test from GUI 1")
    GUIDelete($hGUI2)
EndFunc   ;==>On_Button1
    GUICtrlSetState($hButton2, $GUI_ENABLE)
 
Func On_Button2()
EndFunc
GUICtrlSetState($g_idButton2, $GUI_DISABLE)
gui2()
Func On_Button1()
EndFunc   ;==>On_Button2
 
    MsgBox("", "MsgBox 1", "Test from Gui 1")
Func On_Button3()
MsgBox($MB_OK, "MsgBox 2", "Test from GUI 2")
EndFunc
EndFunc   ;==>On_Button3
Func On_Button2()
    GUICtrlSetState($hButton2, $GUI_DISABLE)
    gui2()
EndFunc
Func On_Button3()
    MsgBox("", "MsgBox 2", "Test from Gui 2")
EndFunc
</syntaxhighlight>
</syntaxhighlight>
-----------------------------------
But did you realise that you can also use what some people think of as a hybrid mode - using common ''OnEvent'' functions and then determining the specific GUI or control which called the function within the function?  As an added bonus, this approach may, depending on the circumstances, let you send parameters to the functions you call - something that you normally cannot do in ''OnEvent'' mode.
But did you realise that you can also use what some people think of as a hybrid mode - using common ''OnEvent'' functions and then determining the specific GUI or control which called the function within the function?  As an added bonus, this approach may, depending on the circumstances, let you send parameters to the functions you call - something that you normally cannot do in ''OnEvent'' mode.
-----------------------------------
<syntaxhighlight lang="autoit">
<syntaxhighlight lang="autoit">
#include <GUIConstantsEx.au3>
#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
Opt("GUIOnEventMode", 1)
 
Opt("GUIOnEventMode", 1)
Global $hGUI1, $hGUI2 = 9999, $hButton1, $hButton2, $hButton3 = 9999 ; Predeclare the variables with dummy values to prevent firing the Case statements
 
Global $g_hGUI1, $g_hGUI2, $g_idButton1, $g_idButton2, $g_idButton3
gui1()
 
gui1()
Func gui1()
 
Func gui1()
    $hGUI1 = GUICreate("Gui 1", 200, 200, 100, 100)
$g_hGUI1 = GUICreate("GUI 1", 200, 200, 100, 100)
    GUISetOnEvent($GUI_EVENT_CLOSE, "On_Close") ; Call a common GUI close function
GUISetOnEvent($GUI_EVENT_CLOSE, "On_Close") ; Call a common GUI close function
    $hButton1 = GUICtrlCreateButton("Msgbox 1", 10, 10, 80, 30)
$g_idButton1 = GUICtrlCreateButton("Msgbox 1", 10, 10, 80, 30)
    GUICtrlSetOnEvent(-1, "On_Button") ; Call a common button function
GUICtrlSetOnEvent(-1, "On_Button") ; Call a common button function
    $hButton2 = GUICtrlCreateButton("Show Gui 2", 10, 60, 80, 30)
$g_idButton2 = GUICtrlCreateButton("Show GUI 2", 10, 60, 80, 30)
    GUICtrlSetOnEvent(-1, "On_Button") ; Call a common button function
GUICtrlSetOnEvent(-1, "On_Button") ; Call a common button function
    GUISetState()
GUISetState()
 
    While 1
While 1
        Sleep(10)
Sleep(10)
    WEnd
WEnd
EndFunc  ;==>gui1
EndFunc  ;==>gui1
 
Func gui2()
Func gui2()
$g_hGUI2 = GUICreate("GUI 2", 200, 200, 350, 350)
GUISetOnEvent($GUI_EVENT_CLOSE, "On_Close") ; Call a common GUI close function
    $hGUI2 = GUICreate("Gui 2", 200, 200, 350, 350)
$g_idButton3 = GUICtrlCreateButton("MsgBox 2", 10, 10, 80, 30)
    GUISetOnEvent($GUI_EVENT_CLOSE, "On_Close") ; Call a common GUI close function
GUICtrlSetOnEvent(-1, "On_Button") ; Call a common button function
    $hButton3 = GUICtrlCreateButton("MsgBox 2", 10, 10, 80, 30)
GUISetState()
    GUICtrlSetOnEvent(-1, "On_Button") ; Call a common button function
EndFunc  ;==>gui2
    GUISetState()
 
Func On_Close()
EndFunc  ;==>gui2
Switch @GUI_WinHandle ; See which GUI sent the CLOSE message
Case $g_hGUI1
Func On_Close()
Exit ; If it was this GUI - we exit <<<<<<<<<<<<<<<
Case $g_hGUI2
    Switch @GUI_WINHANDLE ; See which GUI sent the CLOSE message
GUIDelete($g_hGUI2) ; If it was this GUI - we just delete the GUI <<<<<<<<<<<<<<<
        Case $hGUI1
GUICtrlSetState($g_idButton2, $GUI_ENABLE)
            Exit ; If it was this GUI - we exit <<<<<<<<<<<<<<<
EndSwitch
        Case $hGUI2
EndFunc   ;==>On_Close
            GUIDelete($hGUI2) ; If it was this GUI - we just delete the GUI <<<<<<<<<<<<<<<
 
            GUICtrlSetState($hButton2, $GUI_ENABLE)
Func On_Button()
    EndSwitch
Switch @GUI_CtrlId ; See which button sent the message
Case $g_idButton1
EndFunc
MessageBox(1) ; We can call a function with parameters here <<<<<<<<<<<<<<<<<<<
Case $g_idButton2
Func On_Button()
GUICtrlSetState($g_idButton2, $GUI_DISABLE)
gui2()
    Switch @GUI_CTRLID ; See which button sent the message
Case $g_idButton3
        Case $hButton1
MessageBox(2) ; We can call a function with parameters here <<<<<<<<<<<<<<<<<<<
            MessageBox(1) ; We can call a function with parameters here <<<<<<<<<<<<<<<<<<<
EndSwitch
        Case $hButton2
EndFunc   ;==>On_Button
            GUICtrlSetState($hButton2, $GUI_DISABLE)
 
            gui2()
Func MessageBox($iIndex)
        Case $hButton3
MsgBox($MB_OK, "MsgBox " & $iIndex, "Test from Gui " & $iIndex)
            MessageBox(2) ; We can call a function with parameters here <<<<<<<<<<<<<<<<<<<
EndFunc   ;==>MessageBox
    EndSwitch
EndFunc
Func MessageBox($iIndex)
    MsgBox("", "MsgBox " & $iIndex, "Test from Gui " & $iIndex)
EndFunc
</syntaxhighlight>
</syntaxhighlight>
-----------------------------------
 
=Summary=
 
So you see that managing multiple GUIS is not as difficult as you might think.  One of these methods is bound to suit your script, but do not try and mix them - only one method per script please!
So you see that managing multiple GUIS is not as difficult as you might think.  One of these methods is bound to suit your script, but do not try and mix them - only one method per script please!
[[Category:Tutorials]]
[[Category:GUI]]

Latest revision as of 21:20, 19 September 2021

Introduction

Having several GUIs on the screen at the same time is fairly common but structuring your code to deal with this can seem quite daunting. However, as I hope this tutorial will demonstrate, it is nowhere near as difficult as it first appears.

MessageLoop Mode

Let us start with MessageLoop mode as this is where most new coders run into difficulties with multiple GUIs. This example script illustrates the problem - it exits when the [X] is clicked on either GUI:

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>

Global $g_idButton3

gui1()

Func gui1()
	Local $hGUI1 = GUICreate("GUI 1", 200, 200, 100, 100)
	Local $idButton1 = GUICtrlCreateButton("Msgbox 1", 10, 10, 80, 30)
	Local $idButton2 = GUICtrlCreateButton("Show GUI 2", 10, 60, 80, 30)
	GUISetState(@SW_SHOW, $hGUI1)

	While 1
		Switch GUIGetMsg()
			Case $GUI_EVENT_NONE
				ContinueLoop
			Case $GUI_EVENT_CLOSE
				ExitLoop
			Case $idButton1
				MsgBox($MB_OK, "MsgBox 1", "Test from GUI 1")
			Case $idButton2
				GUICtrlSetState($idButton2, $GUI_DISABLE)
				gui2()
			Case $g_idButton3
				MsgBox($MB_OK, "MsgBox 2", "Test from GUI 2")
		EndSwitch
	WEnd
EndFunc   ;==>gui1

Func gui2()
	Local $hGUI2 = GUICreate("GUI 2", 200, 200, 350, 350)
	$g_idButton3 = GUICtrlCreateButton("MsgBox 2", 10, 10, 80, 30)
	GUISetState(@SW_SHOW, $hGUI2)
EndFunc   ;==>gui2

The script exits because it has a single GUIGetMsg loop and the $GUI_EVENT_CLOSE message is received when either [X] is clicked - we have no way of telling the messages from the two GUIs apart.

The simplest solution is to disable the first GUI while the second is displayed:

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>

gui1()

Func gui1()
	Local $hGUI1 = GUICreate("GUI 1", 200, 200, 100, 100)
	Local $idButton1 = GUICtrlCreateButton("Msgbox 1", 10, 10, 80, 30)
	Local $idButton2 = GUICtrlCreateButton("Show GUI 2", 10, 60, 80, 30)
	GUISetState()

	While 1
		Switch GUIGetMsg()
			Case $GUI_EVENT_NONE
				ContinueLoop
			Case $GUI_EVENT_CLOSE
				ExitLoop
			Case $idButton1
				MsgBox($MB_OK, "MsgBox 1", "Test from GUI 1")
			Case $idButton2
				; Disable the first GUI
				GUISetState(@SW_DISABLE, $hGUI1)
				gui2()
				; Re-enable the first GUI
				GUISetState(@SW_ENABLE, $hGUI1)
		EndSwitch
	WEnd
EndFunc   ;==>gui1

Func gui2()
	Local $hGUI2 = GUICreate("GUI 2", 200, 200, 350, 350)
	Local $idButton3 = GUICtrlCreateButton("MsgBox 2", 10, 10, 80, 30)
	GUISetState()

	While 1
		; We can only get messages from the second GUI
		Switch GUIGetMsg()
			Case $GUI_EVENT_NONE
				ContinueLoop
			Case $GUI_EVENT_CLOSE
				GUIDelete($hGUI2)
				ExitLoop
			Case $idButton3
				MsgBox($MB_OK, "MsgBox 2", "Test from GUI 2")
		EndSwitch
	WEnd

EndFunc   ;==>gui2

This may well be all you need, but it does mean that we cannot action any of the controls on the first GUI until we close the second. And importantly we remain blocked in the While...WEnd loop within the gui2 function - go and read the Interrupting a running function tutorial to see why this is less than ideal.

So how can we deal with multiple GUIs visible at the same time? Fortunately AutoIt offers us a simple way to differentiate between GUIs in MessageLoop mode. Normally we use code like this in our idle loop to detect the messages sent by our GUI and its controls:

Switch GUIGetMsg()
	Case $GUI_EVENT_NONE
		ContinueLoop
	Case $GUI_EVENT_CLOSE
		; Code
	Case $idButton1
		; Code
EndSwitch

But when dealing with multiple GUIs, we need to use the "advanced" parameter when we call GUIGetMsg. As explained in the Help file, the function then returns an array instead of a single value. This array includes information on what exactly triggered the message, just what we need to distinguish the message that was sent (element[0] of the array) and which GUI sent it (element[1]). We can then amend our simple Switch statement above to read like this:

$aMsg = GUIGetMsg(1) ; Use advanced parameter to get an array returned
If Not IsHWnd($aMsg[1]) Then ContinueLoop ; preventing subsequent lines from processing when nothing happens

Switch $aMsg[1] ; First check which GUI sent the message
	Case $hGUI1
		Switch $aMsg[0] ; Now check for the messages sent from $hGUI1
			Case $GUI_EVENT_NONE ; it will not be necessary as we already check: If Not IsHWnd($aMsg[1]) Then
				ContinueLoop
			Case $GUI_EVENT_CLOSE
				; Code
			Case $hControl
				; Code
		EndSwitch
	Case $hGUI2
		Switch $aMsg[0] ; Now check for the messages sent from $hGUI2
			Case $GUI_EVENT_NONE ; it will not be necessary as we already check: If Not IsHWnd($aMsg[1]) Then
				ContinueLoop
			Case $GUI_EVENT_CLOSE
				; Code
			Case $idButton3
				; Code
		EndSwitch
EndSwitch

Although this looks complicated, if you take a moment to study it and you will quickly realise it is simply two Switch structures within an outer Switch. You have already dealt with a single Switch structure for a single GUI. All you are doing here is determining which Switch structure you want to use, and that depends on the GUI which sent the message which is why we need the outer Switch structure as a wrapper.

So here is an example of how to manage two GUIs simultaneously using the "advanced" parameter with GUIGetMsg:

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>

Global $g_hGUI1, $g_idButton1, $g_idButton2, $g_hGUI2, $g_idButton3

example()

Func example()
	gui1()
	Local $aMsg

	While 1
		$aMsg = GUIGetMsg(1) ; Use advanced parameter to get array
		If Not IsHWnd($aMsg[1]) Then ContinueLoop ; preventing subsequent lines from processing when nothing happens

		Switch $aMsg[1] ; check which GUI sent the message
			Case $g_hGUI1
				Switch $aMsg[0] ; Now check for the messages for $g_hGUI1
					Case $GUI_EVENT_CLOSE ; If we get the CLOSE message from this GUI: $g_hGUI1 ...
						ExitLoop ;  ... exit the loop and thus exit the program
					Case $g_idButton1
						MsgBox($MB_OK, "MsgBox 1", "Test from GUI 1")
					Case $g_idButton2
						GUICtrlSetState($g_idButton2, $GUI_DISABLE)
						gui2()
				EndSwitch
			Case $g_hGUI2
				Switch $aMsg[0] ; Now check for the messages for $g_hGUI2
					Case $GUI_EVENT_CLOSE ; If we get the CLOSE message from this GUI : $g_hGUI2 ...
						GUIDelete($g_hGUI2) ; ... just delete the GUI ...
						GUICtrlSetState($g_idButton2, $GUI_ENABLE) ; ... enable button (previously disabled)
					Case $g_idButton3
						MsgBox($MB_OK, "MsgBox", "Test from GUI 2")
				EndSwitch
		EndSwitch
	WEnd

EndFunc   ;==>example

Func gui1()
	$g_hGUI1 = GUICreate("GUI 1", 200, 200, 100, 100)
	$g_idButton1 = GUICtrlCreateButton("Msgbox 1", 10, 10, 80, 30)
	$g_idButton2 = GUICtrlCreateButton("Show GUI 2", 10, 60, 80, 30)
	GUISetState()
EndFunc   ;==>gui1

Func gui2()
	$g_hGUI2 = GUICreate("GUI 2", 200, 200, 350, 350)
	$g_idButton3 = GUICtrlCreateButton("MsgBox 2", 10, 10, 80, 30)
	GUISetState()
EndFunc   ;==>gui2

As you can see, we have a single While...WEnd loop which distinguishes between the two GUIs, both GUIs and their controls remain active and we stay in the main idle loop while we wait (you did read that other tutorial I hope!).

OnEvent Mode

Coders using OnEvent mode do not usually find the same problem with multiple GUIs as they can code separate functions for each $GUI_EVENT_CLOSE as shown here:

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>

Opt("GUIOnEventMode", 1)

Global $g_hGUI2, $g_idButton2

gui1()

Func gui1()
	Local $hGUI1 = GUICreate("GUI 1", 200, 200, 100, 100)
	GUISetOnEvent($GUI_EVENT_CLOSE, "On_Close_Main") ; Run this function when the main GUI [X] is clicked
	Local $idButton1 = GUICtrlCreateButton("Msgbox 1", 10, 10, 80, 30)
	GUICtrlSetOnEvent($idButton1, "On_Button1")
	$g_idButton2 = GUICtrlCreateButton("Show GUI 2", 10, 60, 80, 30)
	GUICtrlSetOnEvent(-1, "On_Button2")
	GUISetState(@SW_SHOW, $hGUI1)

	While 1
		Sleep(10)
	WEnd
EndFunc   ;==>gui1

Func gui2()
	$g_hGUI2 = GUICreate("GUI 2", 200, 200, 350, 350)
	GUISetOnEvent($GUI_EVENT_CLOSE, "On_Close_Secondary") ; Run this function when the secondary GUI [X] is clicked
	Local $idButton3 = GUICtrlCreateButton("MsgBox 2", 10, 10, 80, 30)
	GUICtrlSetOnEvent($idButton3, "On_Button3")
	GUISetState(@SW_SHOW, $g_hGUI2)
EndFunc   ;==>gui2

Func On_Close_Main()
	Exit
EndFunc   ;==>On_Close_Main

Func On_Close_Secondary()
	GUIDelete($g_hGUI2)
	GUICtrlSetState($g_idButton2, $GUI_ENABLE)
EndFunc   ;==>On_Close_Secondary

Func On_Button1()
	MsgBox($MB_OK, "MsgBox 1", "Test from GUI 1")
EndFunc   ;==>On_Button1

Func On_Button2()
	GUICtrlSetState($g_idButton2, $GUI_DISABLE)
	gui2()
EndFunc   ;==>On_Button2

Func On_Button3()
	MsgBox($MB_OK, "MsgBox 2", "Test from GUI 2")
EndFunc   ;==>On_Button3

But did you realise that you can also use what some people think of as a hybrid mode - using common OnEvent functions and then determining the specific GUI or control which called the function within the function? As an added bonus, this approach may, depending on the circumstances, let you send parameters to the functions you call - something that you normally cannot do in OnEvent mode.

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>

Opt("GUIOnEventMode", 1)

Global $g_hGUI1, $g_hGUI2, $g_idButton1, $g_idButton2, $g_idButton3

gui1()

Func gui1()
	$g_hGUI1 = GUICreate("GUI 1", 200, 200, 100, 100)
	GUISetOnEvent($GUI_EVENT_CLOSE, "On_Close") ; Call a common GUI close function
	$g_idButton1 = GUICtrlCreateButton("Msgbox 1", 10, 10, 80, 30)
	GUICtrlSetOnEvent(-1, "On_Button") ; Call a common button function
	$g_idButton2 = GUICtrlCreateButton("Show GUI 2", 10, 60, 80, 30)
	GUICtrlSetOnEvent(-1, "On_Button") ; Call a common button function
	GUISetState()

	While 1
		Sleep(10)
	WEnd
EndFunc   ;==>gui1

Func gui2()
	$g_hGUI2 = GUICreate("GUI 2", 200, 200, 350, 350)
	GUISetOnEvent($GUI_EVENT_CLOSE, "On_Close") ; Call a common GUI close function
	$g_idButton3 = GUICtrlCreateButton("MsgBox 2", 10, 10, 80, 30)
	GUICtrlSetOnEvent(-1, "On_Button") ; Call a common button function
	GUISetState()
EndFunc   ;==>gui2

Func On_Close()
	Switch @GUI_WinHandle ; See which GUI sent the CLOSE message
		Case $g_hGUI1
			Exit ; If it was this GUI - we exit <<<<<<<<<<<<<<<
		Case $g_hGUI2
			GUIDelete($g_hGUI2) ; If it was this GUI - we just delete the GUI <<<<<<<<<<<<<<<
			GUICtrlSetState($g_idButton2, $GUI_ENABLE)
	EndSwitch
EndFunc   ;==>On_Close

Func On_Button()
	Switch @GUI_CtrlId ; See which button sent the message
		Case $g_idButton1
			MessageBox(1) ; We can call a function with parameters here <<<<<<<<<<<<<<<<<<<
		Case $g_idButton2
			GUICtrlSetState($g_idButton2, $GUI_DISABLE)
			gui2()
		Case $g_idButton3
			MessageBox(2) ; We can call a function with parameters here <<<<<<<<<<<<<<<<<<<
	EndSwitch
EndFunc   ;==>On_Button

Func MessageBox($iIndex)
	MsgBox($MB_OK, "MsgBox " & $iIndex, "Test from Gui " & $iIndex)
EndFunc   ;==>MessageBox

Summary

So you see that managing multiple GUIS is not as difficult as you might think. One of these methods is bound to suit your script, but do not try and mix them - only one method per script please!