Обновление окна wpf сначала работает, затем останавливается

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

Первая проблема заключалась в том, что окно прогресса никогда не обновлялось (я думаю, это нормально в WPF). Поскольку в WPF отсутствует команда Refresh(), я исправил это вызовом Dispatcher.Invoke(). Теперь прогресс-бар некоторое время обновляется, потом окно перестает обновляться. Длительная работа в конце концов заканчивается, и окна возвращаются в нормальное состояние.

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

Но я был бы намного счастливее с подходом, который у меня есть здесь, за исключением того, что он перестает обновляться через некоторое время (например, в папке с 1000 файлами он может обновляться для 50-100 файлов, а затем «зависать») . Пользовательский интерфейс не должен реагировать во время этого действия, за исключением сообщения о ходе выполнения.

В любом случае, вот код. Сначала само окно прогресса:

public partial class ProgressWindow : Window
{
    public ProgressWindow(string title, string supertext, string subtext)
    {
        InitializeComponent();
        this.Title = title;
        this.SuperText.Text = supertext;
        this.SubText.Text = subtext;
    }

    internal void UpdateProgress(int count, int total)
    {
        this.ProgressBar.Maximum = Convert.ToDouble(total);
        this.ProgressBar.Value = Convert.ToDouble(count);
        this.SubText.Text = String.Format("{0} of {1} finished", count, total);
        this.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
    }

    private static Action EmptyDelegate = delegate() { };
}


<Window x:Class="Pixort.ProgressWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Pixort Progress" Height="128" Width="256" WindowStartupLocation="CenterOwner" WindowStyle="SingleBorderWindow" ResizeMode="NoResize">
    <DockPanel>
        <TextBlock DockPanel.Dock="Top" x:Name="SuperText" TextAlignment="Left" Padding="6"></TextBlock>
        <TextBlock DockPanel.Dock="Bottom" x:Name="SubText" TextAlignment="Right" Padding="6"></TextBlock>
        <ProgressBar x:Name="ProgressBar" Height="24" Margin="6"/>
    </DockPanel>
</Window>

Длительный метод (в Gallery.cs):

public void ImportFolder(string folderPath, Action<int, int> progressUpdate)
{
    string[] files = this.FileIO.GetFiles(folderPath);

    for (int i = 0; i < files.Length; i++)
    {
        // do stuff with the file
        if (null != progressUpdate)
        {
            progressUpdate.Invoke(i + 1, files.Length);
        }
    }
}

Который так и называется:

 ProgressWindow progress = new ProgressWindow("Import Folder Progress", String.Format("Importing {0}", folder), String.Empty);
 progress.Show();
 this.Gallery.ImportFolder(folder, ((c, t) => progress.UpdateProgress(c, t)));
 progress.Close();

person mlibby    schedule 24.03.2010    source источник


Ответы (4)


Mate выполняет простое программирование WPF с помощью DataBinding. См. шаблон проектирования MVVM, объясняющий то же самое.

Свяжите свойство значения индикатора выполнения с некоторым исходным свойством, определенным в классе DataContext. И обновите исходное свойство в вызываемом диспетчером методе.

Об остальном позаботится движок WPF.

В настоящее время вы написали код без какой-либо привязки...

person RockWorld    schedule 24.03.2010
comment
Вы знаете, где я могу найти рабочий пример этого? Я пробовал много разных вещей, чтобы заставить это работать без добавления слоев косвенности в код. Каждый раз бар вообще не обновлялся. - person mlibby; 24.03.2010

Если я правильно понимаю, вы сейчас выполняете всю свою работу в основном потоке. Это означает, что вы отнимаете (слишком много) времени у обычного Messagepump (Dispatcher).

Короткое исправление было бы аналогом Application.DoEvents() WinForm, но я не знаю, есть ли эквивалент WPF.

Лучшим решением было бы использовать Thread, а затем Backgroundworker — самый простой подход. Может быть, расширить этот вопрос события.

person Henk Holterman    schedule 24.03.2010
comment
Благодарю вас! Как видите, ваш ответ помог мне разобраться в проблеме. - person mlibby; 24.03.2010

Оказывается, это связано с DispatcherPriority в UpdateProgress. Замена DispatcherPriority.Render на что-то более низкое, в моем случае DispatcherPriority.Background помогло.

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

person mlibby    schedule 24.03.2010

Модифицированный код для выполнения ожидаемой операции. [Примечание: код XAML не изменен]


 public partial class ProgressWindow : Window
   {
      public ProgressWindow(string title, string supertext, string subtext)
      {
         InitializeComponent();
         EmptyDelegate = RaiseOnDispatcher;
        this.Title = title; 
        this.SuperText.Text = supertext; 
        this.SubText.Text = subtext; 
      }


    internal void UpdateProgress(int count, int total) 
    {
       this.Dispatcher.Invoke(EmptyDelegate,DispatcherPriority.Render,new object[]{count,total}); 
    }

    private static Action<int, int> EmptyDelegate = null;

    private void RaiseOnDispatcher(int count, int total)
    {
       this.ProgressBar.Maximum = Convert.ToDouble(total);
       this.ProgressBar.Value = Convert.ToDouble(count);
       this.SubText.Text = String.Format("{0} of {1} finished", count, total);
    }
   }


   public class Gallery
   {
      static Action<int, int> ActionDelegate=null;
      public static void ImportFolder(string folderPath, Action<int, int> progressUpdate)
      {
         ActionDelegate = progressUpdate;
         BackgroundWorker backgroundWorker = new BackgroundWorker();
         backgroundWorker.DoWork += new DoWorkEventHandler(worker_DoWork);
         backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_WorkCompleted);
         backgroundWorker.RunWorkerAsync(folderPath);
         backgroundWorker = null;
      }

     static void worker_DoWork(object sender, DoWorkEventArgs e)
      {
         string folderPath = e.Argument.ToString();
         DirectoryInfo dir = new DirectoryInfo(folderPath);
         FileInfo[] files = dir.GetFiles();

         for (int i = 0; i < files.Length; i++)
         {
            // do stuff with the file 
            Thread.Sleep(1000);// remove in actual implementation
            if (null != ActionDelegate)
            {
               ActionDelegate.Invoke(i + 1, files.Length);
            }
         }
      }
      static void worker_WorkCompleted(object sender, RunWorkerCompletedEventArgs e)
      {
         //do after work complete
      }

      public static void Operate()
      {
         string folder = "folderpath";
         ProgressWindow progress = new ProgressWindow("Import Folder Progress", String.Format("Importing {0}", folder), String.Empty);
         progress.Show();
         Gallery.ImportFolder(folder, ((c, t) => progress.UpdateProgress(c, t)));
         progress.Close();
      }


   }

person RockWorld    schedule 24.03.2010