2

我有一个例程,它获取目录中所有图像的列表,然后对所有图像运行 MD5 摘要。由于这需要一段时间,我会弹出一个带有进度条的窗口。进度条由我传递给长时间运行的例程的 lambda 更新。

第一个问题是进度窗口从未更新(我猜这在 WPF 中是正常的)。由于 WPF 缺少Refresh()命令,因此我通过调用Dispatcher.Invoke(). 现在进度条更新了一段时间,然后窗口停止更新。长期运行的工作最终完成,窗户恢复正常。

我已经尝试过 BackgroundWorker,但很快就对与长时间运行的进程触发的事件相关的线程问题感到沮丧。所以如果这真的是最好的解决方案,我只需要更好地学习范式,请这样说。

但是我对这里的方法真的很满意,除了它会在一段时间后停止更新(例如,在一个有 1000 个文件的文件夹中,它可能会更新 50-100 个文件,然后“挂起”) . UI 在此活动期间不需要响应,除了报告进度。

无论如何,这是代码。首先是进度窗口本身:

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

4 回答 4

2

Mate 使用 DataBinding 进行简单的 WPF 编程。请参阅解释相同的 MVVM 设计模式。

将进度条值属性与 DataContext 类中定义的某些源属性绑定。并更新调度程序调用方法中的源属性。

WPF 引擎将负责休息。

您当前编写的代码没有任何绑定...

于 2010-03-24T11:23:07.697 回答
1

如果我理解正确,您现在就在主线程上完成所有工作。这意味着您从正常的 Messagepump (Dispatcher) 中占用了(太多)时间。

简短的修复将类似于 WinForm 的 Application.DoEvents() 但我不知道是否有 WPF 等价物。

更好的解决方案是使用线程,然后 Backgroundworker 是最简单的方法。也许扩展该事件问题。

于 2010-03-24T12:11:08.190 回答
1

原来这与DispatcherPriorityin相关UpdateProgressDispatcherPriority.Render在我的情况下,换成更低的东西就DispatcherPriority.Background可以了。

Henk 的回答让我相信,如果消息泵不堪重负,那么它需要帮助来确定何时该做什么。改变优先级似乎只是门票。

于 2010-03-24T13:01:44.500 回答
1

用于执行预期操作的修改代码。[注意: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();
      }


   }

于 2010-03-24T13:57:54.123 回答