繁体   English   中英

启动前显示对话框时,WPF应用程序立即退出

[英]WPF application exits immediately when showing a dialog before startup

更新我想,我需要了解在WPF中应用程序启动之前显示对话框的“正确”,“支持”方式是什么。

这是代码:

    public partial class App : Application
    {
        [STAThread]
        public static void Main()
        {
            var app = new App();
            app.InitializeComponent();

            new DialogWindow().ShowDialog();

            app.Run( new MainWindow() );
        }
    }

DialogWindow按预期显示。
但关闭后,应用程序立即退出。 MainWindow根本不显示!

我做了一些调试并将问题追溯到:

  1. 创建对话框后,它将成为app的MainWindow,因为此时没有MainWindow。
  2. 因此,关闭对话框会导致应用程序在调度程序队列上发布ShutdownCallback
  3. 但是,调度程序运行时间不足以执行回调。
  4. 因此,一旦随后调用app.Run ,队列中的第一件事就是ShutdownCallback ,这自然会导致应用程序立即关闭。

鉴于此分析,有一个明显的解决方法:在App之后创建MainWindow ,从而使其成为app的MainWindow,这将阻止DialogWindow导致应用程序关闭。

然而 ,这是困扰我的。

首先,这看起来像是一个肮脏的黑客。 我的意思是,没有明确的理由按此顺序创建窗口,我只通过一些调试找到了这个。 这不是受支持的方式。

其次,这显然是一个错误。 我的意思是,如果显式不支持在关闭后创建第二个窗口,它应该抛出一些InvalidOperationException ,对吧?

第三,这不仅是一个错误,而且看起来像一个非常天真的错误,就像多线程初学者会创造的那样。

所有这些让我相信 ,也许我没有得到一些基本的东西? 也许我根本没有意义? 也许这一切都应该以某种不同的方式完成?

这是一些背景知识:
应用程序必须在启动时进行一些引导。 检查这一点,设置异常处理程序,记录 - 你知道,通常的东西。 在此过程中,可能需要向用户请求帮助 - 这是对话框的用途。

我绝对不想把所有这些都放在MainWindow.IsVisibleChanged上执行的某种状态机上。 我想保持它非常简单,简洁和直接 - 引导代码应该是这样的方式,这样就可以很容易地用肉眼发现错误。

默认情况下,WPF应用程序的ShutdownMode是OnLastWindowClose。 在您的代码中,您将显示一个窗口,然后将其关闭。 因此,最后一个窗口关闭,应用程序关闭。 然后在关闭时,显示另一个窗口。 由于应用程序正在关闭,窗口立即关闭。

所以一切都按照您的设计和编程工作。

但是,您想要做一些不同的事情:首先显示为唯一窗口的窗口应该是一个“特殊窗口”,关闭后您想要继续执行,显示“主窗口”然后退出应用程序一次它(或与应用程序关联的所有窗口)关闭。

最简单的方法:首先将关闭模式设置为OnExplicitShutdown,然后在显示主窗口后将其设置为OnLastWindowClose或OnMainWindowClose。 在代码中:

public static void Main()
{
    var app = new App();
    app.InitializeComponent();

    app.ShutdownMode = ShutdownMode.OnExplicitShutdown;
    new DialogWindow().ShowDialog();

    var mainWindow = new MainWindow();
    app.MainWindow = mainWindow;
    app.Run(mainWindow);
    // When the window has loaded, it should then set the app.ShutdownMode to what you actually want.
}

编辑:我不确定你到底在做什么。 您提供的代码将无法编译,因为在正确使用WPF应用程序类(使用App.xaml构建操作作为ApplicationDefinition)时,已经定义了Main方法。 如果您只有一个派生自Application的类,则没有InitializeComponent()方法。 让代码编译的唯一方法是手动将构建操作更改为Page。 但是,在这种情况下,Application.Current == app。

