WPF 在 Window.Closing 阶段调用 DialogHost.Show() 失效全解析

WPF 在 Window.Closing 阶段调用 DialogHost.Show() 失效全解析 🐞

当你在 Window.Closing 里弹 💬,却遇到 “Identifier not found” 或什么都不发生——99 % 不是 MaterialDesignInXamlToolkit 🐛,而是时机 没拿捏好。


🔁 场景复现

private async void Window_Closing(object? sender, CancelEventArgs e)
{
    var ok = await DialogHost.Show(new ConfirmExitView(), "RootDialog");
    if (!(ok is bool b && b)) e.Cancel = true;
}

运行后你会发现:🪟 直接 ❌,💬 不见踪影,甚至抛出 InvalidOperationException


💥 失败🧐根因

  1. 🌳👁️ 正在📦⬇️  Window.Closing 触发时,WPF 已开始把控件从视觉树移除。
  2. DialogHost 🚪 被🗑️  加载时会把自己放进静态📜;卸载时则移除。
  3. Show() 找不到🏠  调用时会按 Identifier 去📜里找 Host,自然一无所获。

换句话说:你要的🏠已经被系统合法🗑️。


🗺️ DialogHost 🔄时间线

Loaded ───┐
          │   (IsOpen=true)   ┌──── Show()
          ▼                  ▼
Visual Tree 🌳👁️ ──────┬───► 💬 展开
          ▲            │
          │            └──── DialogClosing
Unloaded ─┘                    ▲
                               │  (Window.Closing 🕙 在这里)
                               └──── 🌳👁️ 正在📦⬇️
  • Loaded → Unloaded:决定 DialogHost 是否仍在📜中。
  • Closing:只是 同步 通知,UI 线程会继续往下走,除非你手动⛔。

🛠️ 三套稳妥方案

🅰️ 先⛔❌,再异步弹💬(最常用)

private bool _exitConfirmed;

protected override async void OnClosing(CancelEventArgs e)
{
    if (_exitConfirmed) return;       // 二次进入直接放行

    e.Cancel = true;                  // ① 先踩刹车,保留🌳👁️

    var ok = await DialogHost.Show(   // ② 弹✅❓框
        new ConfirmExitView(),
        "RootDialog") is bool b && b;

    if (ok)
    {
        _exitConfirmed = true;        // ③ 用户✅ → 再❌🪟
        Close();
    }
}

⚠️ 关键点

  • e.Cancel = true 必须写在任何 await 之前。
  • 递归 Close() 前要打🛑或解绑事件,避免♾️循环。

🅱️ Dispatcher.BeginInvoke ⏩ 下一帧

private void Window_Closing(object? sender, CancelEventArgs e)
{
    e.Cancel = true;

    Dispatcher.BeginInvoke(async () =>
    {
        var ok = await DialogHost.Show(new ConfirmExitView(), "RootDialog")
                 is bool b && b;
        if (ok)
        {
            Closing -= Window_Closing; // 🔄解绑
            Close();
        }
    }, DispatcherPriority.Background);
}

Show() 推到 UI 消息队列⏩,确保 Host 仍在。


🅾️ 独立🪟 承载“全局” DialogHost

public static async Task<bool> ShowGlobalConfirmAsync()
{
    var win = new Window
    {
        WindowStyle = WindowStyle.None,
        AllowsTransparency = true,
        ShowInTaskbar = false,
        Content = new materialDesign:DialogHost
        {
            Identifier = "GlobalHost",
            Content    = new ConfirmExitView()
        }
    };

    win.Show();                               // 注册🏠
    var ok = await DialogHost.Show(null, "GlobalHost") is bool b && b;
    win.Close();
    return ok;
}

维护成本📈,但可完全绕开主🪟 生命周期。


🧭 常见误区速查

⚠️误区🚨现象🛠️修正
e.Cancel 写在 await 之后仍抛 Identifier not found提前⛔❌
忘记解绑 Closing 事件🪟 无法❌ / ♾️弹框Closing -= handler 或布尔标记
同🪟多个同名 Identifier💬 无响应或抛异常保证全球唯一🌍
老旧 MaterialDesign 包关闭动画异常 / 泄漏升级📦 最新版

❓FAQ

Q: 关闭按钮 🔘 和 Alt+F4 都能触发 Window.Closing 吗?

A: ✅,只要最终走到 Close(),都会触发 Closing,因此上述方案通用。

Q: 能不能在 DialogHost.DialogClosing 里再 Show()

A: 可以,但要用 Dispatcher.BeginInvoke ⏩,或 e.Cancel(); e.Session.UpdateContent(...) 直接替换内容。


🎯 结语

DialogHost 并不“脾气大”😠,只是对 生命周期⏳ 要求严。牢记:先保住🌳👁️,再弹💬。按本文任一🛠️落地,再也不会遇到 Identifier not found 😅。

主题测试文章,只做测试使用。发布者:admin,转转请注明出处:http://onebyone.icu/2025/04/10/wpf-%e5%9c%a8-window-closing-%e9%98%b6%e6%ae%b5%e8%b0%83%e7%94%a8-dialoghost-show-%e5%a4%b1%e6%95%88%e5%85%a8%e8%a7%a3%e6%9e%90/

Like (0)
Previous 2025年3月31日 上午10:38
Next 2024年9月27日 下午6:08

相关推荐

发表回复

Please Login to Comment