wake-up-neo.com

MVVM Wait Cursor Wie wird der Wait Cursor beim Aufruf eines Befehls gesetzt?

Szenario: Der Benutzer klickt in der Ansicht auf eine Schaltfläche. Dadurch wird im ViewModel ein Befehl aufgerufen, DoProcessing How und Where. Wird der Wait-Cursor unter Berücksichtigung der Zuständigkeiten von View und ViewModel gesetzt?

Zur Verdeutlichung möchte ich nur den DEFAULT-Cursor in eine Sanduhr ändern, während der Befehl ausgeführt wird. Wenn der Befehl abgeschlossen ist, verwandelt sich die Cursormut wieder in einen Pfeil. (Es ist eine Synchronoperation, nach der ich suche, und ich möchte, dass die Benutzeroberfläche blockiert).

Ich habe eine IsBusy-Eigenschaft im ViewModel erstellt. Wie stelle ich sicher, dass sich der Mauszeiger der Anwendung ändert?

20
user1328350

Ich benutze es erfolgreich in meiner Anwendung:

/// <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, System.Windows.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();
        }
    }
}

Dies wurde hier entnommen. Courtsey huttelihut .

Sie müssen die SetBusyState -Methode jedes Mal aufrufen, wenn Sie glauben, dass Sie zeitaufwändige Vorgänge ausführen werden. z.B.

...
UIServices.SetBusyState();
DoProcessing();
...

Dadurch wird Ihr Cursor automatisch in einen Wartecursor geändert, wenn die Anwendung ausgelastet ist, und in den Normalzustand zurückgesetzt, wenn Sie sich im Leerlauf befinden.

25

Eine sehr einfache Methode besteht darin, einfach an die 'Cursor'-Eigenschaft des Fensters (oder eines anderen Steuerelements) zu binden. Zum Beispiel:

XAML:

<Window
    x:Class="Example.MainWindow"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
     Cursor="{Binding Cursor}" />

ViewModel-Cursoreigenschaft (mit Apex.MVVM):

    private NotifyingProperty cursor = new NotifyingProperty("Cursor", typeof(System.Windows.Input.Cursor), System.Windows.Input.Cursors.Arrow);
    public System.Windows.Input.Cursor Cursor
    {
        get { return (System.Windows.Input.Cursor)GetValue(cursor); }
        set { SetValue(cursor, value); }
    }

Dann ändern Sie bei Bedarf einfach den Cursor in Ihrer Ansicht ...

    public void DoSomethingLongCommand()
    {
        Cursor = System.Windows.Input.Cursors.Wait;

        ... some long process ...

        Cursor = System.Windows.Input.Cursors.Arrow;
    }
11
bradcarman

Der Befehl wird im Ansichtsmodell verarbeitet. Die sinnvolle Entscheidung wäre also, Folgendes zu tun:

1) Erstellen Sie einen Belegtanzeigedienst und fügen Sie ihn in das Ansichtsmodell ein (dies ermöglicht es Ihnen, die Cursorlogik leicht durch eine unangenehme Animation zu ersetzen).

2) Rufen Sie im Befehlshandler den Besetztanzeigedienst auf, um den Benutzer zu benachrichtigen

Ich könnte mich irren, aber es sieht so aus, als würden Sie versuchen, einige schwere Berechnungen oder E/A-Vorgänge im UI-Thread durchzuführen. Ich empfehle Ihnen dringend, in diesem Fall Arbeiten am Thread-Pool durchzuführen. Mit Task und TaskFactory können Sie die Arbeit mit ThreadPool einfach umbrechen

2
v00d00

Es gibt eine großartige Session (um 50:58) von Laurent Bugnion online (Schöpfer von MVVM Light ). Es gibt auch eine deepDive session verfügbar (alternativ hier (um 24:47)).

In mindestens einem von ihnen codiert er live einen Besetzt-Indikator mit der Bezeichnung BusyProperty.

