我有一个例程,它抓取目录中所有图像的列表,然后在所有图像上运行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();
答案 0 :(得分:2)
原来这与DispatcherPriority
中的UpdateProgress
有关。将DispatcherPriority.Render
更改为更低级别,在我的情况下DispatcherPriority.Background
就可以了。
Henk的回答让我相信,如果消息泵不堪重负,它需要帮助整理出什么时候做什么。改变优先权似乎只是门票。
答案 1 :(得分:1)
Mate使用DataBinding进行简单的WPF编程。 请参阅解释相同的MVVM设计模式。
使用DataContext类中定义的某些source属性绑定progressbar value属性。 并在调度程序调用方法中更新source属性。
WPF引擎将负责休息。
您目前编写的代码没有任何绑定......
答案 2 :(得分:1)
如果我理解正确,您现在就在主线程上完成所有工作。这意味着你从正常的Messagepump(Dispatcher)中拿走(太多)时间。
简短修复与WinForm的Application.DoEvents()类似,但我不知道是否有WPF等效。
更好的解决方案是使用Thread,然后Backgroundworker是最简单的方法。也许扩展该事件问题。
答案 3 :(得分: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();
}
}