web-dev-qa-db-fra.com

Comment afficher un curseur d'attente lorsque l'application WPF est occupée par la liaison de données

J'ai une application WPF utilisant le modèle MVVM qui doit parfois afficher un curseur d'attente lorsqu'il est occupé à faire quelque chose que l'utilisateur doit attendre. Merci à une combinaison de réponses sur cette page: afficher Hourglass lorsque l'application est occupée , j'ai une solution qui fonctionne presque (bien qu'elle ne soit pas vraiment MVVM dans l'esprit). Chaque fois que je fais quelque chose de long dans mes modèles de vue, je fais ceci:

using (UiServices.ShowWaitCursor())
{
.. do time-consuming logic
this.SomeData = somedata;
}

(ShowWaitCursor () renvoie un IDisposable qui montre le waitcursor jusqu'à ce qu'il soit éliminé) La dernière ligne de mon exemple est l'endroit où j'ai défini une propriété. Cette propriété est liée dans mon XAML, par exemple comme ça:

<ItemsControl ItemsSource="{Binding SomeData}" /> 

Cependant, comme il peut s'agir d'une longue liste d'objets et parfois de modèles de données complexes, etc., la liaison et le rendu réels prennent parfois beaucoup de temps. Étant donné que cette liaison a lieu en dehors de mon instruction using, le curseur d'attente disparaîtra avant la fin de l'attente réelle pour l'utilisateur.

Ma question est donc de savoir comment faire un curseur d'attente dans une application WPF MVVM qui prend en compte la liaison de données?

29
T.J.Kjaer

La réponse d'Isak n'a pas fonctionné pour moi, car elle n'a pas résolu le problème de savoir comment agir lorsque l'attente réelle est terminée pour l'utilisateur. J'ai fini par faire ceci: chaque fois que je commence à faire quelque chose de chronophage, j'appelle une méthode d'assistance. Cette méthode d'assistance modifie le curseur, puis crée un DispatcherTimer qui sera appelé lorsque l'application est inactive. Quand il est appelé, il remet le curseur de la souris en arrière:

/// <summary>
///   Contains helper methods for UI, so far just one for showing a waitcursor
/// </summary>
public static class UiServices
{

     /// <summary>
     ///   A value indicating whether the UI is currently busy
     /// </summary>
     private static bool IsBusy;

     /// <summary>
     /// Sets the busystate as busy.
     /// </summary>
     public static void SetBusyState()
     {
          SetBusyState(true);
     }

     /// <summary>
     /// Sets the busystate to busy or not busy.
     /// </summary>
     /// <param name="busy">if set to <c>true</c> the application is now busy.</param>
     private static void SetBusyState(bool busy)
     {
          if (busy != IsBusy)
          {
               IsBusy = busy;
               Mouse.OverrideCursor = busy ? Cursors.Wait : null;

               if (IsBusy)
               {
                   new DispatcherTimer(TimeSpan.FromSeconds(0), DispatcherPriority.ApplicationIdle, dispatcherTimer_Tick, Application.Current.Dispatcher);
               }
          }
     }

     /// <summary>
     /// Handles the Tick event of the dispatcherTimer control.
     /// </summary>
     /// <param name="sender">The source of the event.</param>
     /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
     private static void dispatcherTimer_Tick(object sender, EventArgs e)
     {
          var dispatcherTimer = sender as DispatcherTimer;
          if (dispatcherTimer != null)
          {
              SetBusyState(false);
              dispatcherTimer.Stop();
          }
     }
}
85
T.J.Kjaer

Je n'ai donc pas aimé utiliser OverrideCursor car j'avais plusieurs fenêtres et je voulais que celles qui n'exécutaient pas quelque chose aient le curseur fléché normal.

Voici ma solution:

<Window.Style>
    <Style TargetType="Window">
        <Style.Triggers>
            <DataTrigger Binding="{Binding IsBusy}" Value="True">
                <Setter Property="Cursor" Value="Wait" />
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Window.Style>
<Grid>
    <Grid.Style>
        <Style TargetType="Grid">
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsBusy}" Value="True">
                    <Setter Property="IsHitTestVisible" Value="False" /> <!-- Ensures wait cursor is active everywhere in the window -->
                    <Setter Property="IsEnabled" Value="False" /> <!-- Makes everything appear disabled -->
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Grid.Style>
    <!-- Window controls go here -->
</Grid>
9
Jas Laferriere

Ce que j'ai fait dans le passé, c'est de définir des propriétés booléennes dans le viewmodel qui indique qu'un long calcul est en cours. Par exemple IsBusy qui est défini sur true lorsqu'il fonctionne et sur false lorsqu'il est inactif.

Ensuite, dans la vue, je me lie à cela et affiche une barre de progression ou un spinner ou similaire pendant que cette propriété est vraie. Personnellement, je n'ai jamais réglé le curseur en utilisant cette approche, mais je ne vois pas pourquoi ce ne serait pas possible.

Si vous voulez encore plus de contrôle et qu'un simple booléen ne suffit pas, vous pouvez utiliser VisualStateManager que vous conduisez à partir de votre modèle de vue. Avec cette approche, vous pouvez spécifier en détail l'apparence de l'interface utilisateur en fonction de l'état du modèle de vue.

7
Isak Savo

En plus de la contribution d'Isak Savo, vous voudrez peut-être consulter le blog de Brian Keating pour un échantillon de travail.

1
Izmoto