webentwicklung-frage-antwort-db.com.de

Wo ist die Application.DoEvents () in WPF?

Ich habe den folgenden Beispielcode, der jedes Mal zoomt, wenn eine Taste gedrückt wird:

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

die Ausgabe ist:

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

wie Sie sehen, gibt es ein Problem, das ich mit Application.DoEvents(); lösen wollte, aber ... es existiert nicht a priori in .NET 4.

Was ist zu tun?

79
serhio

Die alte Application.DoEvents () -Methode wurde in WPF verworfen, um die beschriebene Verarbeitung mit einem Dispatcher oder einem Background Worker Thread durchzuführen. Unter den Links finden Sie einige Artikel zur Verwendung beider Objekte.

Wenn Sie Application.DoEvents () unbedingt verwenden müssen, können Sie einfach die Datei system.windows.forms.dll in Ihre Anwendung importieren und die Methode aufrufen. Dies wird jedoch nicht empfohlen, da Sie alle Vorteile von WPF verlieren.

25
Dillie-O

Versuchen Sie so etwas

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

Nun, ich bin gerade auf einen Fall gestoßen, in dem ich mit der Arbeit an einer Methode beginne, die auf dem Dispatcher-Thread ausgeführt wird, und die blockiert werden muss, ohne den UI-Thread zu blockieren. Es stellte sich heraus, dass msdn erklärt, wie DoEvents () basierend auf dem Dispatcher selbst implementiert werden:

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

(entnommen aus Dispatcher.PushFrame-Methode )

53
flq

Wenn Sie nur die Fenstergrafik aktualisieren müssen, verwenden Sie diese besser

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

scheint auch zu funktionieren.

5
serhio

Ein Problem bei beiden vorgeschlagenen Ansätzen ist, dass sie eine inaktive CPU-Auslastung mit sich bringen (nach meiner Erfahrung bis zu 12%). Dies ist in einigen Fällen suboptimal, wenn beispielsweise das modale Verhalten der Benutzeroberfläche mithilfe dieser Technik implementiert wird.

Die folgende Variation führt eine minimale Verzögerung zwischen Frames mit einem Timer ein (beachten Sie, dass dies hier mit Rx geschrieben ist, aber mit jedem normalen Timer erreicht werden kann):

 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()));
3
Jonas Chapuis

Seit der Einführung von async und await ist es jetzt möglich, den UI-Thread teilweise durch einen (früher) * synchronen Codeblock mit Task.Delay, Z.

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

Ich bin ehrlich, ich habe es nicht mit dem exakten Code oben versucht, aber ich verwende es in engen Schleifen, wenn ich viele Elemente in eine ItemsControl lege, die eine teure Elementvorlage enthält, manchmal füge ich eine hinzu kleine Verzögerung, um den anderen Dingen auf der Benutzeroberfläche mehr Zeit zu geben.

Beispielsweise:

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

Im Windows Store ist der Effekt bei einem Nice-Themaübergang in der Sammlung sehr wünschenswert.

Luke

  • zeige Kommentare. Als ich schnell meine Antwort schrieb, dachte ich darüber nach, einen synchronen Codeblock zu nehmen und den Thread dann wieder an den Aufrufer zurückzugeben, wodurch der Codeblock asynchron wird. Ich möchte meine Antwort nicht komplett umformulieren, da die Leser dann nicht sehen können, worüber Servy und ich gestritten haben.
1
Luke Puplett