Search code examples
dm-script

How to handle modal dialog posted by deferred task when DM app window is minimized?


Granted, this is a rather arcane question, but it does actually impact a DM script module I have in the works. I'm trying to use a custom modal dialog to alert users to an error condition detected by a deferred main thread task. In most cases this works just fine, but if the DM app window happens to be minimized when the error message is posted, then DM ends up in a strange state when it is restored as the foreground app. The modal dialog is invisible, but it nevertheless inhibits user action within DM until it is dismissed with either 'enter' or 'esc' keystroke.

The sample code below demonstrates the problem and mentions a solution that worked in GMS 1.

Is there a similar or better workaround that will work in GMS 2 and later?

class DeferredAlertTask
{
    Number deferredTaskID;

    DeferredAlertTask(Object self)
    {
        Number taskDelay_sec = 5;
        String message = "Click OK and then minimize the DM app window.\n";
        message += "After 5 seconds, select DM on the task bar to restore it.\n";
        message += "Dialog will be invisible, must hit 'enter' or 'esc' to go on.";
        OKDialog(message);

        deferredTaskID = AddMainThreadSingleTask(self, "Task", taskDelay_sec);
    }

    void Task(Object self)
    {
        String banner = "Error dialog";
        String message = "Error message details.";

        // Create the dialog box descriptor TagGroup
        TagGroup dialogItemsSpec;
        TagGroup dialogSpec = DLGCreateDialog(banner, dialogItemsSpec);

        // Create and add the content box and text field to the layout
        TagGroup contentBoxItemsSpec;
        TagGroup contentBoxSpec = DLGCreateBox(contentBoxItemsSpec);
        TagGroup contentLabelSpec = DLGCreateLabel(message);
        contentBoxItemsSpec.DLGAddElement(contentLabelSpec);
        dialogItemsSpec.DLGAddElement(contentBoxSpec);

        // If the DM app window has been minimized, 
        // this modal dialog will be invisible,
        // but it will still inhibit further user action
        // within DM as it awaits 'esc' or 'enter'.

        // The following is a remedy that works in GMS1, but not in GMS2
        // GetApplicationWindow().WindowSelect();

        Object dialog = Alloc(UIFrame).Init(dialogSpec);
        String result = (dialog.Pose()) ? "OK" : "Cancel";
        OKDialog(result);
    }
}

void main()
{
    Alloc(DeferredAlertTask);
}

main();

Solution

  • The suggestion to base a solution on the LaunchExternalProcess() function and an external program has provided a path to an answer. By using the free, open-source Windows macro creation package called AutoHotKey, I have been able to create a very compact executable named RestoreDM.exe. By placing this executable in a folder easily accessible from a DM script, it can be launched via LaunchExternalProcessAsync() to make sure that the DM app window is restored before posting a custom dialog. Below is a modified version of the original test script illustrating this solution and providing details about the AutoHotKey script:

    class DeferredAlertTask
    {
        Number deferredTaskID;
    
        DeferredAlertTask(Object self)
        {
            Number taskDelay_sec = 5;
            String message = "Click OK and then minimize the DM app window.\n";
            message += "After 5 seconds, select DM on the task bar to restore it.\n";
            message += "Dialog will be invisible, must hit 'enter' or 'esc' to go on.";
            OKDialog(message);
    
            deferredTaskID = AddMainThreadSingleTask(self, "Task", taskDelay_sec);
        }
    
        void Task(Object self)
        {
            String banner = "Error dialog";
            String message = "Error message details.";
    
            // Create the dialog box descriptor TagGroup
            TagGroup dialogItemsSpec;
            TagGroup dialogSpec = DLGCreateDialog(banner, dialogItemsSpec);
    
            // Create and add the content box and text field to the layout
            TagGroup contentBoxItemsSpec;
            TagGroup contentBoxSpec = DLGCreateBox(contentBoxItemsSpec);
            TagGroup contentLabelSpec = DLGCreateLabel(message);
            contentBoxItemsSpec.DLGAddElement(contentLabelSpec);
            dialogItemsSpec.DLGAddElement(contentBoxSpec);
    
            // If the DM app window has been minimized, 
            // this modal dialog will be invisible,
            // but it will still inhibit further user action
            // within DM as it awaits 'esc' or 'enter'.
    
            // The following is a remedy that works in GMS1, but not in GMS2
            // GetApplicationWindow().WindowSelect();
    
            // For GMS2, we can use an executable that restores the DM app window.
            // The lines below launch RestoreDM.exe, placed in C:\ProgramData\Gatan,
            // where RestoreDM is an executable of the following AutoHotKey script:
            // IfWinNotActive, Digital Micrograph
            //      WinRestore, Digital Micrograph
            String commandDir = GetApplicationDirectory(3, 0);
            String restoreCommand = commandDir.PathConcatenate("RestoreDM");
            LaunchExternalProcessAsync(restoreCommand);
            Sleep(0.1);
    
            Object dialog = Alloc(UIFrame).Init(dialogSpec);
            String result = (dialog.Pose()) ? "OK" : "Cancel";
            OKDialog(result);
        }
    }
    
    void main()
    {
        Alloc(DeferredAlertTask);
    }
    
    main();
    

    It is necessary to use the asynchronous variant, LaunchExternalProcessAsync(), because the deferred alert task is called on the main thread and thus blocks DM from restoring its window when prompted by the RestoreDM program (causing DM to hang). Note also that a brief sleep is required after calling the external program in order to make sure that the DM app window is restored before the custom dialog is posed.