Où se trouve Application.DoEvents () dans WPF?

88

J'ai l'exemple de code suivant qui effectue un zoom chaque fois qu'un bouton est enfoncé:

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

la sortie est:

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

comme vous pouvez le voir, il y a un problème, que je pensais résoudre en utilisant Application.DoEvents();mais ... il n'existe pas a priori dans .NET 4.

Que faire?

serhio
la source
8
Filetage? Application.
Colin Mackay
2
Je sais que c'est mauvais et mauvais, mais je préfère quelque chose que rien du tout.
serhio

Réponses:

25

L'ancienne méthode Application.DoEvents () a été déconseillée dans WPF en faveur de l'utilisation d'un Dispatcher ou d'un Background Worker Thread pour effectuer le traitement comme vous l'avez décrit. Voir les liens pour quelques articles sur la façon d'utiliser les deux objets.

Si vous devez absolument utiliser Application.DoEvents (), vous pouvez simplement importer le system.windows.forms.dll dans votre application et appeler la méthode. Cependant, ce n'est vraiment pas recommandé, car vous perdez tous les avantages fournis par WPF.

Dillie-O
la source
Je sais que c'est mauvais et mauvais, mais je préfère quelque chose que rien du tout ... Comment puis-je utiliser Dispatcher dans ma situation?
serhio
3
Je comprends votre situation. J'étais dedans quand j'ai écrit ma première application WPF, mais j'ai continué et j'ai pris le temps d'apprendre la nouvelle bibliothèque et c'était bien mieux à long terme. Je recommande vivement de prendre le temps. En ce qui concerne votre cas particulier, il me semble que vous voudriez que le répartiteur gère l'affichage des coordonnées à chaque fois que votre événement de clic se déclenche. Vous devrez en savoir plus sur Dispatcher pour l'implémentation exacte.
Dillie-O
non, j'appellerais Application.DoEventsaprès avoir incrémenté myScaleTransform.ScaleX. Je ne sais pas si c'est possible avec Dispatcher.
serhio
12
Supprimer Application.DoEvents () est presque aussi ennuyeux que MS en supprimant le bouton "Démarrer" sur Windows 8.
JeffHeaton
2
Non, c'était une bonne chose, cette méthode faisait plus de mal que de bien.
Jesse
136

Essayez quelque chose comme ça

public static void DoEvents()
{
    Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
                                          new Action(delegate { }));
}
Fredrik Hedblad
la source
1
J'ai même écrit une méthode d'extension pour l'application :)public static void DoEvents(this Application a)
serhio
@serhio: Méthode d'extension soignée :)
Fredrik Hedblad
2
Je dois cependant remarquer que dans l'application réelle, Application.Currentparfois est nul ... alors peut-être que ce n'est pas tout à fait équivalent.
serhio
Parfait. Cela devrait être la réponse. Les opposants ne devraient pas obtenir le crédit.
Jeson Martajaya
6
Cela ne fonctionnera pas toujours car cela ne pousse pas la trame, si une instruction d'interruption est faite (c'est-à-dire un appel à une méthode WCF qui dans une synchronisation synchronisée continue avec cette commande) il y a de fortes chances que vous ne voyiez pas `` rafraîchir '' car il sera bloqué .. C'est pourquoi le flq de réponse fourni par la ressource MSDN est plus correct que celui-ci.
GY du
57

Eh bien, je viens de frapper un cas où je commence à travailler sur une méthode qui s'exécute sur le thread Dispatcher, et elle doit bloquer sans bloquer le thread UI. Il s'avère que msdn explique comment implémenter un DoEvents () basé sur le Dispatcher lui-même:

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

(tiré de la méthode Dispatcher.PushFrame )

Certains peuvent le préférer dans une seule méthode qui appliquera la même logique:

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);
}
flq
la source
Bonne trouvaille! Cela semble plus sûr que l'implémentation suggérée par Meleak. J'ai trouvé un article de blog à ce sujet
HugoRune
2
@HugoRune Ce billet de blog indique que cette approche n'est pas nécessaire et qu'elle doit utiliser la même implémentation que Meleak.
Lukazoid
1
@Lukazoid Autant que je sache, une implémentation simple peut provoquer des blocages difficiles à retracer. (Je ne suis pas sûr de la cause, peut-être que le problème est le code dans la file d'attente du répartiteur appelant à nouveau DoEvents, ou le code dans la file d'attente du répartiteur générant d'autres cadres de répartiteur.) Dans tous les cas, la solution avec exitFrame ne présentait pas de tels problèmes, je recommande celui-là. (Ou, bien sûr, ne pas utiliser du tout doEvents)
HugoRune
1
L'affichage d'une superposition sur votre fenêtre au lieu d'une boîte de dialogue en combinaison avec la manière de caliburn d'impliquer les machines virtuelles lorsque l'application se ferme a exclu les rappels et nous a obligés à bloquer sans blocage. Je serais ravi si vous me présentiez une solution sans le hack DoEvents.
flq
1
nouveau lien vers l'ancien article de blog: kent-boogaart.com/blog/dispatcher-frames
CAD bloke
11

Si vous avez juste besoin de mettre à jour le graphique de la fenêtre, mieux vaut l'utiliser comme ceci

public static void DoEvents()
{
    Application.Current.Dispatcher.Invoke(DispatcherPriority.Render,
                                          new Action(delegate { }));
}
Александр Пекшев
la source
L'utilisation de DispatcherPriority.Render fonctionne plus rapidement que DispatcherPriority.Background. Testé aujourd'hui avec StopWatcher
Александр Пекшев
Je viens d'utiliser cette astuce mais j'utilise DispatcherPriority.Send (priorité la plus élevée) pour de meilleurs résultats; une interface utilisateur plus réactive.
Bent Rasmussen
6
myCanvas.UpdateLayout();

semble fonctionner aussi bien.

serhio
la source
Je vais l'utiliser car cela me semble beaucoup plus sûr, mais je vais garder les DoEvents pour d'autres cas.
Carter Medlin
Je ne sais pas pourquoi mais cela ne fonctionne pas pour moi. DoEvents () fonctionne très bien.
newman
Dans mon cas, je devais faire cela ainsi que les DoEvents ()
Jeff
3

Un problème avec les deux approches proposées est qu'elles impliquent une utilisation du processeur inactive (jusqu'à 12% selon mon expérience). Ceci est sous-optimal dans certains cas, par exemple lorsque le comportement modal de l'interface utilisateur est implémenté à l'aide de cette technique.

