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

发布于 2024-10-08 12:43:15 字数 1848 浏览 2 评论 0原文

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

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();< /code> 但是...它在 .NET 4 中先验不存在。

该怎么办?

I have the following sample code that zooms each time a button is pressed:

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));
    }
}

the output is:

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

as you can see, there is a problem, that I thought solve by using Application.DoEvents(); but... it does not exist a priori in .NET 4.

What to do?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

柏林苍穹下 2024-10-15 12:43:15

尝试这样的事情

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

Try something like this

public static void DoEvents()
{
    Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
                                          new Action(delegate { }));
}
拥醉 2024-10-15 12:43:15

好吧,我刚刚遇到一个情况,我开始处理在 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);
}

另请参阅:https://kent-boogaart.com/blog/dispatcher-frames

Well, I just hit a case where I start work on a method that runs on the Dispatcher thread, and it needs to block without blocking the UI Thread. Turns out that msdn explains how to implement a DoEvents() based on the Dispatcher itself:

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;
}

(taken from Dispatcher.PushFrame Method)

Some may prefer it in a single method that will enforce the same logic:

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);
}

See also: https://kent-boogaart.com/blog/dispatcher-frames

回眸一遍 2024-10-15 12:43:15

旧的 Application.DoEvents() 方法已在 WPF 中弃用,转而使用 调度程序后台工作线程 来执行您所描述的处理。请参阅有关如何使用这两个对象的几篇文章的链接。

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

The old Application.DoEvents() method has been deprecated in WPF in favor of using a Dispatcher or a Background Worker Thread to do the processing as you have described. See the links for a couple of articles on how to use both objects.

If you absolutely must use Application.DoEvents(), then you could simply import the system.windows.forms.dll into your application and call the method. However, this really isn't recommended, since you're losing all the advantages that WPF provides.

苯莒 2024-10-15 12:43:15

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

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

If you need just update window graphic, better use like this

public static void DoEvents()
{
    Application.Current.Dispatcher.Invoke(DispatcherPriority.Render,
                                          new Action(delegate { }));
}
女中豪杰 2024-10-15 12:43:15
myCanvas.UpdateLayout();

似乎也有效。

myCanvas.UpdateLayout();

seems to work as well.

霊感 2024-10-15 12:43:15

这两种建议方法的一个问题是它们会导致 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()));

One problem with both proposed approaches is that they entail idle CPU usage (up to 12% in my experience). This is suboptimal in some cases, for instance when modal UI behavior is implemented using this technique.

The following variation introduces a minimum delay between frames using a timer (note that it is written here with Rx but can be achieved with any regular timer):

 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()));
姐不稀罕 2024-10-15 12:43:15

自从引入 asyncawait 后,现在可以使用 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 Store上,当集合上有一个不错的主题过渡时,效果是相当理想的。

卢克

  • 看评论。当我快速写下答案时,我正在考虑采用同步代码块然后将线程放弃给其调用者的行为,其效果使代码块异步。我不想完全改写我的答案,因为这样读者就看不到我和瑟维在争吵什么。

Since the introduction of async and await its now possible to relinquish the UI thread partway through a (formerly)* synchronous block of code using Task.Delay, e.g.

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

I'll be honest, I've not tried it with the exact code above, but I use it in tight loops when I'm placing many items into an ItemsControl which has an expensive item template, sometimes adding a small delay to give the other stuff on the UI more time.

For example:

        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);
            }
        }

On Windows Store, when there's a nice theme transition on the collection, the effect is quite desirable.

Luke

  • see comments. When I was quickly writing my answer, I was thinking about the act of taking a synchronous block of code and then relinquishing the thread back to its caller, the effect of which makes the block of code asynchronous. I don't want to completely rephrase my answer because then readers can't see what Servy and I were bickering about.
铁轨上的流浪者 2024-10-15 12:43:15

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

对我来说很好用!

Make your DoEvent() in WPF:

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

Work well for me!

落日海湾 2024-10-15 12:43:15

推入框架:

using System.Windows.Threading;
...
var frame = new DispatcherFrame();
Dispatcher.PushFrame(frame);

退出框架:

frame.Continue = false;

下面的例子展示了如何使用DispatcherFrame来实现
与 Windows 窗体 DoEvents 方法类似的结果。

https://learn.microsoft.com/dotnet/api/ system.windows.threading.dispatcher.pushframe

更新:

使 Task.Delay() 非阻塞的有趣方法

// How to use: Task.Delay(250).WaitNB();
public static void WaitNB(this Task task)
{
    var frame = new DispatcherFrame();
    task.ContinueWith(t => frame.Continue = false);
    Dispatcher.PushFrame(frame);
}

Push frame:

using System.Windows.Threading;
...
var frame = new DispatcherFrame();
Dispatcher.PushFrame(frame);

Exit frame:

frame.Continue = false;

The following example shows how to use a DispatcherFrame to achieve
similar results as the Windows Forms DoEvents method.

https://learn.microsoft.com/dotnet/api/system.windows.threading.dispatcher.pushframe

Updated:

Interesting way to make Task.Delay() non-blocking

// How to use: Task.Delay(250).WaitNB();
public static void WaitNB(this Task task)
{
    var frame = new DispatcherFrame();
    task.ContinueWith(t => frame.Continue = false);
    Dispatcher.PushFrame(frame);
}
近箐 2024-10-15 12:43:15

回答最初的问题: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);

在代码中使用。

Answering the original question: Where is DoEvents?

I think DoEvents is VBA. And VBA does not seem to have a Sleep function. But VBA has a way to get exactly the same effect as a Sleep or Delay. Seems to me that DoEvents is equivalent to Sleep(0).

In VB and C#, you are dealing in .NET. And the original question is a C# question. In C#, you would use Thread.Sleep(0), where 0 is 0 milliseconds.

You need

using System.Threading.Task;

at the top of the file in order to use

Sleep(100);

in your code.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文