WPF中的Application.DoEvents()在哪里?


88

我有以下示例代码,每次按下按钮时都会缩放:

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中不存在先验问题。

该怎么办?


8
穿线?Application.DoEvents()是编写正确的多线程应用程序的可怜人的替代品,无论如何都极其糟糕。
科林·麦凯

2
我知道这很可怜,但是我更喜欢一点都没有的东西。
serhio 2010年

Answers:


25

WPF中不推荐使用旧的Application.DoEvents()方法,而是使用分派器后台工作线程执行您所描述的处理。有关如何使用两个对象的文章,请参见链接。

如果绝对必须使用Application.DoEvents(),则只需将system.windows.forms.dll导入应用程序并调用该方法。但是,实际上不建议这样做,因为您将失去WPF提供的所有优势。


我知道这很糟糕,但是我更喜欢一点什么都没有...我该如何使用Dispatcher?
serhio 2010年

3
我了解你的情况。编写我的第一个WPF应用程序时我就在其中,但我继续前进并花了一些时间学习新库,从长远来看,这样做会更好。我强烈建议您花时间。至于您的特殊情况,在我看来,您希望调度程序在您的click事件触发时处理坐标的显示。您需要阅读有关Dispatcher的更多内容以获取确切的实现。
Dillie-O 2010年

不,我会Application.DoEvents在增加myScaleTransform.ScaleX之后打电话给我。不知道Dispatcher是否可行。
serhio 2010年

12
删除Application.DoEvents()几乎是恼人的,因为MS取消对Windows 8的“开始”按钮
JeffHeaton

2
不,这是一件好事,这种方法带来的弊大于利。
杰西

136

试试这个

public static void DoEvents()
{
    Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
                                          new Action(delegate { }));
}

1
我什至为应用程序编写了扩展方法:)public static void DoEvents(this Application a)
serhio 2010年

@serhio:整洁的扩展方法:)
Fredrik Hedblad 2010年

2
但是我应该指出,在实际应用程序中Application.Current有时为null ...因此它可能并不完全等效。
serhio 2010年

完善。这应该是答案。反对者不应获得声望。
杰森·玛塔卡雅

6
如果正在执行中断指令(即,以同步方式继续执行此命令的WCF方法调用),这将不会总是起作用,因为您将看不到“刷新”,因为它将被阻止..这就是为什么从MSDN资源提供的答案flq比此答案更正确的原因。
2014年

57

好吧,我碰到了一个情况,即我开始在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方法

某些人可能会首选使用可以强制执行相同逻辑的单个方法:

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建议的实现更为安全。我发现了一篇有关它的博客文章
HugoRune 2012年

2
@HugoRune该博客文章指出此方法是不必要的,并使用与Meleak相同的实现。
Lukazoid 2012年

1
就我所知,@ Lukazoid的简单实现可能会导致难以跟踪的锁定。(我不确定原因,可能是问题出在调度程序队列中的代码再次调用DoEvents,或者调度程序队列中的代码生成了进一步的调度程序框架。)无论如何,exitFrame的解决方案都没有出现此类问题,因此我想推荐一个。(或者,当然,根本不使用doEvents)
HugoRune 2012年

1
在应用程序关闭时,在窗口上显示覆盖层而不是对话框,以及caliburn涉及VM的方法的结合,排除了回调,并要求我们阻塞而不阻塞。如果您为我提供没有DoEvents hack的解决方案,我将非常高兴。
2012年

1
旧博客文章的新链接:kent-boogaart.com/blog/dispatcher-frames
CAD

11

如果您只需要更新窗口图形,最好像这样使用

public static void DoEvents()
{
    Application.Current.Dispatcher.Invoke(DispatcherPriority.Render,
                                          new Action(delegate { }));
}

使用DispatcherPriority.Render的工作要比DispatcherPriority.Background更快。今天测试了StopWatcher
АлександрПекшев

我只是使用了这个技巧,但是使用DispatcherPriority.Send(最高优先级)以获得最佳结果。响应速度更快的用户界面。
Bent Rasmussen '18年

6
myCanvas.UpdateLayout();

似乎也可以。


我将使用它,因为它对我来说似乎更安全,但在其他情况下,我将保留DoEvents。
卡特·梅德林

不知道为什么,但这对我不起作用。DoEvents()工作正常。
2013年

就我而言,我必须像DoEvents()
Jeff

3

这两种提议的方法的一个问题是它们需要闲置的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()));

1

由于引入的asyncawait其现在可以通过(以前)放弃对UI线程的中途使用的代码*同步块Task.Delay,例如

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具有昂贵项目模板的项目中时,我会在紧密的循环中使用它,有时会增加一些延迟以使其他项目用户界面上的内容更多的时间。

例如:

        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 Store上,当集合上有很好的主题转换时,效果是非常理想的。

路加

  • 看评论。当我快速编写答案时,我正在考虑采取一个同步代码块,然后将线程交还给它的调用者的行为,其作用使代码块成为异步的。我不想完全改写我的答案,因为那样读者就看不到我和Servy在吵什么。

“现在可以在同步块中途放弃UI线程”,不是,不是。您只是使代码成为异步代码,而不是以同步方法从UI线程中提取消息。现在,经过正确设计的WPF应用程序将成为一个永远不会阻塞UI线程的应用程序,它首先通过使用异步来允许现有消息泵适当地泵送消息,从而在UI线程中同步执行长时间运行的操作。
Servy 2014年

@Servy在幕后,它await将使编译器对async方法的其余部分进行签名,以作为等待任务的延续。该延续将发生在UI线程(相同的同步上下文)上。然后,控制权返回到异步方法的调用者,即WPF事件子系统,在该事件中,事件将一直运行,直到在延迟时间段结束后某个时间安排的连续运行为止。
路加·普普利特

是的,我很清楚这一点。这就是使方法异步的原因(向调用方屈服控制并仅调度继续)。您的回答表明,该方法实际上是在使用异步更新UI时是同步的。
2014年

第一种方法(OP的代码)是同步Servy。第二个示例只是在循环中或必须将项目倒入较长列表时保持UI继续运行的提示。
路加·普普利特

您所做的就是使同步代码异步。您并没有按照描述或回答的要求使UI在同步方法中保持响应。
2014年

0

在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();

为我工作!


-2

回答原始问题:DoEvents在哪里?

我认为DoEvents是VBA。而且VBA似乎没有睡眠功能。但是VBA可以达到与“睡眠”或“延迟”完全相同的效果。在我看来,DoEvents等效于Sleep(0)。

在VB和C#中,您正在使用.NET。最初的问题是C#问题。在C#中,您将使用Thread.Sleep(0),其中0是0毫秒。

你需要

using System.Threading.Task;

在文件顶部以便使用

Sleep(100);

在您的代码中。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.