Table of Contents

How To Handle Dialog Results

Dialogs return information after the user clicks a button, selects a file, or closes the window.

Expr has two common result styles:

Use synchronous results for short scripts that can wait. Use asynchronous callbacks when the dialog should stay open while the rest of the application continues.


Handle A Message Dialog Result

Message dialogs return a result `map`.

result = msg_ok_cancel()
    .title('Confirm')
    .message('Start probing?')
    .show();
 
if (result.ok)
{
    print('start probing');
}
else
{
    print('cancelled');
}

Common result fields are:

Field Meaning
`action` Text action, such as `ok`, `cancel`, or `none`.
`action_code` Numeric host action code.
`ok` `true` when OK or Yes was selected.
`cancel` `true` when Cancel, No, or Close was selected.
`affirmative` `true` for affirmative choices such as OK or Yes.
`save` Save flag from the host dialog result.
`password` Password text for `msg_password()`. Empty for other message dialogs.

For most scripts, `result.ok` and `result.cancel` are the clearest fields to use.


Handle A File Dialog Result

File dialogs return an `array` of selected paths, or `none()` when the user cancels.

files = file_open()
    .title('Open program')
    .add_filter('Expr files', '*.expr;*.txt')
    .show();
 
if (files.is_none())
{
    print('cancelled');
}
else
{
    print('selected: ', files.get(0));
}

`file_save()` uses the same result style. The returned array normally contains one path.

files = file_save()
    .title('Save report')
    .file('report.txt')
    .add_filter('Text files', '*.txt')
    .show();
 
if (!files.is_none())
{
    report_file = files.get(0);
    print('save to: ', report_file);
}

With `file_open().multi_select(true)`, the array can contain more than one path.

files = file_open()
    .title('Open images')
    .multi_select(true)
    .add_filter('Images', '*.png;*.jpg;*.webp')
    .show();
 
if (!files.is_none())
{
    for (i = 0; i < files.length(); i += 1)
    {
        print(files.get(i));
    }
}

Use A Callback For Asynchronous Results

When you use `on_result(callback)`, `show()` returns the dialog object immediately. The callback receives the result later.

function ConfirmResult(result)
{
    if (result.ok)
    {
        print('confirmed');
    }
    else
    {
        print('cancelled');
    }
}
 
msg_ok_cancel()
    .title('Confirm')
    .message('Continue?')
    .on_result(ConfirmResult)
    .show();

For file dialogs, the callback receives the selected files array or `none()`.

function FilesSelected(files)
{
    if (files.is_none())
    {
        print('cancelled');
        return;
    }
 
    print('first file: ', files.get(0));
}
 
file_open()
    .title('Open file')
    .on_result(FilesSelected)
    .show();

Pass the callback name without parentheses. `FilesSelected` passes the callback. `FilesSelected()` calls it immediately.


Store Async Results In An Object

For asynchronous dialogs, a class is usually the cleanest way to keep dialog state and result handling together.

class FileChoice()
{
    dlg = none();
    selected = none();
    accepted = false;
 
    function Show()
    {
        dlg = file_open()
            .title('Open file')
            .add_filter('Expr files', '*.expr;*.txt')
            .on_result(this.OnResult);
 
        dlg.show();
    }
 
    function OnResult(files)
    {
        accepted = !files.is_none();
        selected = files;
 
        if (accepted)
        {
            print('selected: ', selected.get(0));
        }
        else
        {
            print('cancelled');
        }
    }
}
 
choice = FileChoice();
choice.Show();

This keeps the dialog object, selected files, and accepted flag together.


Handle Password Results

`msg_password()` returns the same message-dialog result map, with the `password` field filled when OK is selected.

result = msg_password()
    .title('Password')
    .message('Enter password')
    .password_label('Password:')
    .show();
 
if (result.ok)
{
    password = result.password;
    print('password entered');
}
else
{
    print('cancelled');
}

Avoid printing or storing passwords unless that is really needed.


Handle Custom Window Results

Custom `window()` dialogs use result codes in `on_request()` and `on_closed()`.

Code Meaning
`0` Close
`1` OK
`2` Apply

Use `on_request()` to validate before the window closes. Return `false` to keep the window open for OK or Close requests.

Use `on_closed()` to store final results after the window closes.

class ToolDialog()
{
    wnd = none();
    tool_input = none();
    accepted = false;
    tool = '';
 
    function Show()
    {
        tool_input = textinput()
            .position(90, 20)
            .size(150, 24)
            .text('T1');
 
        wnd = window()
            .title('Tool')
            .size(280, 120)
            .buttons(false, true, true)
            .on_request(this.OnRequest)
            .on_closed(this.OnClosed);
 
        wnd.add(label().position(20, 20).size(60, 24).text('Tool'));
        wnd.add(tool_input);
        wnd.show();
    }
 
    function OnRequest(code)
    {
        if (code == 1 && tool_input.text.length() == 0)
        {
            print('Tool is required');
            return false;
        }
 
        return true;
    }
 
    function OnClosed(code)
    {
        accepted = code == 1;
 
        if (accepted)
        {
            tool = tool_input.text;
            print('tool: ', tool);
        }
    }
}
 
dlg = ToolDialog();
dlg.Show();

This is the same result pattern as built-in dialogs: keep the callback small, validate in request callbacks, and store final values in one object.


Return Or Store Results Deliberately

For synchronous dialogs, assign the return value directly:

result = msg_yes_no()
    .title('Question')
    .message('Continue?')
    .show();
 
continue_job = result.ok;

For asynchronous dialogs, store the result in an object field:

class ConfirmState()
{
    done = false;
    ok = false;
 
    function OnResult(result)
    {
        done = true;
        ok = result.ok;
    }
}
 
confirm = ConfirmState();
msg_ok_cancel().message('Continue?').on_result(confirm.OnResult).show();

Avoid scattering dialog state across many root variables. One object is easier to inspect, clear, and reuse.


Common Mistakes

Using file dialog results without checking for cancel:

files = file_open().show();
print(files.get(0));          // wrong if user cancels

Better:

files = file_open().show();
if (!files.is_none())
{
    print(files.get(0));
}

Calling a callback instead of passing it:

dlg.on_result(OnResult());    // wrong
dlg.on_result(OnResult);      // correct

Expecting an asynchronous result immediately:

dlg = msg_ok_cancel().on_result(OnResult).show();
// result is not available here yet

For asynchronous dialogs, put result handling inside the callback.


Use synchronous dialogs for short scripts:

result = msg_ok_cancel().message('Continue?').show();
if (result.ok)
{
    print('continue');
}

Use asynchronous dialogs with an object for longer workflows:

class DialogState()
{
    accepted = false;
 
    function OnResult(result)
    {
        accepted = result.ok;
    }
}
 
state = DialogState();
msg_ok_cancel().message('Continue?').on_result(state.OnResult).show();

See Also


How-To index: How-To Guides