2013-08-15 2 views
8

Приносим извинения, если на этот вопрос ответили много раз, но я не могу найти ответ, который работает на меня. Я хотел бы создать модальное окно, которое показывает различные сообщения о ходе выполнения, в то время как мое приложение выполняет длительные задачи. Эти задачи выполняются в отдельном потоке, и я могу обновить текст в окне выполнения на разных этапах процесса. Связь между потоками работает хорошо. Проблема в том, что я не могу заставить окно быть поверх других окон приложений (не для каждого приложения на компьютере), оставаться на вершине, предотвращать взаимодействие с родительским окном и до сих пор разрешать продолжить работу.WPF modal progress window

Вот что я пытался до сих пор:

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

В простейшем случае, я создаю экземпляр окна и вызвать .Show() на нем:

//from inside my secondary thread 
this._splash.Dispatcher.Invoke(new Action(() => this._splash.Show()); 

//Do things 
//update splash text 
//Do more things 

//close the splash when done 
this._splash.Dispatcher.Invoke(new Action(() => this._splash.Hide()); 

Это правильно отображает окно и продолжает работать мой код для обработки задачи инициализации, но это позволяет мне нажать на родительское окно и довести это до фронта.

Далее я попытался отключить главное окно и повторного включения позже:

Application.Current.Dispatcher.Invoke(new Action(() => this.MainWindow.IsEnabled = false)); 

//show splash, do things, etc 

Application.Current.Dispatcher.Invoke(new Action(() => this.MainWindow.IsEnabled = true)); 

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

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

Тогда я нашел сообщения о том, как .ShowDialog() вместо .Show(). Я попробовал это, и он правильно показал диалог и не разрешил мне щелкнуть по родительскому окну, но вызов .ShowDialog() заставляет программу зависать, пока вы не закроете диалоговое окно, прежде чем он продолжит выполнение кода. Это, очевидно, не то, что я хочу. Я полагаю, я мог бы позвонить ShowDialog() в другой поток, чтобы этот поток зависал, но поток, выполняющий работу, не ... это рекомендуемый метод?

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

Так что это подводит меня к вопросу. Каков правильный способ справиться с этим?

Спасибо за ваше время и извините за длинный вопрос, но детали очень важны :)

ответ

1

Вы можете иметь конструктор вашего окна ПРОГРЕССА принимает в Task, а затем убедитесь, что окно вызывает task.Start на OnLoaded событии. Затем вы используете ShowDialog из родительской формы, что приведет к тому, что окно прогресса запустит задачу.

Обратите внимание, что вы также можете вызвать task.Start в конструкторе или в родительской форме где-либо перед вызовом ShowDialog. Какой бы ни был смысл для вас.

Другой вариант - просто использовать индикатор выполнения в полосе состояния главного окна и избавиться от всплывающего окна. В наши дни этот вариант становится все более распространенным явлением.

1

Вы можете использовать свойство Visibility на Window, чтобы скрыть все окно во время запуска заставки.

XAML

<Window ... Name="window" /> 

Код

window.Visibility = System.Windows.Visibility.Hidden; 

//show splash 
//do work 
//end splash 

window.Visibility = System.Windows.Visibility.Visible; 
+0

Я не хотел скрывать, я просто хотел, чтобы это «отключено», а диалоговое окно вверх. Модальность, созданная при вызове «ShowDialog», идеальна, но мне нужно было не останавливать выполнение. – BrianVPS

-1

Я нашел способ, чтобы сделать эту работу, вызвав ShowDialog() в отдельном потоке. Я создал свои собственные методы ShowMe() и HideMe() в моем классе диалога, которые обрабатывают работу. Я также фиксирую событие Closing, чтобы предотвратить закрытие диалогового окна, чтобы я мог его повторно использовать.

Вот мой код для моего выплеска класса экрана:

public partial class StartupSplash : Window 
{ 
    private Thread _showHideThread; 

    public StartupSplash() 
    { 
     InitializeComponent(); 

     this.Closing += OnCloseDialog; 
    } 


    public string Message 
    { 
     get 
     { 
      return this.lb_progress.Content.ToString(); 
     } 
     set 
     { 
      if (Application.Current.Dispatcher.Thread == System.Threading.Thread.CurrentThread) 
       this.lb_progress.Content = value; 
      else 
       this.lb_progress.Dispatcher.Invoke(new Action(() => this.lb_progress.Content = value)); 
     } 
    } 


    public void ShowMe() 
    { 
     _showHideThread = new Thread(new ParameterizedThreadStart(doShowHideDialog)); 
     _showHideThread.Start(true); 
    } 

    public void HideMe() 
    { 
     //_showHideThread.Start(false); 
     this.doShowHideDialog(false); 
    } 

    private void doShowHideDialog(object param) 
    { 
     bool show = (bool)param; 

     if (show) 
     { 
      if (Application.Current.Dispatcher.Thread == System.Threading.Thread.CurrentThread) 
       this.ShowDialog(); 
      else 
       Application.Current.Dispatcher.Invoke(new Action(() => this.ShowDialog())); 
     } 
     else 
     { 
      if (Application.Current.Dispatcher.Thread == System.Threading.Thread.CurrentThread) 
       this.Close(); 
      else 
       Application.Current.Dispatcher.Invoke(new Action(() => this.Close())); 
     } 
    } 


    private void OnCloseDialog(object sender, CancelEventArgs e) 
    { 
     e.Cancel = true; 
     this.Hide(); 
    } 
} 
+1

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

+0

Несколько потоков пользовательского интерфейса * очень * трудны в работе и могут вызвать много тонких и трудных для понимания или устранения проблем из-за основополагающих допущений, сделанных во всех существующих инфраструктурах пользовательского интерфейса, что существует только один поток пользовательского интерфейса. – Servy

+0

Я бы обычно соглашался с тем, что несколько потоков пользовательского интерфейса сложны, но в этом случае SplashScreen инкапсулирует всю логику вторичного потока прямо внутри. Все, что мне нужно сделать, это вызвать ShowMe и установить свойство Message для изменения текста в диалоговом окне. Вызывающий не должен знать дополнительные потоки. С этим очень легко работать, и я думаю, что все будет хорошо.Я обязательно буду исследовать другие ответы здесь, но сейчас я должен двигаться дальше, поскольку мой крайний срок был на прошлой неделе :( – BrianVPS

13

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

У него есть проблема, что, как только вы ее назовете, вы блокируете выполнение. Как вы могли бы запустить некоторый код после показа формы, но определить, что она должна быть до того, как она будет показана? Это твоя проблема.

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

Что вы можете сделать, это использовать событие LoadedWindow, чтобы определить код, который должен запускаться после отображения окна, но где он определен до его отображения.

public static void DoWorkWithModal(Action<IProgress<string>> work) 
{ 
    SplashWindow splash = new SplashWindow(); 

    splash.Loaded += (_, args) => 
    { 
     BackgroundWorker worker = new BackgroundWorker(); 

     Progress<string> progress = new Progress<string>(
      data => splash.Text = data); 

     worker.DoWork += (s, workerArgs) => work(progress); 

     worker.RunWorkerCompleted += 
      (s, workerArgs) => splash.Close(); 

     worker.RunWorkerAsync(); 
    }; 

    splash.ShowDialog(); 
} 

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

Это может тогда назвать что-то вроде этого:

public void Foo() 
{ 
    DoWorkWithModal(progress => 
    { 
     Thread.Sleep(5000);//placeholder for real work; 
     progress.Report("Finished First Task"); 

     Thread.Sleep(5000);//placeholder for real work; 
     progress.Report("Finished Second Task"); 

     Thread.Sleep(5000);//placeholder for real work; 
     progress.Report("Finished Third Task"); 
    }); 
} 
+0

+1, но обратите внимание, что вы можете сделать функцию 'void Foo (Action > work)', которая инкапсулирует все объекты, не относящиеся к задаче, чтобы их можно было повторно использовать для различных задач, где вы устанавливаете «рабочий .DoWork + = delegate {work (Ред .Properport);}; '. Таким образом, он является развязанным и многоразовым. –

+0

@DaxFohl Если бы я собирался зайти так далеко, я бы, вероятно, переместил объект' IProgress ' вместо «Действие '; это поможет лучше переносить семантику. – Servy

+0

@DaxFohl Пример отредактирован. – Servy