MVVM Wait Cursor如何在调用命令期间设置.wait游标?

时间:2012-04-12 06:52:20

标签: wpf mvvm cursor wait

方案: 用户单击视图上的按钮 这将在ViewModel,DoProcessing上调用一个命令 考虑到View和ViewModel的责任,等待光标的设置方式和位置在哪里?

为了清楚起见,我只是想在命令运行时将DEFAULT游标更改为沙漏。命令完成后,光标mut变回箭头。 (这是我正在寻找的同步操作,我希望UI能够阻止)。

我在ViewModel上创建了一个IsBusy属性。如何确保应用程序的鼠标指针发生变化?

8 个答案:

答案 0 :(得分:25)

我在我的申请中成功使用它:

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

这取自here。 Courtsey huttelihut

每次您认为要执行任何耗时的操作时,都需要调用SetBusyState方法。 e.g。

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

当应用程序忙时,这将自动将光标更改为等待光标,并在空闲时恢复正常。

答案 1 :(得分:12)

一个非常简单的方法是简单地绑定到窗口(或任何其他控件)的'Cursor'属性。例如:

XAML:

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

ViewModel Cursor属性(使用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); }
    }

然后只需在需要时更改视图中的光标......

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

        ... some long process ...

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

答案 2 :(得分:2)

命令在视图模型上处理,因此合理的决定将是:

1)创建一个繁忙的指示器服务并将其注入视图模型(这将允许您轻松地用一些讨厌的动画替换光标逻辑)

2)在命令处理程序中调用busy指示符服务以通知用户

我可能错了,但看起来你正试图在UI线程上做一些繁重的计算或I / O.在这种情况下,我强烈建议您在线程池上执行工作。您可以使用Task和TaskFactory轻松地使用ThreadPool

进行工作

答案 3 :(得分:1)

Laurent Bugnion在线(Session的创造者)有一个很棒的MVVM Light(50:58)。 还有deepDive session可用(或here(24:47))。

在至少其中一个中,他使用“忙碌属性”编码繁忙的指示符。

答案 4 :(得分:1)

ViewModel应该只决定它是否忙碌,并且关于使用哪个游标或是否使用其他技术(例如进度条)的决定应该留给View。

另一方面,也不太希望在View中使用代码隐藏处理它,因为理想情况是View应该不具有代码隐藏。

因此,我选择创建一个可在View XAML中使用的类,以指定当ViewModel繁忙时,应将光标更改为Wait。使用UWP + Prism的类定义为:

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

使用它的方式是:

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

答案 5 :(得分:1)

您要在viewmodel中拥有一个bool属性。

    private bool _IsBusy;
    public bool IsBusy 
    {
        get { return _IsBusy; }
        set 
        {
            _IsBusy = value;
            NotifyPropertyChanged("IsBusy");
        }
    }

现在您要设置窗口样式以绑定到它。

<Window.Style>
    <Style TargetType="Window">
        <Setter Property="ForceCursor" Value="True"/>
        <Style.Triggers>
            <DataTrigger Binding="{Binding IsBusy}" Value="True">
                <Setter Property="Cursor" Value="Wait"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Window.Style>

现在,每当执行命令且视图模型处于繁忙状态时,只需设置IsBusy标志并在完成后将其重置。窗口将自动显示等待光标,并在完成后恢复原始光标。

您可以在视图模型中编写命令处理程序函数,如下所示:

    private void MyCommandExectute(object obj) // this responds to Button execute
    {
        try
        {
            IsBusy = true;

            CallTheFunctionThatTakesLongTime_Here();
        }
        finally
        {
            IsBusy = false;
        }
    }

答案 6 :(得分:0)

恕我直言,等待光标逻辑在viewmodel中的命令旁边是完全正常的。

关于更改游标的最佳方法,创建一个更改IDisposable属性的Mouse.OverrideCursor包装器。

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

<强>用法:

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

以上是我发布到此question的解决方案的修订版。

答案 7 :(得分:0)

private static void LoadWindow<T>(Window owner) where T : Window, new()
{
    owner.Cursor = Cursors.Wait;
    new T { Owner = owner }.Show();
    owner.Cursor = Cursors.Arrow;
}