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

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

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.

怎么办?

105109 次浏览

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.

Try something like this

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

seems to work as well.

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

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

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.

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

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

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.

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!

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