wpf 窗口刷新首先起作用,然后停止
我有一个例程,可以获取目录中所有图像的列表,然后对所有图像运行 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();
I've got a routine that grabs a list of all images in a directory, then runs an MD5 digest on all of them. Since this takes a while to do, I pop up a window with a progress bar. The progress bar is updated by a lambda that I pass in to the long-running routine.
The first problem was that the progress window was never updated (which is normal in WPF I guess). Since WPF lacks a Refresh()
command I fixed this with a call to Dispatcher.Invoke()
. Now the progress bar is updated for a while, then the window stops being updated. The long-running work does eventually finish and the windows go back to normal.
I have already tried a BackgroundWorker and quickly became frustrated by a threading issue related to an event triggered by the long-running process. So if that's really the best solution and I just need to learn the paradigm better, please say so.
But I'd be really much happier with the approach I've got here, except that it stops updating after a bit (for example, in a folder with 1000 files, it might update for 50-100 files, then "hang"). The UI does not need to be responsive during this activity, except to report on progress.
Anyway, here's the code. First the progress window itself:
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>
The long running method (in 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);
}
}
}
Which is called thusly:
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();
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
Mate 使用 DataBinding 进行简单的 WPF 编程。
请参阅 MVVM 设计模式的解释相同。
将进度条值属性与 DataContext 类中定义的某些源属性绑定。
并更新调度程序调用方法中的源属性。
WPF 引擎将负责休息。
您当前编写的代码没有任何绑定...
Mate do simple WPF programming by using DataBinding.
Refer MVVM design pattern explaining same.
Bind progressbar value property with some source property defined in DataContext class.
And update source property in dispatcher invoked method.
WPF engine will take care of rest.
You have currently written code wothout any binding...
如果我理解正确的话,您现在在主线程上完成所有工作。这意味着您从正常的消息泵(调度程序)中占用了(太多)时间。
简短的修复将类似于 WinForm 的 Application.DoEvents() 但我不知道是否有 WPF 等效项。
更好的解决方案是使用线程,然后后台工作者是最简单的方法。也许可以扩展该事件问题。
If I understand correctly you do all your work on the Main thread now. That means you are taking away (too much) time from the normal Messagepump (Dispatcher).
The short fix would be an analog to WinForm's Application.DoEvents() but I don't know if there is a WPF equivalent.
The better solution would be to use a Thread, and then the Backgroundworker is the easiest approach. Maybe expand on that event issue.
事实证明,这与
UpdateProgress
中的DispatcherPriority
有关。将DispatcherPriority.Render
更改为较低的值,在我的例子中DispatcherPriority.Background
就成功了。亨克的回答让我相信,如果消息泵不堪重负,它需要帮助来弄清楚何时该做什么。改变优先级似乎只是一张门票。
Turns out this is related to the
DispatcherPriority
inUpdateProgress
. ChangingDispatcherPriority.Render
to something lower, in my caseDispatcherPriority.Background
did the trick.Henk's answer lead me to believe that if the message pump is overwhelmed, that it needed help sorting out what to do when. Changing priority seems to be just the ticket.
修改代码以执行预期操作。
[注意:Xaml代码未修改]
Modified code for performing expected operation.
[Note : Xaml code is not modified]