在WPF中,Application.DoEvents()在哪里?
在WPF中,Application.DoEvents()在哪里?
我有以下示例代码,每次按下按钮时都会进行缩放:
XAML:
*.cs
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void myButton_Click(object sender, RoutedEventArgs e) { Console.WriteLine("缩放比例:{0},位置:{1}", myScaleTransform.ScaleX, myCanvas.PointToScreen(GetMyByttonLocation())); myScaleTransform.ScaleX = myScaleTransform.ScaleY = myScaleTransform.ScaleX + 1; Console.WriteLine("缩放比例:{0},位置:{1}", myScaleTransform.ScaleX, myCanvas.PointToScreen(GetMyByttonLocation())); } private Point GetMyByttonLocation() { return new Point( Canvas.GetLeft(myButton), Canvas.GetTop(myButton)); } }
输出结果为:
缩放比例:1,位置:296;315 缩放比例:2,位置:296;315 缩放比例:2,位置:346;365 缩放比例:3,位置:346;365 缩放比例:3,位置:396;415 缩放比例:4,位置:396;415
如你所见,存在一个问题,我原以为可以通过使用 Application.DoEvents();
来解决,但是...在.NET 4中它不存在。
怎么办?
问题出现的原因:
需要在Dispatcher线程上运行的方法需要阻止UI线程,但又不能阻止UI线程。
解决方法:
根据MSDN上的解释,可以基于Dispatcher本身实现一个DoEvents()方法。代码如下:
public void DoEvents() { DispatcherFrame frame = new DispatcherFrame(); Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), frame); Dispatcher.PushFrame(frame); } public object ExitFrame(object f) { ((DispatcherFrame)f).Continue = false; return null; }
有些人可能更喜欢将其放在一个单独的方法中以强制执行相同的逻辑。代码如下:
public static void DoEvents() { var frame = new DispatcherFrame(); Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback( delegate (object f) { ((DispatcherFrame)f).Continue = false; return null; }),frame); Dispatcher.PushFrame(frame); }
此外,还可以参考以下链接了解更多信息:
- https://kent-boogaart.com/blog/dispatcher-frames
- https://kent-boogaart.com/blog/dispatcher-frames (new link to old blog post)
- http://kentb.blogspot.de/2008/04/dispatcher-frames.html
在这些链接中,有人认为这种方法是不必要的,应该使用与Meleak建议的实现相同的方法。
有人认为简单的实现可能会导致难以追踪的锁死问题。无论原因是什么(可能是调用了DoEvents的调度队列中的代码,或者调度队列中的代码生成了更多的调度帧),使用exitFrame的解决方案都没有出现这些问题,所以我建议使用这种方式。(当然,也可以完全不使用DoEvents)
还有人认为大多数需要DoEvents的情况都可以用不同的方式来编写,而不需要使用DoEvents。
如果你能给我提供一种不使用DoEvents的解决方法,我会非常高兴。
问题的出现的原因:在WPF中,没有直接的Application.DoEvents()方法可用,而在WinForms中有。这可能是因为WPF使用了不同的UI线程模型,而不需要像WinForms那样使用DoEvents()来处理UI响应性。
解决方法:可以通过使用Dispatcher.Invoke方法来实现类似于Application.DoEvents()的功能。可以创建一个静态方法DoEvents(),在该方法中使用Application.Current.Dispatcher.Invoke来触发后台操作。另外还可以创建一个扩展方法DoEvents(),该方法可以作用于Application类。
public static void DoEvents() { Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { })); } public static void DoEvents(this Application a) { a.Dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { })); }
然而需要注意的是,在实际应用中,Application.Current有时候可能为null,所以这种方法可能不是完全等效的。
还有一种方法是使用Dispatcher.PushFrame方法来实现类似的功能。这个方法会启动一个新的消息循环,使得WPF应用程序继续处理未完成的消息。
public static void DoEvents() { DispatcherFrame frame = new DispatcherFrame(); Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), frame); Dispatcher.PushFrame(frame); } private static object ExitFrame(object frame) { ((DispatcherFrame)frame).Continue = false; return null; }
需要注意的是,这种方法可能不适用于某些情况,例如在调用WCF方法时可能会被阻塞。
在WPF中没有直接的Application.DoEvents()方法,但可以通过使用Dispatcher.Invoke或Dispatcher.PushFrame方法来实现类似的功能。这些方法可以让WPF应用程序继续处理未完成的消息,以提高UI的响应性。
在WPF中,旧的Application.DoEvents()方法已被弃用,推荐使用Dispatcher或Background Worker Thread来处理所描述的处理过程。如果绝对必须使用Application.DoEvents(),则可以简单地将system.windows.forms.dll导入到应用程序中并调用该方法。然而,这并不推荐,因为你会失去WPF提供的所有优势。
如果你必须在特定情况下使用Dispatcher,你可以在每次点击事件触发时使用Dispatcher来处理坐标的显示。你需要进一步了解Dispatcher来实现确切的实现方法。
删除Application.DoEvents()几乎和微软在Windows 8中删除“开始”按钮一样令人讨厌。
在这个时代,我无法相信这仍然是一个问题。我记得在VB6中调用DoEvents来模拟异步行为。问题是后台工作器只适用于不进行UI处理的情况,但是UI处理(例如创建数千个ListBoxItems)可能会锁定UI线程。当确实需要进行繁重的UI工作时,我们应该怎么办?
4年后,这个问题仍然存在。例如,StatusBarText = "Updating"不会更新UI。更新UI不应该需要一个后台线程,通常微软在某些本应该简单的事情上过于复杂了。