MahApps/MahApps.Metro · error · InvalidOperationException

Dialog isn't visible to close

Error message

Dialog isn't visible to close

What it means

Thrown by ProgressDialogController.CloseAsync when WrappedDialog.IsVisible is false. The controller is obtained from ShowProgressAsync, which already closes the dialog when its task completes or is cancelled; calling CloseAsync again finds the dialog no longer visible. It guards against double-close and orphaned event handler unsubscribes.

Source

Thrown at src/MahApps.Metro/Controls/Dialogs/ProgressDialogController.cs:174

        /// Sets the dialog's progress bar brush.
        /// </summary>
        /// <param name="brush">The brush to use for the progress bar's foreground.</param>
        public void SetProgressBarForegroundBrush(Brush brush)
        {
            this.WrappedDialog.Invoke(() => this.WrappedDialog.ProgressBarForeground = brush);
        }

        /// <summary>
        /// Begins an operation to close the ProgressDialog.
        /// </summary>
        /// <returns>A task representing the operation.</returns>
        public Task CloseAsync()
        {
            this.WrappedDialog.Invoke(() =>
                {
                    if (!this.WrappedDialog.IsVisible)
                    {
                        throw new InvalidOperationException("Dialog isn't visible to close");
                    }

                    this.WrappedDialog.Dispatcher.VerifyAccess();
                    this.WrappedDialog.KeyDown -= this.WrappedDialog_KeyDown;
                    this.WrappedDialog.PART_NegativeButton!.Click -= this.PART_NegativeButton_Click;

                    this.cancellationTokenRegistration.Dispose();
                });

            return this.CloseCallback()
                       .ContinueWith(_ => this.WrappedDialog.Invoke(() =>
                           {
                               this.IsOpen = false;
                               this.Closed?.Invoke(this, EventArgs.Empty);
                           }));
        }
    }
}

View on GitHub (pinned to 72099e310b)

Solutions

  1. Call CloseAsync exactly once; track an 'already closing' flag.
  2. Do not call CloseAsync if the cancellation token already closed it — let ShowProgressAsync's completion handle it.
  3. Guard the call: if (controller.IsOpen) await controller.CloseAsync(); — note IsOpen becomes false after close completes.
  4. Avoid wiring both the negative button and an explicit CloseAsync to the same dismiss path.

Example fix

// before
var controller = await this.ShowProgressAsync("...", "...");
// work done
cancellationToken.Cancel();
await controller.CloseAsync(); // may throw - cancel already closed it

// after
var controller = await this.ShowProgressAsync("...", "...");
// work done
if (controller.IsOpen)
{
    await controller.CloseAsync();
}
Defensive patterns

Strategy: validation

Validate before calling

// Check IsOpen before closing; it flips to false after a successful close.
if (controller.IsOpen)
{
    await controller.CloseAsync();
}

// Also avoid calling CloseAsync when a cancellation token already dismisses the dialog.

Try / catch

try { await controller.CloseAsync(); }
catch (InvalidOperationException ex) when (ex.Message == "Dialog isn't visible to close")
{
    // Already closed (e.g. via cancel button or token). Safe to ignore.
}

Prevention

When it happens

Trigger: Calling controller.CloseAsync() twice; calling CloseAsync after the negative/cancel button already dismissed the progress dialog; calling CloseAsync after the CancellationTokenSource triggered the dialog's internal close.

Common situations: A 'cancel' handler that calls both controller.CloseAsync() and cancels the token (which itself closes the dialog); awaiting ShowProgressAsync and then also calling CloseAsync; race between a work-task completion and an explicit close.

Related errors


AI-assisted analysis of MahApps/MahApps.Metro@72099e310b (2026-08-13). Data as JSON: /api/errors/0680b7c9aba45315. Report an issue: GitHub.