场景:
考虑到 View 和 ViewModel 的职责,如何以及在哪里设置等待光标?
需要明确的是,我只是想在命令运行时将默认光标更改为沙漏。 命令完成后,光标将变回箭头。 (这是我正在寻找的同步操作,并且我希望 UI 被阻止)。
我在 ViewModel 上创建了一个 IsBusy 属性。 如何确保Application的鼠标指针发生变化?
我在我的应用程序中成功使用它:
/// <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();
...
这会在应用程序繁忙时自动将光标更改为等待光标,并在空闲时恢复正常。
一个非常简单的方法是简单地绑定到窗口(或任何其他控件)的“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;
}
您希望在视图模型中拥有
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;
}
}
命令是在视图模型上处理的,因此合理的决定是执行以下操作:
1)创建一个繁忙的指示器服务并将其注入到视图模型中(这将允许您轻松地用一些令人讨厌的动画替换光标逻辑)
2)在命令处理程序中调用忙指示器服务来通知用户
我可能是错的,但看起来你正在尝试在 UI 线程上进行一些繁重的计算或 I/O。在这种情况下,我强烈建议您在线程池上执行工作。您可以使用 Task 和 TaskFactory 轻松包装 ThreadPool 的工作
Laurent Bugnion 在线(MVVM Light 的创建者)举办了一场精彩的会议(50:58)。 还有一个deepDive 课程(或者这里(24:47))。
至少在其中一个中,他使用 is BusyProperty 编写了一个繁忙指示器。
ViewModel 应该只决定它是否繁忙,而关于使用什么光标,或者是否使用进度条等其他技术的决定应该留给 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>
恕我直言,等待光标逻辑位于视图模型中的命令旁边是完全可以的。
至于更改光标的最佳方法,请创建一个更改
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))
{
// ...
}
以上是我发布到此问题的解决方案的修订版本。
private static void LoadWindow<T>(Window owner) where T : Window, new()
{
owner.Cursor = Cursors.Wait;
new T { Owner = owner }.Show();
owner.Cursor = Cursors.Arrow;
}
我成功地使用了一个简单的替代方案。您可以将此布尔值到光标转换器添加到您的应用程序,然后使用转换器将窗口的光标绑定到视图模型的 IsBusy 属性:
public class BooleanToBusyCursorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool booleanValue)
{
return booleanValue ? Cursors.Wait : Cursors.Arrow;
}
return Cursors.Arrow;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}