那么会发生以下情况:

  1. 应用程序启动。 由于到目前为止尚未创建WPF应用程序,因此Application.Current为null。 这也意味着没有调度程序循环正在运行且调度程序消息未处理(请注意,调度程序循环也处理Windows消息)。
  2. 创建一个新的App对象。 由于Application.Current为null,因此它将自身设置为Application.Current。
    • Application.Current.MainWindow为null,Application.Current.Windows是一个空列表。
    • 由于ShutdownMode是OnLastWindowClose,一旦当前应用程序(即应用程序)的最后一个窗口关闭,就会启动关闭。
  3. DialogBox以模态显示。 由于没有运行调度程序循环,ShowDialog()本身运行“本地”调度程序循环。
    • 实际上这是两部分:首先创建窗口。 它属于当前应用程序,因此它将自己添加到Application.Current.Windows。 由于它是显示的第一个窗口,Application.Current.MainWindow为null,因此它也将自身设置为主窗口。 其次,窗口以模态显示。
    • 由于Application.Current.Windows现在是非空的,一旦它为空,就会启动shutdown。
  4. 用户关闭对话窗口。 作为关闭的一部分,窗口将自己从Application.Current.Windows中删除。 此外,由于它是MainWindow,因此将其设置为null。 由于Application.Current.Windows现在为空,因此关闭开始。 但是,由于没有调度程序循环运行,因此尚未执行任何操作(仅设置内部标志或类似内容)。
    • 如果您使用过app.Run(new DialogWindow()); app.Run(new MainWindow()); app.Run(new DialogWindow()); app.Run(new MainWindow()); ,在创建MainWindow时会出现异常,因为在这种情况下,调度程序循环正常运行。 因此,它实际上可以自行关闭,因此在创建MainWindow时,它会抛出异常,因为调度程序循环已经关闭。
  5. MainWindow已创建。 如上所述,它将自己添加到Application.Current.Windows并将其自身设置为Application.Current.MainWindow。
    • 但是,已经达到了关闭应用程序的条件。 但是,到目前为止,应用程序没有机会做某事。
  6. 现在调用Run()。 调度程序循环再次启动,现在有机会关闭应用程序。 因此它会关闭应用程序并关闭所有打开的窗口。

所以再一次,没有错误。

因此,解决此问题的一种方法是更改​​为OnExplicitShutdown。 然后在步骤4中,没有达到关闭的理由。 更好(正如更像普通的WPF应用程序)将具有适当的ApplicationDefinition。 从App.xaml中删除StartupUri,而不是处理Startup事件:

private void OnStartup(object sender, StartupEventArgs e)
{
    this.ShutdownMode = ShutdownMode.OnExplicitShutdown;
    new DialogWindow().ShowDialog();

    var mainWindow = new MainWindow();
    this.ShutdownMode = ShutdownMode.OnLastWindowClose; // or OnMainWindowClose
    mainWindow.Show();
}

由于在关闭对话框窗口时我们有OnExplicitShudown,因此应用程序没有理由在此时开始关闭。 然后,在创建MainWindow之后,我们再次将窗口作为主窗口和(一个)应用程序窗口。 那么我们就可以切换到我们想要的关机模式并显示主窗口。

它似乎有点儿。

我通常不在Main()放任何东西,我让no-arg app.Run()被调用并在OnStartup方法中调用我需要的所有内容,但这不会改变你所看到的行为。

每当我需要在我的主应用程序窗口准备好显示之前显示某些内容时,例如收集输入或显示启动画面,我会在第二个线程上执行此操作。

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);

    // show splash
    var thread = new Thread(() =>
    {
        Dispatcher.CurrentDispatcher.BeginInvoke
            ((Action)(() => new MySplashWindow().Show()));
        Dispatcher.Run();
    }
    thread.SetApartmentState(ApartmentState.STA);
    thread.IsBackground = true;
    thread.Start();

    // run configuration steps

    // instantiate and show main window
}

显然,如果你在第二个线程上调用ShowDialog() ,你需要确保在显示主窗口之前得到答案。 它的优势在于,您可以在等待特定部分的用户输入时运行其他引导任务; 这实际上取决于你的各种任务的顺序。

也许这对你的情况有帮助; 也许不是 - 只是一个想法。

如果将Application.ShutdownMode设置为OnExplicitShutdown ,您是否可以避免将ShutdownCallback置于调度程序队列中并继续执行您想要的操作而不考虑Windows? 我没有测试过这个,但它似乎是一个潜在的解决方案,以及使用Application.MainWindow属性,可以动态更改。

非常翔实的帖子,感谢所有贡献。 我使用安慰剂窗口将其设置为主窗口但未显示。 我还将关闭模式设置为OnLastWindow在打开和关闭所有设置对话框之后,我用实际主窗口替换了安慰剂窗口并调用了App.Run()。 这可能不是最佳实践,但它有效且工作速度快。

Application app = new App();

MainWindow y = new MainWindow();
app.MainWindow = y;
y.WindowStartupLocation = WindowStartupLocation.CenterScreen;
app.ShutdownMode = ShutdownMode.OnLastWindowClose;
//do lots of setup work to include authentication
MainWindow x = new MainWindow(containerdata)
app.MainWindow = x;
App.Run()

我有类似的问题。 我花了很多时间而无法解决它。 我没时间了。

我不知道这是一个正确的解决方案,但我只是隐藏了登录对话框而不是关闭它。 它仍然有效

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM