102 votes

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

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

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

le résultat 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 j'ai pensé résoudre en utilisant Application.DoEvents(); mais... il n'existe pas a priori dans .NET 4.

Que faire ?

8 votes

L'enfilage ? Application.DoEvents() était le substitut du pauvre pour écrire des applications correctement multithreadées et, de toute façon, une pratique extrêmement pauvre.

5 votes

Je sais que c'est pauvre et mauvais, mais je préfère quelque chose que rien du tout.

1 votes

150voto

Fredrik Hedblad Points 42772

Essayez quelque chose comme ceci

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

2 votes

J'ai même écrit une méthode d'extension pour l'application :) public static void DoEvents(this Application a)

0 votes

@serhio : Méthode d'extension soignée :)

4 votes

Je dois cependant remarquer que dans l'application réelle Application.Current est parfois nulle... donc peut-être que ce n'est pas tout à fait équivalent.

65voto

flq Points 11937

Eh bien, je viens de rencontrer un cas où je commence à travailler sur une méthode qui s'exécute sur le thread du Dispatcher, et il faut qu'elle se bloque sans bloquer le thread de l'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 )

Certains préfèreront peut-être une méthode unique 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);
}

0 votes

Bien trouvé ! Cela semble plus sûr que l'implémentation suggérée par Meleak. J'ai trouvé un article de blog à ce sujet

2 votes

@HugoRune Cet article de blog indique que cette approche n'est pas nécessaire et qu'il faut utiliser la même mise en œuvre que Meleak.

1 votes

@Lukazoid Pour autant que je puisse dire, la mise en œuvre simple peut provoquer des verrouillages difficiles à tracer. (Je ne suis pas sûr de la cause, peut-être que le problème vient du code dans la file d'attente du distributeur qui appelle à nouveau DoEvents, ou du code dans la file d'attente du distributeur qui génère d'autres trames du distributeur). Dans tous les cas, la solution avec exitFrame n'a pas présenté de tels problèmes, donc je recommande celle-ci. (Ou, bien sûr, ne pas utiliser doEvents du tout)

26voto

Dillie-O Points 16780

L'ancienne méthode Application.DoEvents() a été dépréciée dans WPF en faveur de l'utilisation d'une méthode de type Dispatcher ou un Fil de travail d'arrière-plan pour effectuer le traitement que vous avez décrit. Voir les liens pour un couple d'articles sur la façon d'utiliser les deux objets.

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

1 votes

Je sais que c'est pauvre et mauvais, mais je préfère quelque chose que rien du tout... Comment puis-je utiliser Dispatcher dans ma situation ?

3 votes

Je comprends votre situation. J'étais dans cette situation lorsque j'ai écrit ma première application WPF, mais j'ai pris le temps d'apprendre la nouvelle bibliothèque et je m'en suis beaucoup mieux porté sur le long terme. Je vous recommande vivement de prendre le temps nécessaire. Pour ce qui est de votre cas particulier, il me semble que vous souhaitez que le distributeur se charge de l'affichage des coordonnées à chaque fois que votre événement de clic se déclenche. Vous devrez lire davantage sur le répartiteur pour connaître l'implémentation exacte.

0 votes

Non, j'appellerais Application.DoEvents après avoir incrémenté myScaleTransform.ScaleX. Je ne sais pas si cela est possible avec Dispatcher.

6voto

serhio Points 9649
myCanvas.UpdateLayout();

semble fonctionner aussi bien.

0 votes

Je vais utiliser cette méthode car elle me semble beaucoup plus sûre, mais je vais garder les DoEvents pour d'autres cas.

0 votes

Je ne sais pas pourquoi mais ça ne marche pas pour moi. DoEvents() fonctionne bien.

0 votes

Dans mon cas, j'ai dû faire cela ainsi que le DoEvents()

3voto

Jonas Chapuis Points 21

Un problème avec les deux approches proposées est qu'elles impliquent une utilisation inactive du CPU (jusqu'à 12% dans mon expérience). Cela est sous-optimal dans certains cas, par exemple lorsque le comportement modal de l'interface utilisateur est mis en œuvre en utilisant 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 ordinaire) :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X