Обновление окна wpf сначала работает, затем останавливается - PullRequest
2 голосов
/ 24 марта 2010

У меня есть процедура, которая собирает список всех изображений в каталоге, а затем запускает дайджест 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();

Ответы [ 4 ]

2 голосов
/ 24 марта 2010

Оказывается, это связано с DispatcherPriority в UpdateProgress. Изменение DispatcherPriority.Render на что-то более низкое, в моем случае DispatcherPriority.Background добилось цели.

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

1 голос
/ 24 марта 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&ltint, int&gt 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&ltint, int&gt ActionDelegate=null;
      public static void ImportFolder(string folderPath, Action&ltint, int&gt 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 &lt 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) =&gt progress.UpdateProgress(c, t)));
         progress.Close();
      }


   }

1 голос
/ 24 марта 2010

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

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

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

1 голос
/ 24 марта 2010

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

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

Двигатель WPF позаботится об отдыхе.

Вы написали код без привязки ...

...