WPF 中的 Application.DoEvents() 在哪里?
Posted
技术标签:
【中文标题】WPF 中的 Application.DoEvents() 在哪里?【英文标题】:Where is the Application.DoEvents() in WPF? 【发布时间】:2011-05-28 23:19:57 【问题描述】:我有以下示例代码,每次按下按钮时都会缩放:
XAML:
<Window x:Class="WpfApplication12.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Canvas x:Name="myCanvas">
<Canvas.LayoutTransform>
<ScaleTransform x:Name="myScaleTransform" />
</Canvas.LayoutTransform>
<Button Content="Button"
Name="myButton"
Canvas.Left="50"
Canvas.Top="50"
Click="myButton_Click" />
</Canvas>
</Window>
*.cs
public partial class MainWindow : Window
public MainWindow()
InitializeComponent();
private void myButton_Click(object sender, RoutedEventArgs e)
Console.WriteLine("scale 0, location: 1",
myScaleTransform.ScaleX,
myCanvas.PointToScreen(GetMyByttonLocation()));
myScaleTransform.ScaleX =
myScaleTransform.ScaleY =
myScaleTransform.ScaleX + 1;
Console.WriteLine("scale 0, location: 1",
myScaleTransform.ScaleX,
myCanvas.PointToScreen(GetMyByttonLocation()));
private Point GetMyByttonLocation()
return new Point(
Canvas.GetLeft(myButton),
Canvas.GetTop(myButton));
输出是:
scale 1, location: 296;315
scale 2, location: 296;315
scale 2, location: 346;365
scale 3, location: 346;365
scale 3, location: 396;415
scale 4, location: 396;415
如您所见,有一个问题,我认为可以通过使用 Application.DoEvents();
解决,但是......它在 .NET 4 中先验存在。
怎么办?
【问题讨论】:
线程? Application.DoEvents() 是穷人的替代品,可以替代编写正确的多线程应用程序,并且在任何情况下都非常糟糕。 我知道这很糟糕,但我更喜欢什么都没有的东西。 这是相关的:How to wait for WaitHandle while serving WPF Dispatcher events? 【参考方案1】:试试这样的
public static void DoEvents()
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
new Action(delegate ));
【讨论】:
我什至为应用写了一个扩展方法:)public static void DoEvents(this Application a)
@serhio: 简洁的扩展方法:)
我应该注意的是,在实际应用程序中Application.Current
有时为空......所以也许它并不完全等同。
完美。这应该是答案。反对者不应该得到赞誉。
这并不总是有效,因为它不会推送帧,如果正在执行中断指令(即调用 WCF 方法,该方法同步地继续执行此命令)您可能不会看到'刷新',因为它将被阻止。这就是为什么从 MSDN 资源提供的答案 flq 比这个更正确。【参考方案2】:
好吧,我刚刚遇到了一个案例,我开始使用在 Dispatcher 线程上运行的方法,它需要在不阻塞 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;
(取自Dispatcher.PushFrame Method)
有些人可能更喜欢在单一方法中执行相同的逻辑:
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);
【讨论】:
不错的发现!这看起来比 Meleak 建议的实现更安全。我找到了a blog post about it @HugoRune 该博客文章指出这种方法是不必要的,并且使用与 Meleak 相同的实现。 @Lukazoid 据我所知,简单的实现可能会导致难以追踪的锁定。 (我不确定原因,可能问题是调度程序队列中的代码再次调用 DoEvents,或者调度程序队列中的代码生成更多调度程序帧。)无论如何,exitFrame 的解决方案没有出现此类问题,所以我会推荐那个。 (或者,当然,根本不使用 doEvents) 在您的窗口上显示覆盖而不是对话框,结合 caliburn 在应用程序关闭时让虚拟机参与的方式排除了回调,并要求我们在不阻塞的情况下阻塞。如果您向我提供一个没有 DoEvents hack 的解决方案,我会很高兴。 旧博文的新链接:kent-boogaart.com/blog/dispatcher-frames【参考方案3】:旧的 Application.DoEvents() 方法已在 WPF 中弃用,取而代之的是使用 Dispatcher 或 Background Worker Thread 来执行您所描述的处理。有关如何使用这两个对象的文章,请参阅链接。
如果您绝对必须使用 Application.DoEvents(),那么您可以简单地将 system.windows.forms.dll 导入您的应用程序并调用该方法。但是,确实不建议这样做,因为您将失去 WPF 提供的所有优势。
【讨论】:
我知道这很糟糕,但我更喜欢什么都没有的东西......在我的情况下如何使用 Dispatcher? 我了解您的情况。当我编写我的第一个 WPF 应用程序时,我就在其中,但我继续前进并花时间学习新库,从长远来看,它会更好。我强烈建议花点时间。至于您的特定情况,在我看来,您希望调度程序在每次触发单击事件时处理坐标的显示。您需要阅读有关 Dispatcher 的更多信息以了解确切的实现。 删除 Application.DoEvents() 几乎和 MS 删除 Windows 8 上的“开始”按钮一样烦人。 不,这是一件好事,这种方法弊大于利。 我不敢相信这在当今时代仍然是一个问题。我记得必须在 VB6 中调用 DoEvents 来模拟异步行为。问题是后台工作人员仅在您不进行 UI 处理时才起作用,但 UI 处理 - 例如创建数千个 ListBoxItems 可能会锁定 UI 线程。当 UI 工作真的很繁重时,我们应该怎么做?【参考方案4】:如果你只需要更新窗口图形,最好这样使用
public static void DoEvents()
Application.Current.Dispatcher.Invoke(DispatcherPriority.Render,
new Action(delegate ));
【讨论】:
使用 DispatcherPriority.Render 比 DispatcherPriority.Background 工作得更快。今天用 StopWatcher 测试 我只是使用了这个技巧,但使用 DispatcherPriority.Send (最高优先级)以获得最佳效果;响应速度更快的用户界面。【参考方案5】:myCanvas.UpdateLayout();
似乎也可以。
【讨论】:
我将使用它,因为它对我来说似乎更安全,但我将保留 DoEvents 用于其他情况。 不知道为什么,但这对我不起作用。 DoEvents() 工作正常。 就我而言,我必须这样做以及 DoEvents()【参考方案6】:这两种提议的方法的一个问题是它们需要空闲的 CPU 使用率(根据我的经验高达 12%)。这在某些情况下不是最理想的,例如当使用此技术实现模态 UI 行为时。
以下变体使用定时器引入了帧之间的最小延迟(注意这里是用 Rx 编写的,但可以使用任何常规定时器来实现):
var minFrameDelay = Observable.Interval(TimeSpan.FromMilliseconds(50)).Take(1).Replay();
minFrameDelay.Connect();
// synchronously add a low-priority no-op to the Dispatcher's queue
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(() => minFrameDelay.Wait()));
【讨论】:
【参考方案7】:自从引入async
和await
以来,现在可以通过使用Task.Delay
的(以前)* 同步代码块中途放弃 UI 线程,例如
private async void myButton_Click(object sender, RoutedEventArgs e)
Console.WriteLine("scale 0, location: 1",
myScaleTransform.ScaleX,
myCanvas.PointToScreen(GetMyByttonLocation()));
myScaleTransform.ScaleX =
myScaleTransform.ScaleY =
myScaleTransform.ScaleX + 1;
await Task.Delay(1); // In my experiments, 0 doesn't work. Also, I have noticed
// that I need to add as much as 100ms to allow the visual tree
// to complete its arrange cycle and for properties to get their
// final values (as opposed to NaN for widths etc.)
Console.WriteLine("scale 0, location: 1",
myScaleTransform.ScaleX,
myCanvas.PointToScreen(GetMyByttonLocation()));
老实说,我没有使用上面的确切代码尝试过,但是当我将许多项目放入具有昂贵项目模板的 ItemsControl
中时,我会在紧密循环中使用它,有时会添加一个稍稍延迟,让 UI 上的其他内容有更多时间。
例如:
var levelOptions = new ObservableCollection<GameLevelChoiceItem>();
this.ViewModel[LevelOptionsViewModelKey] = levelOptions;
var syllabus = await this.LevelRepository.GetSyllabusAsync();
foreach (var level in syllabus.Levels)
foreach (var subLevel in level.SubLevels)
var abilities = new List<GamePlayingAbility>(100);
foreach (var g in subLevel.Games)
var gwa = await this.MetricsRepository.GetGamePlayingAbilityAsync(g.Value);
abilities.Add(gwa);
double PlayingScore = AssessmentMetricsProcessor.ComputePlayingLevelAbility(abilities);
levelOptions.Add(new GameLevelChoiceItem()
LevelAbilityMetric = PlayingScore,
AbilityCaption = PlayingScore.ToString(),
LevelCaption = subLevel.Name,
LevelDescriptor = level.Ordinal + "." + subLevel.Ordinal,
LevelLevels = subLevel.Games.Select(g => g.Value),
);
await Task.Delay(100);
在 Windows 应用商店中,当合集有漂亮的主题过渡时,效果非常理想。
卢克
参见 cmets。当我快速写下我的答案时,我正在考虑获取同步代码块然后将线程放回其调用者的行为,其效果使代码块异步。我不想完全改写我的答案,因为这样读者就看不到我和 Servy 争吵的内容了。【讨论】:
"现在可以通过同步块中途放弃 UI 线程" 不,不是。您刚刚使代码异步,而不是以同步方法从 UI 线程抽取消息。现在,一个正确设计的 WPF 应用程序将不会阻塞 UI 线程,首先在 UI 线程中同步执行长时间运行的操作,使用异步来允许现有的消息泵适当地泵送消息。 @Servy 在幕后,await
将导致编译器注册其余的异步方法作为等待任务的延续。这种延续将发生在 UI 线程上(相同的同步上下文)。然后控制权返回到异步方法的调用者,即 WPF 事件子系统,其中事件将一直运行,直到在延迟期到期后的某个时间运行预定的延续。
是的,我很清楚这一点。这就是使方法异步的原因(将控制权交给调用者并仅调度延续)。您的回答表明该方法实际上是使用 asynchrony 来更新 UI 时是同步的。
第一种方法(OP的代码)是同步的,Servy。第二个示例只是在循环或必须将项目倒入长列表时保持 UI 运行的提示。
而你所做的是使同步代码异步。正如您的描述或答案所要求的那样,您没有在同步方法中保持 UI 响应。【参考方案8】:
在 WPF 中创建你的 DoEvent():
Thread t = new Thread(() =>
// do some thing in thread
for (var i = 0; i < 500; i++)
Thread.Sleep(10); // in thread
// call owner thread
this.Dispatcher.Invoke(() =>
MediaItem uc = new MediaItem();
wpnList.Children.Add(uc);
);
);
t.TrySetApartmentState(ApartmentState.STA); //for using Clipboard in Threading
t.Start();
为我工作!
【讨论】:
【参考方案9】:回答最初的问题:DoEvents 在哪里?
我认为 DoEvents 是 VBA。而且VBA好像没有Sleep功能。但是 VBA 有办法获得与睡眠或延迟完全相同的效果。在我看来,DoEvents 相当于 Sleep(0)。
在 VB 和 C# 中,您正在处理 .NET。原来的问题是一个 C# 问题。在 C# 中,您将使用 Thread.Sleep(0),其中 0 是 0 毫秒。
你需要
using System.Threading.Task;
在文件顶部以便使用
Sleep(100);
在您的代码中。
【讨论】:
以上是关于WPF 中的 Application.DoEvents() 在哪里?的主要内容,如果未能解决你的问题,请参考以下文章