Приложение WPF закрывается сразу, когда отображается диалоговое окно перед запуском - PullRequest
21 голосов
/ 13 сентября 2010

Обновление : Полагаю, мне нужно понять, каков "правильный", "поддерживаемый" способ отображения диалога перед запуском приложения в 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, что, естественно, приводит к немедленному закрытию приложения.

Учитывая этот анализ, существует очевидный обходной путь: создайте MainWindow сразу после App, что делает его app MainWindow, что предотвратит DialogWindow закрытие приложения.

Однако , вот что меня беспокоит.

Во-первых, это выглядит как грязный хак для меня. Я имею в виду, что нет явной причины для создания окон в этом порядке, и я нашел это только путем некоторой отладки. Это не может быть поддерживаемым способом.

Во-вторых, это явно ошибка. Я имею в виду, что если создание второго окна после завершения работы не поддерживалось явно, оно должно было выдать InvalidOperationException, верно?

В-третьих, это не только ошибка, но и очень наивная, что-то похожее на многопоточность.

Все это заставляет меня поверить , что, может быть, у меня здесь нет ничего фундаментального? Может быть, я не имею смысла вообще? Может быть, все это должно быть сделано по-другому?

Вот немного фона:
Приложение должно выполнить некоторую загрузку при запуске. Проверьте это и то, настройте обработчики исключений, ведение журнала - вы знаете, обычные вещи. В этом процессе может возникнуть необходимость обратиться к пользователю за помощью - для этого и нужен диалог.

Я абсолютно не хочу помещать все это в какой-то конечный автомат, который выполняется на MainWindow.IsVisibleChanged или что-то в этом роде. Мне бы хотелось, чтобы это было действительно просто, кратко и просто - так, как должен быть представлен код начальной загрузки, чтобы легко было обнаружить ошибки невооруженным глазом.

Ответы [ 5 ]

32 голосов
/ 19 января 2011

По умолчанию ShutdownMode приложения WPF - 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.
}

EDIT: Я не уверен, что именно ты делаешь. Код, который вы дали, не скомпилируется, поскольку при правильном использовании класса приложения 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 теперь не пуста, после ее запуска начнется отключение.
  4. Пользователь закрывает диалоговое окно. Как часть закрытия, окно удаляет себя из Application.Current.Windows. Кроме того, поскольку это MainWindow, для него установлено значение null. Поскольку Application.Current.Windows теперь пуста, начинается отключение. Однако, поскольку не работает ни один диспетчерский цикл, пока ничего не сделано (установлен только внутренний флаг или аналог).
    • Если бы вы использовали app.Run(new DialogWindow()); app.Run(new MainWindow());, у вас было бы исключение при создании MainWindow, поскольку в этом случае цикл диспетчера работает правильно. Таким образом, он может фактически отключиться, поэтому при создании MainWindow он вызывает исключение, поскольку цикл диспетчера уже отключен.
  5. Основное окно создано. Как и выше, он добавляет себя в Application.Current.Windows и устанавливает себя как Application.Current.MainWindow.
    • Однако условие для закрытия приложения уже достигнуто. Но до сих пор у приложения не было возможности что-то сделать.
  6. Теперь Run () вызывается. Диспетчерская петля запускается снова и теперь имеет возможность завершить работу приложения. Поэтому он закрывает приложение и закрывает все открытые окна.

Итак, снова без ошибок.

Так что один из способов решить эту проблему - перейти на OnExplicitShutdown.Затем на шаге 4 причина прекращения работы не достигается.Лучше (как в обычном приложении WPF) было бы иметь правильное ApplicationDefinition.Удалите StartupUri из App.xaml и вместо этого обработайте событие 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, у нас снова есть окно как главного окна и как одно из окон приложения.Тогда мы можем перейти в режим выключения, который нам нужен, и показать главное окно.

2 голосов
/ 13 сентября 2010

Это кажется глючным.

Я обычно ничего не помещаю в 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() во втором потоке, вам нужно убедиться, что вы получите ответ, прежде чем показывать главное окно. У него есть преимущество в том, что вы можете запускать другие задачи начальной загрузки, ожидая пользовательского ввода в определенной части; это действительно зависит от того, насколько последовательны ваши различные задачи.

Может быть, это поможет в вашем случае; может нет - просто мысль.

1 голос
/ 19 января 2012

Очень информативный пост, спасибо всем, кто внес вклад.Я использовал плацебо-окно, установив его в главное окно, но не показывало его.Я также установил режим выключения на 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()
0 голосов
/ 23 мая 2018

У меня была похожая проблема. Я провел много времени и не мог решить это. Я был вне времени.

Я не знаю, правильное это решение или нет, но я просто спрятал диалог входа в систему вместо того, чтобы закрывать его. Он все еще работает

0 голосов
/ 15 сентября 2010

Если вы установите Application.ShutdownMode на OnExplicitShutdown, можете ли вы избежать включения ShutdownCallback в очередь диспетчера и продолжать делать все, что хотите, независимо от окон?Я не проверял это, но похоже на потенциальное решение, а также использование свойства Application.MainWindow , которое можно изменить на лету.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...