簡體   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