Wo ist die Application.DoEvents () in WPF?


86

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 können, gibt es ein Problem, das ich mithilfe von lösen wollte, Application.DoEvents();aber ... es existiert nicht a priori in .NET 4.

Was ist zu tun?


8
Einfädeln? Application.DoEvents () war der Ersatz des armen Mannes für das Schreiben von ordnungsgemäßen Multithread-Anwendungen und auf jeden Fall für äußerst schlechte Praktiken.
Colin Mackay

2
Ich weiß, dass das arm und schlecht ist, aber ich bevorzuge etwas, das überhaupt nichts ist.
Serhio

Antworten:


25

Die alte Application.DoEvents () -Methode wurde in WPF zugunsten der Verwendung eines Dispatchers oder eines Background Worker-Threads für die von Ihnen beschriebene Verarbeitung veraltet . 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 wirklich nicht empfohlen, da Sie alle Vorteile verlieren, die WPF bietet.


Ich weiß, dass das arm und schlecht ist, aber ich bevorzuge etwas, das überhaupt nichts ... Wie kann ich Dispatcher in meiner Situation verwenden?
Serhio

3
Ich verstehe deine Situation. Ich war dabei, als ich meine erste WPF-App schrieb, aber ich nahm mir die Zeit, um die neue Bibliothek zu lernen, und war auf lange Sicht viel besser dafür. Ich kann es nur empfehlen, sich die Zeit zu nehmen. In Ihrem speziellen Fall möchte ich, dass der Dispatcher die Anzeige der Koordinaten jedes Mal übernimmt, wenn Ihr Klickereignis ausgelöst wird. Für die genaue Implementierung müssen Sie mehr über den Dispatcher lesen.
Dillie-O

Nein, ich würde Application.DoEventsnach dem Inkrementieren von myScaleTransform.ScaleX aufrufen. Ich weiß nicht, ob dies mit Dispatcher möglich ist.
Serhio

12
Das Entfernen von Application.DoEvents () ist fast so ärgerlich wie das Entfernen der Schaltfläche "Start" unter Windows 8 durch MS.
JeffHeaton

2
Nein, es war gut, dass diese Methode mehr Schaden als Nutzen verursachte.
Jesse

135

Versuchen Sie so etwas

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

1
Ich habe sogar eine Erweiterungsmethode für die Anwendung geschrieben :)public static void DoEvents(this Application a)
Serhio

@serhio: Ordentliche Erweiterungsmethode :)
Fredrik Hedblad

2
Ich sollte jedoch bemerken, dass in der realen Anwendung Application.Currentmanchmal null ist ... also ist es vielleicht nicht ganz gleichwertig.
Serhio

Perfekt. Dies sollte die Antwort sein. Neinsager sollten den Kredit nicht bekommen.
Jeson Martajaya

6
Dies funktioniert nicht immer, da der Frame nicht gepusht wird, wenn eine unterbrechende Anweisung ausgeführt wird (dh ein Aufruf einer WCF-Methode, die synchron zu diesem Befehl fortfährt), wird möglicherweise nicht "Aktualisieren" angezeigt, da dieser blockiert wird Aus diesem Grund ist die von der MSDN-Ressource bereitgestellte Antwort flq korrekter als diese.
GY

57

Nun, ich habe gerade einen Fall getroffen, 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 stellt sich heraus, dass msdn erklärt, wie ein DoEvents () basierend auf dem Dispatcher selbst implementiert wird:

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 der Dispatcher.PushFrame-Methode )

Einige bevorzugen es möglicherweise in einer einzigen Methode, die dieselbe Logik erzwingt:

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

Schöner Fund! Dies sieht sicherer aus als die von Meleak vorgeschlagene Implementierung. Ich habe einen Blog-Beitrag darüber gefunden
HugoRune

2
@HugoRune In diesem Blogbeitrag heißt es, dass dieser Ansatz nicht erforderlich ist und dass dieselbe Implementierung wie bei Meleak verwendet wird.
Lukazoid

1
@Lukazoid Soweit ich das beurteilen kann, kann die einfache Implementierung zu schwer nachvollziehbaren Abstürzen führen. (Ich bin mir über die Ursache nicht sicher. Möglicherweise liegt das Problem in Code in der Dispatcher-Warteschlange, der DoEvents erneut aufruft, oder in Code in der Dispatcher-Warteschlange, der weitere Dispatcher-Frames generiert.) In jedem Fall zeigte die Lösung mit exitFrame keine derartigen Probleme empfehle das. (Oder natürlich überhaupt keine doEvents verwenden)
HugoRune

1
Das Anzeigen eines Overlays in Ihrem Fenster anstelle eines Dialogfelds in Kombination mit der Methode von Caliburn, VMs beim Schließen der App einzubeziehen, schloss Rückrufe aus und erforderte das Blockieren ohne Blockieren. Ich würde mich freuen, wenn Sie mir eine Lösung ohne den DoEvents-Hack präsentieren.
flq

