Need a wait cursor for long operations.
Mouse.OverrideCursor works in the ViewModel but not really an MVVM pattern.
I have an IsBusy property - how do I get wait cursor in the View?
This kind of works
But I only get the wait cursor over the button
I want the wait cursor on the whole Window
I hacked in Mouse.OverrideCursor = Cursor.Wait in the converter but it feels hacky
Is there a better way to do this?
<Window x:Class="WaitCursor.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WaitCursor"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BVC"/>
<local:ViewModel x:Key="ViewModel"/>
<local:BusyMouseConverter x:Key="BusyToCursorConverter"/>
</Window.Resources>
<Window.Cursor>
<Binding Path="IsBusy" Converter="{StaticResource BusyToCursorConverter}"/>
</Window.Cursor>
<Grid DataContext="{StaticResource ViewModel}">
<Button Content="Search" VerticalAlignment="Top" Command="{Binding SearchCommand}" Margin="2,0,2,2"
Cursor="{Binding Path=IsBusy, Converter={StaticResource BusyToCursorConverter}}"/>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Markup;
namespace WaitCursor
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
[ValueConversion(typeof(bool), typeof(Cursors))]
public class BusyMouseConverter : MarkupExtension, IValueConverter
{
public BusyMouseConverter()
{
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is bool)
{
if ((bool)value)
{
Mouse.OverrideCursor = Cursors.Wait; // this works but it feels hacky
return Cursors.Wait;
}
else
{
Mouse.OverrideCursor = null;
return null;
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is Cursors)
{
if (value == Cursors.Wait)
return true;
else
return false;
}
return null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return instance;
}
private static BusyMouseConverter instance = new BusyMouseConverter();
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.ComponentModel;
using System.Diagnostics;
namespace WaitCursor
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
class ViewModel : ViewModelBase
{
private ICommand _SearchCommand;
public ICommand SearchCommand
{
get
{
if (_SearchCommand == null)
{
_SearchCommand = new RelayCommand(param => this.Search(), null);
}
return _SearchCommand;
}
}
private bool isBusy = false;
public bool IsBusy
{
get { return isBusy; }
set
{
if (isBusy == value)
return;
isBusy = value;
NotifyPropertyChanged("IsBusy");
NotifyPropertyChanged("Cursor");
}
}
private async void Search()
{
IsBusy = true;
//UIServices.SetBusyState();
//Mouse.OverrideCursor = Cursors.Wait;
await Task.Delay(5000);
Debug.WriteLine("Search");
//Mouse.OverrideCursor = null;
IsBusy = false;
}
}
public class RelayCommand : ICommand
{
public RelayCommand(Action<object> execute) : this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
}
}
The problem with this code:
<Window (...)>
<Window.Resources>
<local:ViewModel x:Key="ViewModel"/>
<local:BusyMouseConverter x:Key="BusyToCursorConverter"/>
</Window.Resources>
<Window.Cursor>
<Binding Path="IsBusy" Converter="{StaticResource BusyToCursorConverter}"/>
</Window.Cursor>
<Grid DataContext="{StaticResource ViewModel}">
<Button Cursor="{Binding Path=IsBusy, Converter={StaticResource BusyToCursorConverter}}"/>
</Grid>
</Window>
is that the Binding
will only work for Button
, because it has the proper DataContext
available (inherited from Grid
). Your Window
however has no DataContext
set (null
is the default value), and your binding will not work. In order to make things work you need to move your view-model to the Window.DataContext
(it will then be inherited by all controls nested in the window):
<Window (...)>
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Window.Resources>
<local:BusyMouseConverter x:Key="BusyToCursorConverter"/>
</Window.Resources>
<Window.Cursor>
<Binding Path="IsBusy" Converter="{StaticResource BusyToCursorConverter}"/>
</Window.Cursor>
<Grid>
<Button Cursor="{Binding Path=IsBusy, Converter={StaticResource BusyToCursorConverter}}"/>
</Grid>
</Window>
I'd personally refrain from keeping your view-model as a resource since the resource instance will not be the instance set as Window.DataContext
value, which may lead to unexpected behavior.