Managing Multiple GUIs

From AutoIt Wiki

Jump to: navigation, search

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>

Global $hButton3 = 9999

gui1()

Func gui1()

    $hGUI1 = GUICreate("Gui 1", 200, 200, 100, 100)
    $hButton1 = GUICtrlCreateButton("Msgbox 1", 10, 10, 80, 30)
    $hButton2 = GUICtrlCreateButton("Show Gui 2", 10, 60, 80, 30)
    GUISetState()

    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                ExitLoop
            Case $hButton1
                MsgBox("", "MsgBox 1", "Test from Gui 1")
            Case $hButton2
                GUICtrlSetState($hButton2, $GUI_DISABLE)
                gui2()
            Case $hButton3
                MsgBox("", "MsgBox 2", "Test from Gui 2") 
        EndSwitch
    WEnd

EndFunc   ;==>gui1

Func gui2()

    $hGUI2 = GUICreate("Gui 2", 200, 200, 350, 350)
    $hButton3 = GUICtrlCreateButton("MsgBox 2", 10, 10, 80, 30)
    GUISetState()

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 2 GUIs apart.

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

#include <GUIConstantsEx.au3>

gui1()

Func gui1()

    $hGUI1 = GUICreate("Gui 1", 200, 200, 100, 100)
    $hButton1 = GUICtrlCreateButton("Msgbox 1", 10, 10, 80, 30)
    $hButton2 = GUICtrlCreateButton("Show Gui 2", 10, 60, 80, 30)
    GUISetState()

    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                ExitLoop
            Case $hButton1
                MsgBox("", "MsgBox 1", "Test from Gui 1")
            Case $hButton2
                ; Disable the first GUI
                GUISetState(@SW_DISABLE, $hGUI1)
                gui2()
                ; Re-enable the first GUI
                GUISetState(@SW_ENABLE, $hGUI1)
        EndSwitch
    WEnd

EndFunc   ;==>gui1