1
Neuer Link zum alten Blog-Beitrag: kent-boogaart.com/blog/dispatcher-frames
CAD-

11

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

Mit DispatcherPriority.Render arbeiten Sie schneller als mit DispatcherPriority.Background. Getestet heute mit StopWatcher
19лександр Пекшев

Ich habe gerade diesen Trick verwendet, aber DispatcherPriority.Send (höchste Priorität) verwendet, um die besten Ergebnisse zu erzielen. reaktionsschnellere Benutzeroberfläche.
Bent Rasmussen

6
myCanvas.UpdateLayout();

scheint auch zu funktionieren.


Ich werde dies verwenden, da es mir viel sicherer erscheint, aber ich werde die DoEvents für andere Fälle behalten.
Carter Medlin

Ich weiß nicht warum, aber das funktioniert bei mir nicht. DoEvents () funktioniert einwandfrei.
Newman

In meinem Fall musste ich dies ebenso tun wie die DoEvents ()
Jeff

3

Ein Problem bei beiden vorgeschlagenen Ansätzen besteht darin, dass die CPU im Leerlauf ausgelastet ist (meiner Erfahrung nach bis zu 12%). Dies ist in einigen Fällen nicht optimal, beispielsweise wenn das Verhalten der modalen Benutzeroberfläche mithilfe dieser Technik implementiert wird.

Die folgende Variante führt eine minimale Verzögerung zwischen Frames mit einem Timer ein (beachten Sie, dass sie hier mit Rx geschrieben wird, aber mit jedem regulären 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()));

1

Seit der Einführung von asyncund es ist awaitnun möglich, den UI-Thread teilweise durch einen (ehemals) * synchronen Codeblock abzugeben, indem Task.Delayz

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 genauen Code oben versucht, aber ich verwende es in engen Schleifen, wenn ich viele Artikel in eine ItemsControlVorlage mit einer teuren Artikelvorlage lege und manchmal eine kleine Verzögerung hinzufüge, um den anderen zu geben Sachen auf der Benutzeroberfläche mehr Zeit.

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

Wenn es im Windows Store einen schönen Themenübergang in der Sammlung gibt, ist der Effekt durchaus wünschenswert.

Luke

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

"Es ist jetzt möglich, den UI-Thread teilweise durch einen synchronen Block abzugeben." Nein, das ist es nicht. Sie haben den Code gerade asynchron gemacht , anstatt Nachrichten aus dem UI-Thread in einer synchronen Methode zu pumpen. Nun wäre eine korrekt gestaltete WPF-Anwendung eine Anwendung, die den UI-Thread niemals blockiert, indem sie in erster Linie synchron laufende Vorgänge im UI-Thread ausführt und Asynchronität verwendet, damit die vorhandene Nachrichtenpumpe Nachrichten entsprechend pumpen kann.
Servy

@Servy Unter dem Deckmantel awaitveranlasst der Compiler, den Rest der asynchronen Methode als Fortsetzung der erwarteten Aufgabe anzumelden . Diese Fortsetzung erfolgt im UI-Thread (gleicher Synchronisierungskontext). Die Steuerung kehrt dann zum Aufrufer der asynchronen Methode zurück, dh zum WPF-Ereignissubsystem, in dem Ereignisse ausgeführt werden, bis die geplante Fortsetzung einige Zeit nach Ablauf der Verzögerungszeit ausgeführt wird.
Luke Puplett

Ja, das ist mir klar. Das macht die Methode asynchron (die nachgebende Kontrolle für den Aufrufer und nur das Planen einer Fortsetzung). Ihre Antwort besagt, dass die Methode synchron ist, wenn tatsächlich Asynchronität zum Aktualisieren der Benutzeroberfläche verwendet wird.
Servy

Die erste Methode (der OP-Code) ist synchron, Servy. Das zweite Beispiel ist nur ein Tipp, um die Benutzeroberfläche in einer Schleife am Laufen zu halten oder Elemente in eine lange Liste einfügen zu müssen.
Luke Puplett

Und was Sie getan haben, ist, den synchronen Code asynchron zu machen. Sie haben die Benutzeroberfläche nicht innerhalb einer synchronen Methode angesprochen, wie in Ihrer Beschreibung angegeben oder in der Antwort gefragt.
Servy

0

Machen Sie Ihr 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();

Arbeite gut für mich!


-2

Beantwortung der ursprünglichen Frage: Wo ist DoEvents?

Ich denke, DoEvents ist VBA. Und VBA scheint keine Schlaffunktion zu haben. VBA bietet jedoch die Möglichkeit, genau den gleichen Effekt wie Sleep oder Delay zu erzielen. Mir scheint, dass DoEvents dem Schlaf (0) entspricht.

In VB und C # handelt es sich um .NET. Und die ursprüngliche Frage ist eine C # -Frage. In C # würden Sie Thread.Sleep (0) verwenden, wobei 0 0 Millisekunden ist.

Du brauchst

using System.Threading.Task;

oben in der Datei, um zu verwenden

Sleep(100);

in Ihrem Code.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.