web-dev-qa-db-fra.com

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

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 constater, 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?

79
serhio

L'ancienne méthode Application.DoEvents () était obsolète dans WPF au lieu d'utiliser Dispatcher ou Background Worker Thread pour effectuer le traitement que vous 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 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 offerts par WPF.

25
Dillie-O

Essayez quelque chose comme ça

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

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 d'interface utilisateur. 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;
}

(extrait de méthode Dispatcher.PushFrame )

53
flq

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

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

semble fonctionner aussi bien.

5
serhio

Un problème avec les deux approches proposées est qu’elles entraînent une utilisation ininterrompue de la CPU (jusqu’à 12% selon mon expérience). Ceci est parfois sous-optimal, par exemple lorsque le comportement de l'interface utilisateur modale est implémenté à l'aide de cette technique.

La variante suivante introduit un délai minimum entre les images à l'aide d'un temporisateur (notez qu'il est écrit ici avec Rx mais qu'il peut être atteint avec n'importe quel temporisateur normal):

 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

Depuis l'introduction de async et await, il est maintenant possible de renoncer au thread d'interface utilisateur à travers un bloc de code synchrone * (anciennement) * 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()));
}

Soyons honnêtes, je n'ai pas essayé le code exact ci-dessus, mais je l'utilise en boucle lorsque je place de nombreux éléments dans un ItemsControl qui comporte un modèle d'élément coûteux, en ajoutant parfois un petit délai pour donner plus de temps aux autres éléments de l'interface utilisateur.

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 le Windows Store, lorsqu’il ya une transition de thème Nice sur la collection, l’effet est tout à fait souhaitable.

Luke

  • voir les commentaires. Lorsque j’écrivais rapidement ma réponse, je pensais au fait de prendre un bloc de code synchrone puis de rendre le fil à son appelant, ce qui rend le bloc de code asynchrone. Je ne veux pas reformuler complètement ma réponse parce qu'alors les lecteurs ne peuvent pas voir pourquoi Servy et moi nous disputions.
1
Luke Puplett