lepoco/wpfui · error · InvalidOperationException
IsFocusInsideDialog can only be called from the UI thread.
Error message
IsFocusInsideDialog can only be called from the UI thread.
What it means
Thrown by ContentDialog.IsFocusInsideDialog when the call is made on a thread other than the UI (dispatcher) thread and the dispatcher is still running. The method touches Keyboard.FocusedElement, which has UI-thread affinity, so cross-thread access is explicitly rejected.
Source
Thrown at src/Wpf.Ui/Controls/ContentDialog/ContentDialog.FocusBehavior.cs:56
/// <summary>
/// Returns <see langword="true"/> when the keyboard focus is currently within the dialog's visual/logical tree.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown when the method is called from a non-UI thread.
/// </exception>
public bool IsFocusInsideDialog()
{
if (Dispatcher is not { HasShutdownStarted: false, HasShutdownFinished: false })
{
return false;
}
if (Dispatcher.CheckAccess())
{
return IsFocusInsideDialogCore(Keyboard.FocusedElement);
}
throw new InvalidOperationException("IsFocusInsideDialog can only be called from the UI thread.");
}
/// <summary>
/// Completely prevents focus from escaping the ContentDialog. When a focus escape is detected,
/// the focus is forcibly pulled back into the dialog.
/// </summary>
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
if (_suppressFocusRestore)
{
return;
}
var window = Window.GetWindow(this);
if (e.NewFocus == null || window is not { IsActive: true })
{
return;
}View on GitHub (pinned to ffebacd610)
Solutions
- Marshal the call onto the dispatcher: await using Dispatcher.InvokeAsync(() => dialog.IsFocusInsideDialog()).
- Ensure any focus logic runs in a UI-thread context (e.g. inside an event handler or a command bound to UI controls).
- Before calling, gate on dialog.Dispatcher.CheckAccess() to avoid the throw and switch to Invoke when needed.
Example fix
// before bool inside = dialog.IsFocusInsideDialog(); // called from background thread // after bool inside = await dialog.Dispatcher.InvokeAsync(() => dialog.IsFocusInsideDialog());
Defensive patterns
Strategy: validation
Validate before calling
if (dialog.Dispatcher.CheckAccess()) { return dialog.IsFocusInsideDialog(); }
return dialog.Dispatcher.Invoke(() => dialog.IsFocusInsideDialog()); Type guard
static bool OnUiThread(System.Windows.Threading.Dispatcher d) => d.CheckAccess();
Prevention
- Always marshal focus logic onto the UI dispatcher.
- Run focus checks inside UI-thread-bound event handlers or commands.
- Gate on Dispatcher.CheckAccess() before calling thread-affine members.
When it happens
Trigger: Invoking IsFocusInsideDialog() from a background/task thread while the dispatcher is alive (HasShutdownStarted/HasShutdownFinished false) and Dispatcher.CheckAccess() returns false (current thread does not own the dispatcher).
Common situations: Calling focus-related logic from an async continuation that did not marshal back to the UI thread, from a timer/background worker, or from a VM command running off-dispatcher. Dispatcher.CheckAccess returning false is the direct trigger.
Related errors
- Cannot set ContentPresenter: a ContentDialogHost host has al
- Cannot set ContentDialogHost: a legacy ContentPresenter host
- The DialogHost was never set.
- The DialogHost is not the same as the one that was previousl
- Cannot set DialogHost when DialogHostEx is already set.
AI-assisted analysis of lepoco/wpfui@ffebacd610 (2026-08-13).
Data as JSON: /api/errors/d0dc86600570fda8.
Report an issue: GitHub.