exprv4:how-to:handle_dialog_results

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:

  • synchronous dialogs: `show()` waits and returns the result
  • asynchronous dialogs: `show()` returns immediately and a callback receives the result later

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

exprv4/how-to/handle_dialog_results.txt · Last modified: by 127.0.0.1

Page Tools