From wpf-dev-pack
Discovers Prism IDialogAware modal dialogs in FlaUI UI automation for WPF testing. Handles titleless windows, empty titles, progress dialogs; verifies IsCancel ESC keys and status messages.
npx claudepluginhub christian289/dotnet-with-claudecode --plugin wpf-dev-packThis skill uses the workspace's default tool permissions.
Prism's `IDialogAware` dialogs in WPF-UI applications often use custom window styles (`TitlelessWindow`, `CommonWindow`) that make standard FlaUI modal discovery unreliable. This skill covers patterns for finding and verifying these dialogs.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
Prism's IDialogAware dialogs in WPF-UI applications often use custom window styles (TitlelessWindow, CommonWindow) that make standard FlaUI modal discovery unreliable. This skill covers patterns for finding and verifying these dialogs.
Prism dialogs using TitlelessWindow style have no Title property. Standard modal search by title returns nothing.
Symptoms:
_mainWindow.ModalWindows returns windows but modal.Title is emptyFix — identify by child control type:
/// <summary>
/// Finds a Progress dialog by looking for a modal with a ProgressBar control.
/// TitlelessWindow style means Title is empty, so we identify by content.
/// </summary>
private AutomationElement? FindProgressDialog()
{
var modalWindows = _mainWindow.ModalWindows;
foreach (var modal in modalWindows)
{
var progressBar = modal.FindFirstDescendant(cf =>
cf.ByControlType(ControlType.ProgressBar));
if (progressBar is not null)
{
return modal;
}
}
return null;
}
For titled dialogs (CommonWindow with Title):
/// <summary>
/// Finds a modal dialog with a non-empty Title (e.g., warning, confirmation).
/// </summary>
private AutomationElement? FindTitledModal()
{
var modalWindows = _mainWindow.ModalWindows;
foreach (var modal in modalWindows)
{
if (!string.IsNullOrEmpty(modal.Title))
{
return modal;
}
}
return null;
}
General pattern — identify modal by unique child:
private AutomationElement? FindModalByChild(
Func<AutomationElement, bool> childMatcher)
{
foreach (var modal in _mainWindow.ModalWindows)
{
var descendants = modal.FindAllDescendants();
if (descendants.Any(childMatcher))
{
return modal;
}
}
return null;
}
// Usage: find modal containing a specific button text
var dialog = FindModalByChild(d =>
d.Name?.Contains("Cancel", StringComparison.OrdinalIgnoreCase) == true);
WPF buttons with IsCancel="True" close the dialog on ESC press. In Prism dialogs, this triggers IDialogAware.OnDialogClosed(). Testing this requires verifying both dialog closure and the application's response.
Pattern — ESC cancel with Status Bar verification:
// 1. Wait for dialog to appear
var dialog = RetryHelper.WaitForElement(
() => FindProgressDialog(),
TimeSpan.FromSeconds(10));
Assert.NotNull(dialog);
// 2. Press ESC (triggers IsCancel="True" button → CancelCommand)
Thread.Sleep(500); // Allow dialog to fully render
Keyboard.Press(VirtualKeyShort.ESCAPE);
// 3. Verify dialog closed
var closed = RetryHelper.WaitUntil(
() => FindProgressDialog() is null,
TimeSpan.FromSeconds(10));
Assert.True(closed, "ESC should close the dialog");
// 4. Verify cancellation via Status Bar message
// This distinguishes cancel from normal completion
var statusVerified = RetryHelper.WaitUntil(
() =>
{
var texts = _mainWindow.FindAllDescendants(cf =>
cf.ByControlType(ControlType.Text));
return texts.Any(t =>
t.Name?.Contains("canceled", StringComparison.OrdinalIgnoreCase) == true);
},
TimeSpan.FromSeconds(5));
Assert.True(statusVerified, "Status Bar should show 'canceled' message");
Why Status Bar verification matters:
VisionTaskCompleted(TaskEventReason.Canceled) event — unique to cancellationLoading test data via Ctrl+O requires interacting with the Windows file dialog, which is a separate process.
Pattern:
// Ctrl+O to open file dialog
DragHelper.ForceSetForeground(_mainWindow);
Thread.Sleep(300);
Keyboard.TypeSimultaneously(VirtualKeyShort.CONTROL, VirtualKeyShort.KEY_O);
Thread.Sleep(2000); // File dialog takes time to open
// Type file path and confirm
Keyboard.TypeSimultaneously(VirtualKeyShort.CONTROL, VirtualKeyShort.KEY_A);
Thread.Sleep(100);
Keyboard.Type(taskConfigPath);
Thread.Sleep(500);
Keyboard.Type(VirtualKeyShort.ENTER);
Thread.Sleep(3000); // Loading takes time
// Handle potential warning dialog (e.g., path validation)
var warningDialog = FindTitledModal();
if (warningDialog is not null)
{
Keyboard.Type(VirtualKeyShort.ENTER); // Dismiss warning
Thread.Sleep(500);
}
| Style | Title | How to Find |
|---|---|---|
CommonWindow (default) | Set via prism:Dialog.WindowStyle Setter | modal.Title |
TitlelessWindow | Empty | Child control type (ProgressBar, specific Button, etc.) |
CommonWindow with no Title Setter | Empty | Same as TitlelessWindow |
Thread.Sleep(2000) after Ctrl+O — Windows file dialog is cross-process, needs extra timeThread.Sleep(3000) after ENTER in file dialog — task config loading + FunctionBlock instantiationThread.Sleep(500) before ESC — allow dialog to fully render and receive keyboard focusRetryHelper.WaitUntil for state verification instead of fixed sleep