MVVM等待光标如何在调用命令期间设置.wait光标?

30

场景:

  • 用户在视图上点击按钮
  • 这会调用ViewModel中的一个命令DoProcessing

考虑到View和ViewModel的职责,如何以及在哪里设置等待光标?

明确一下,我只想将默认光标更改为沙漏形状,而命令正在运行时。当命令完成后,光标必须变回箭头。(我想要的是同步操作,并且希望UI被阻塞)。

我在ViewModel中创建了一个IsBusy属性。如何确保应用程序的鼠标指针更改?

8个回答

36

我在我的应用程序中成功使用它:

/// <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();
        }
    }
}
这段内容摘自这里,感谢huttelihut
每当您认为将执行任何耗时操作时,需要调用SetBusyState方法。例如:
...
UIServices.SetBusyState();
DoProcessing();
...

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


你如何在进度条中使用它?(例如:modernui进度条) - sexta13
@sexta13 - 不太确定那是怎么工作的。如果您能发布一个样例应用程序的链接并描述您想要它如何工作,我可以尝试帮助您。 - Shakti Prakash Singh
1
今天晚些时候我回家后,我会发布那个问题,但是根据我所读的内容,我必须将IsBusy绑定到Visibility...但稍后我会再试着问一遍 :) 预先感谢。 - sexta13
<ProgressBar Minimum="0" Maximum="1" Height="16" IsIndeterminate="True" Margin="0,0,0,16" /> 如果你有这个,你怎么将isbusy绑定到可见性上(例如)? - sexta13
2
我不确定为什么人们在踩这个答案。今天我收到了6个踩的票,没有任何评论,而且是在一年半之后发布的?至少留下一个评论来解释为什么要踩这个答案,这样我才能改进它。 - Shakti Prakash Singh

14

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

XAML:


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

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

我认为你需要将NotifyingProperty更改为DependencyProperty。 - Sasha
1
注意,我正在使用Apex库(http://apex.codeplex.com/)。 我在视图模型中定义光标作为属性。 我将其设置为NotifyingProperty,以便在更改时可以通知视图。 这里不需要DependencyProperty。 - bradcarman

7
您想在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;
        }
    }

你测试过这个吗?我尝试了这种方法,但显然,在MyCommandExectute完成之后才更新UI,所以实际上什么也没有发生。 - user2727133
1
@user2727133 是的,我试过了,它非常好用,这是一个非常整洁的解决方案,你可以将其插入到你的基类中并重复使用。 - zar
2
谢谢你的提示,我已经将它添加到基类中,看起来运行良好 - 但只有在CallTheFunctionThatTakesLongTime_Here()是异步的情况下。 - user2727133
这很棒,因为虚拟机不必引用鼠标,所以虚拟机在目标框架中不需要窗口。 - Paul McCarthy

2
命令在视图模型上处理,因此合理的决定是执行以下操作:
1)创建一个繁忙指示器服务并将其注入到视图模型中(这将允许您轻松地用一些令人不快的动画替换光标逻辑)
2)在命令处理程序中调用繁忙指示器服务以通知用户
我可能错了,但看起来你正在尝试在UI线程上进行一些重量级的计算或I/O。在这种情况下,我强烈建议您在线程池上执行工作。您可以使用Task和TaskFactory轻松地将工作包装在ThreadPool中。

1

有一个由Laurent Bugnion在线提供的精彩Session(在50:58处)(MVVM Light的创作者)。

还有一个深入探讨的会话可用(或者在此处(在24:47处))。

其中至少有一个他使用isBusyProperty现场编写了一个繁忙指示器。


在ViewModel上同意IsBusy属性。 - GazTheDestroyer
1
这个内容如何显示在主窗口上,而不仅仅是对应于视图模型的视图上,即视图是主壳体的子级。 - Banoona

1
ViewModel 应该只决定是否繁忙,关于使用什么光标或者使用其他技术例如进度条的决定应该交给 View 来处理。另一方面,在 View 中使用代码后台处理也不是很理想,因为最理想的是 View 不应该有代码后台。
因此,我选择创建一个可以在 View XAML 中使用的类来指定当 ViewModel 繁忙时更改光标为等待状态。使用 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>

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

0

我认为,在视图模型中将等待光标逻辑放在命令旁边是完全可以的。

至于最佳的更改光标方式,可以创建一个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))
{
     // ...
}

以上是我对这个问题发布的解决方案的修订版本。


8
我不是很确定。等待光标只是繁忙指示器的一种实现方式。ViewModel 不应该知道 View 的实现方式。如果 View 想以其他方式表示繁忙,如动画或简单文本消息怎么办?一个简单的 IsBusy 属性允许 View 自行实现其方式。 - GazTheDestroyer
@GazTheDestroyer:没错,我从来没有这样想过。 - Dennis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接