1
Boas Enkler

Das ViewModel sollte nur entscheiden, ob es ausgelastet ist und welcher Cursor verwendet werden soll oder ob eine andere Technik wie eine Fortschrittsanzeige verwendet werden soll. Dies sollte der Ansicht überlassen bleiben.

Andererseits ist es auch nicht wünschenswert, mit Code-Behind in der Ansicht umzugehen, da Views im Idealfall keinen Code-Behind haben sollten.

Aus diesem Grund habe ich mich für eine Klasse entschieden, die in der View-XAML verwendet werden kann, um anzugeben, dass der Cursor in Wait geändert werden soll, wenn das ViewModel ausgelastet ist. Unter Verwendung von UWP + Prism lautet die Klassendefinition:

public class CursorBusy : FrameworkElement
{
    private static CoreCursor _arrow = new CoreCursor(CoreCursorType.Arrow, 0);
    private static CoreCursor _wait = new CoreCursor(CoreCursorType.Wait, 0);

    public static readonly DependencyProperty IsWaitCursorProperty =
        DependencyProperty.Register(
            "IsWaitCursor",
            typeof(bool),
            typeof(CursorBusy),
            new PropertyMetadata(false, OnIsWaitCursorChanged)
    );

    public bool IsWaitCursor
    {
        get { return (bool)GetValue(IsWaitCursorProperty); } 
        set { SetValue(IsWaitCursorProperty, value); }
    }

    private static void OnIsWaitCursorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        CursorBusy cb = (CursorBusy)d;
        Window.Current.CoreWindow.PointerCursor = (bool)e.NewValue ? _wait : _arrow;
    }
}

Und die Art, es zu benutzen, ist:

<mvvm:SessionStateAwarePage
    x:Class="Orsa.Views.ImportPage"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    xmlns:mvvm="using:Prism.Windows.Mvvm"
    xmlns:local="using:Orsa"
    xmlns:d="http://schemas.Microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mvvm:ViewModelLocator.AutoWireViewModel="True"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid>
    <Grid.RowDefinitions>
    .
    .
    </Grid.RowDefinitions>
    <local:CursorBusy IsWaitCursor="{Binding IsBusy}"/>
    (other UI Elements)
    .
    .
    </Grid>
</mvvm:SessionStateAwarePage>
1
Bruce
private static void LoadWindow<T>(Window owner) where T : Window, new()
{
    owner.Cursor = Cursors.Wait;
    new T { Owner = owner }.Show();
    owner.Cursor = Cursors.Arrow;
}
0
LawMan

IMHO, dass es völlig in Ordnung ist, wenn sich die Wait-Cursor-Logik im Ansichtsmodell neben dem Befehl befindet.

Um den Cursor am besten zu ändern, erstellen Sie einen Wrapper für IDisposable, der die Eigenschaft Mouse.OverrideCursor ändert.

public class StackedCursorOverride : IDisposable
{
    private readonly static Stack<Cursor> CursorStack;

    static StackedCursorOverride()
    {
        CursorStack = new Stack<Cursor>();
    }

    public StackedCursorOverride(Cursor cursor)
    {            
        CursorStack.Push(cursor);
        Mouse.OverrideCursor = cursor;            
    }

    public void Dispose()
    {
        var previousCursor = CursorStack.Pop();
        if (CursorStack.Count == 0)
        {
            Mouse.OverrideCursor = null;
            return;
        }

        // if next cursor is the same as the one we just popped, don't change the override
        if ((CursorStack.Count > 0) && (CursorStack.Peek() != previousCursor))
            Mouse.OverrideCursor = CursorStack.Peek();             
    }
}

Verwendung:

using (new StackedCursorOverride(Cursors.Wait))
{
     // ...
}

Das obige ist eine überarbeitete Version der Lösung, die ich zu dieser Frage gepostet habe.

0
Dennis