2009-03-05 3 views
7

Я пытаюсь отобразить диалоговое окно wait wait для продолжительной работы. Проблема в том, что это однопоточно, хотя я говорю, что WaitScreen для отображения его никогда не делает. Есть ли способ изменить видимость этого экрана и немедленно отобразить его? В качестве примера я включил вызов курсора. Сразу после вызова this.Cursor курсор обновляется немедленно. Это именно то поведение, которое я хочу.Экран «Подождите» в WPF

private void Button_Click(object sender, RoutedEventArgs e) 
{ 
    this.Cursor = System.Windows.Input.Cursors.Pen; 
    WaitScreen.Visibility = Visibility.Visible; 

    // Do something long here 
    for (Int32 i = 0; i < 100000000; i++) 
    { 
    String s = i.ToString(); 
    } 

    WaitScreen.Visibility = Visibility.Collapsed; 
    this.Cursor = System.Windows.Input.Cursors.Arrow; 
} 

WaitScreen - это просто сетка с индексом Z 99, которую я скрываю и показываю.

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

+0

Я был таким же, когда пытался сделать это однопоточным. Думая, просто измените курсор. Но это действительно не сработало. – Ray

ответ

15

Я нашел способ! Благодаря this thread.

public static void ForceUIToUpdate() 
{ 
    DispatcherFrame frame = new DispatcherFrame(); 

    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Render, new DispatcherOperationCallback(delegate(object parameter) 
    { 
    frame.Continue = false; 
    return null; 
    }), null); 

    Dispatcher.PushFrame(frame); 
} 

Эта функция должна быть вызвана непосредственно перед длительной работой. Затем будет принудительно обновлять поток пользовательского интерфейса.

17

Выполнение этого однопоточного действительно будет больно, и оно никогда не будет работать так, как вам хотелось бы. Окно в конечном итоге станет черным в WPF, и программа изменится на «Не реагировать».

Я бы рекомендовал использовать BackgroundWorker для выполнения вашей долгосрочной задачи.

Это не так уж сложно. Что-то вроде этого будет работать.

private void DoWork(object sender, DoWorkEventArgs e) 
{ 
    //Do the long running process 
} 

private void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
{ 
    //Hide your wait dialog 
} 

private void StartWork() 
{ 
    //Show your wait dialog 
    BackgroundWorker worker = new BackgroundWorker(); 
    worker.DoWork += DoWork; 
    worker.RunWorkerCompleted += WorkerCompleted; 
    worker.RunWorkerAsync(); 
} 

Вы можете посмотреть на события ProgressChanged для отображения прогресса, если вам нравится (не забудьте установить WorkerReportsProgress истина). Вы также можете передать параметр RunWorkerAsync, если вашим методам DoWork нужен объект (доступно в e.Argument).

Это действительно самый простой способ, вместо того, чтобы пытаться сделать его выделенным.

+0

Спасибо за ответ. Я нашел способ сделать это в потоке пользовательского интерфейса (см. Ответ). В идеальном мире BackgroundWorker, безусловно, путь. На данный момент у меня просто нет возможности изменить этот код. –

3

Другой вариант написать затянувшийся рутину как функция, которая возвращает IEnumerable<double>, указывающий на прогресс, и просто сказать:

yield return 30; 

Это будет означать 30% пути через, например. Затем вы можете использовать таймер WPF для его выполнения в «фоновом режиме» в качестве сопроводительной сопрограммы.

It's described in some detail here, with sample code.

4

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

Option # 1 Execute кода для отображения сообщения ожидания синхронно в том же методе, который делает реальную задачу , Просто поместите эту строку перед длительным процессом:

Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal, (Action)(() => { /* Your code to display a waiting message */ })); 

Он будет обрабатывать ожидающие сообщения на главной диспетчерской нити в конце Invoke().

Примечание: Причина выбора приложения.Current.Dispatcher, но диспетчер.CurrentDispatcher объясняется here.

Вариант № 2 Отобразите экран «Подождите» и обновите пользовательский интерфейс (ожидающие обработки сообщения).

Для этого WinForms Разработчик выполнил метод Application.DoEvents. WPF предлагает две альтернативы для достижения аналогичных результатов:

Вариант № 2.1 С использованием DispatcherFrame class.

Проверить немного громоздкий пример из MSDN:

[SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)] 
public void DoEvents() 
{ 
    DispatcherFrame frame = new DispatcherFrame(); 
    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), frame); 
    Dispatcher.PushFrame(frame); 
} 

public object ExitFrame(object f) 
{ 
    ((DispatcherFrame)f).Continue = false; 
    return null; 
} 

Вариант № 2,2 запустит пустое действие

Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, (Action)(() => { })); 

См дискуссии, которые один (2,1 или 2,2) лучше here. Вариант IMHO №1 по-прежнему лучше, чем # 2.

Вариант № 3 Отображение ожидающего сообщения в отдельном окне.

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

Скачать WpfLoadingOverlay.zip из this github (это был образец из статьи «WPF Реагирования: асинхронная загрузка анимация во время рендеринга», но я не могу найти его в Интернете больше) или иметь взгляд на основная идея ниже:

public partial class LoadingOverlayWindow : Window 
{ 
    /// <summary> 
    ///  Launches a loading window in its own UI thread and positions it over <c>overlayedElement</c>. 
    /// </summary> 
    /// <param name="overlayedElement"> An element for overlaying by the waiting form/message </param> 
    /// <returns> A reference to the created window </returns> 
    public static LoadingOverlayWindow CreateAsync(FrameworkElement overlayedElement) 
    { 
     // Get the coordinates where the loading overlay should be shown 
     var locationFromScreen = overlayedElement.PointToScreen(new Point(0, 0)); 

     // Launch window in its own thread with a specific size and position 
     var windowThread = new Thread(() => 
      { 
       var window = new LoadingOverlayWindow 
        { 
         Left = locationFromScreen.X, 
         Top = locationFromScreen.Y, 
         Width = overlayedElement.ActualWidth, 
         Height = overlayedElement.ActualHeight 
        }; 
       window.Show(); 
       window.Closed += window.OnWindowClosed; 
       Dispatcher.Run(); 
      }); 
     windowThread.SetApartmentState(ApartmentState.STA); 
     windowThread.Start(); 

     // Wait until the new thread has created the window 
     while (windowLauncher.Window == null) {} 

     // The window has been created, so return a reference to it 
     return windowLauncher.Window; 
    } 

    public LoadingOverlayWindow() 
    { 
     InitializeComponent(); 
    } 

    private void OnWindowClosed(object sender, EventArgs args) 
    { 
     Dispatcher.InvokeShutdown(); 
    } 
} 
Смежные вопросы