La variation suivante introduit un délai minimum entre les trames en utilisant un timer (notez qu'il est écrit ici avec Rx mais peut être réalisé avec n'importe quel timer régulier):

 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()));
Jonas Chapuis
la source
1

Depuis l'introduction de asyncet awaitil est maintenant possible d'abandonner le thread d'interface utilisateur à mi-chemin à travers un (anciennement) * bloc de code synchrone en utilisant Task.Delay, par exemple

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

Je vais être honnête, je ne l'ai pas essayé avec le code exact ci-dessus, mais je l'utilise dans des boucles serrées lorsque je place de nombreux éléments dans un ItemsControlqui a un modèle d'article coûteux, ajoutant parfois un petit délai pour donner l'autre trucs sur l'interface utilisateur plus de temps.

Par exemple:

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

Sur Windows Store, lorsqu'il y a une belle transition de thème sur la collection, l'effet est tout à fait souhaitable.

Luke

  • voir les commentaires. Lorsque j'écrivais rapidement ma réponse, je pensais à l'acte de prendre un bloc de code synchrone puis de céder le thread à son appelant, ce qui rend le bloc de code asynchrone. Je ne veux pas reformuler complètement ma réponse car les lecteurs ne peuvent pas voir sur quoi Servy et moi nous chamaillions.
Luke Puplett
la source
"il est maintenant possible d'abandonner le thread d'interface utilisateur à mi-chemin à travers un bloc synchrone" Non, ce n'est pas le cas. Vous venez de rendre le code asynchrone , plutôt que de pomper les messages du thread d'interface utilisateur dans une méthode synchrone. Désormais, une application WPF correctement conçue serait celle qui ne bloque jamais le thread d'interface utilisateur en exécutant de manière synchrone des opérations de longue durée dans le thread d'interface utilisateur, en utilisant l'asynchronie pour permettre à la pompe de messages existante de pomper les messages de manière appropriée.
Servy
@Servy Sous les couvertures, le awaitfera en sorte que le compilateur signe le reste de la méthode async en tant que continuation de la tâche attendue. Cette continuation se produira sur le thread d'interface utilisateur (même contexte de synchronisation). Le contrôle revient ensuite à l'appelant de la méthode async, c'est-à-dire le sous-système d'événements WPF, où les événements s'exécuteront jusqu'à ce que la continuation planifiée s'exécute quelque temps après l'expiration du délai.
Luke Puplett
oui, j'en suis bien conscient. C'est ce qui rend la méthode asynchrone (le contrôle cédant à l'appelant et la planification d'une continuation uniquement). Votre réponse indique que la méthode est synchrone alors qu'elle utilise en fait l' asynchronie pour mettre à jour l'interface utilisateur.
Servy
La première méthode (le code de l'OP) est synchrone, Servy. Le deuxième exemple est juste un conseil pour maintenir l'interface utilisateur en marche lorsque vous êtes dans une boucle ou lorsque vous devez verser des éléments dans une longue liste.
Luke Puplett
Et ce que vous avez fait, c'est rendre le code synchrone asynchrone. Vous n'avez pas maintenu l'interface utilisateur réactive à partir d'une méthode synchrone, comme l'indique votre description ou la réponse le demande.
Servy
0

Créez votre DoEvent () dans 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();

Travaillez bien pour moi!

VinaCaptcha
la source
-2

Réponse à la question initiale: Où est DoEvents?

Je pense que DoEvents est VBA. Et VBA ne semble pas avoir de fonction de veille. Mais VBA a un moyen d'obtenir exactement le même effet qu'un Sleep ou Delay. Il me semble que DoEvents est équivalent à Sleep (0).

En VB et C #, vous avez affaire à .NET. Et la question d'origine est une question C #. En C #, vous utiliseriez Thread.Sleep (0), où 0 correspond à 0 millisecondes.

Vous avez besoin

using System.Threading.Task;

en haut du fichier pour utiliser

Sleep(100);

dans votre code.

Indinfer
la source