Func gui2()

    $hGUI2 = GUICreate("Gui 2", 200, 200, 350, 350)
    $hButton3 = GUICtrlCreateButton("MsgBox 2", 10, 10, 80, 30)
    GUISetState()

    While 1
        ; We can only get messages from the second GUI
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                GUIDelete($hGUI2)
                ExitLoop
            Case $hButton3
                MsgBox("", "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_CLOSE
        ; Code
    Case $hButton1
        ; 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
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_CLOSE 
                ; Code
            Case $hControl
                ; Code
        EndSwitch
    Case $hGUI2
        Switch $aMsg[0] ; Now check for the messages sent from $hGUI2
            Case $GUI_EVENT_CLOSE
                ; Code
            Case $hButton3
                ; Code
        EndSwitch
EndSwitch

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:

#include <GUIConstantsEx.au3>

Global $hGUI2 = 9999, $hButton3 = 9999 ; Predeclare the variables with dummy values to prevent firing the Case statements

gui1()

Func gui1()

    $hGUI1 = GUICreate("Gui 1", 200, 200, 100, 100)
    $hButton1 = GUICtrlCreateButton("Msgbox 1", 10, 10, 80, 30)
    $hButton2 = GUICtrlCreateButton("Show Gui 2", 10, 60, 80, 30)
    GUISetState()

    While 1
        $aMsg = GUIGetMsg(1) ; Use advanced parameter to get array
        Switch $aMsg[1] ; check which GUI sent the message
            Case $hGUI1
                Switch $aMsg[0] ; Now check for the messages for $hGUI1
                    Case $GUI_EVENT_CLOSE ; If we get the CLOSE message from this GUI - we exit <<<<<<<<<<<<<<<
                        ExitLoop
                    Case $hButton1
                        MsgBox("", "MsgBox 1", "Test from Gui 1")
                    Case $hButton2
                        GUICtrlSetState($hButton2, $GUI_DISABLE)
                        gui2()
                EndSwitch
            Case $hGUI2
                Switch $aMsg[0] ; Now check for the messages for $hGUI2
                    Case $GUI_EVENT_CLOSE ; If we get the CLOSE message from this GUI - we just delete the GUI <<<<<<<<<<<<<<<
                        GUIDelete($hGUI2)
                        GUICtrlSetState($hButton2, $GUI_ENABLE)
                    Case $hButton3
                        MsgBox("", "MsgBox", "Test from Gui 2")
                EndSwitch
        EndSwitch
    WEnd

EndFunc   ;==>gui1

Func gui2()

    $hGUI2 = GUICreate("Gui 2", 200, 200, 350, 350)
    $hButton3 = 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 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

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>

Opt("GUIOnEventMode", 1)

Global $hGUI2, $hButton2 ; Predeclare these variables

gui1()

Func gui1()

    $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
    $hButton1 = GUICtrlCreateButton("Msgbox 1", 10, 10, 80, 30)
    GUICtrlSetOnEvent(-1, "On_Button1")
    $hButton2 = GUICtrlCreateButton("Show Gui 2", 10, 60, 80, 30)
    GUICtrlSetOnEvent(-1, "On_Button2")
    GUISetState()

    While 1
        Sleep(10)
    WEnd

EndFunc   ;==>gui1

Func gui2()

    $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
    $hButton3 = GUICtrlCreateButton("MsgBox 2", 10, 10, 80, 30)
    GUICtrlSetOnEvent(-1, "On_Button3")
    GUISetState()

EndFunc   ;==>gui2

Func On_Close_Main()

    Exit

EndFunc

Func On_Close_Secondary()

    GUIDelete($hGUI2)
    GUICtrlSetState($hButton2, $GUI_ENABLE)

EndFunc

Func On_Button1()

    MsgBox("", "MsgBox 1", "Test from Gui 1")

EndFunc

Func On_Button2()

    GUICtrlSetState($hButton2, $GUI_DISABLE)
    gui2()

EndFunc

Func On_Button3()

    MsgBox("", "MsgBox 2", "Test from Gui 2")

EndFunc

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>

Opt("GUIOnEventMode", 1)

Global $hGUI1, $hGUI2 = 9999, $hButton1, $hButton2, $hButton3 = 9999 ; Predeclare the variables with dummy values to prevent firing the Case statements

gui1()

Func gui1()

    $hGUI1 = GUICreate("Gui 1", 200, 200, 100, 100)
    GUISetOnEvent($GUI_EVENT_CLOSE, "On_Close") ; Call a common GUI close function
    $hButton1 = GUICtrlCreateButton("Msgbox 1", 10, 10, 80, 30)
    GUICtrlSetOnEvent(-1, "On_Button") ; Call a common button function
    $hButton2 = 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()

    $hGUI2 = GUICreate("Gui 2", 200, 200, 350, 350)
    GUISetOnEvent($GUI_EVENT_CLOSE, "On_Close") ; Call a common GUI close function
    $hButton3 = 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 $hGUI1
            Exit ; If it was this GUI - we exit <<<<<<<<<<<<<<<
        Case $hGUI2
            GUIDelete($hGUI2) ; If it was this GUI - we just delete the GUI <<<<<<<<<<<<<<<
            GUICtrlSetState($hButton2, $GUI_ENABLE)
    EndSwitch

EndFunc

Func On_Button()

    Switch @GUI_CTRLID ; See which button sent the message
        Case $hButton1
            MessageBox(1) ; We can call a function with parameters here <<<<<<<<<<<<<<<<<<<
        Case $hButton2
            GUICtrlSetState($hButton2, $GUI_DISABLE)
            gui2()
        Case $hButton3
            MessageBox(2) ; We can call a function with parameters here <<<<<<<<<<<<<<<<<<<
    EndSwitch

EndFunc

Func MessageBox($iIndex)

    MsgBox("", "MsgBox " & $iIndex, "Test from Gui " & $iIndex)

EndFunc

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!

Personal tools