I have a WPF/MVVM app, which consists of one window with a few buttons.
Each of the buttons triggers a call to an external device (an USB missile launcher), which takes
Ok the CanExecute
method will not work because the click will immediately put you into your long-running task.
So here's how I would do it:
Make your view model implement INotifyPropertyChanged
Add a property called something like:
public bool IsBusy
{
get
{
return this.isBusy;
}
set
{
this.isBusy = value;
RaisePropertyChanged("IsBusy");
}
}
Bind your buttons to this property in this manner:
<Button IsEnabled="{Binding IsBusy}" .. />
In your ShowMessage/CallExternal device methods add the line
IsBusy = true;
Should do the trick
Try this:
//Declare a new BackgroundWorker
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (o, ea) =>
{
try
{
// Call your device
// If ou need to interact with the main thread
Application.Current.Dispatcher.Invoke(new Action(() => //your action));
}
catch (Exception exp)
{
}
};
//This event is raise on DoWork complete
worker.RunWorkerCompleted += (o, ea) =>
{
//Work to do after the long process
disableGui = false;
};
disableGui = true;
//Launch you worker
worker.RunWorkerAsync();
I think this is a bit more elegant:
XAML:
<Button IsEnabled="{Binding IsGuiEnabled}" Content="Simulate external device" Command="{Binding DeviceCommand}" Height="50" Margin="0 10"></Button>
C# (using async & await):
public class MainWindowViewModel : INotifyPropertyChanged
{
private bool isGuiEnabled;
/// <summary>
/// True to enable buttons, false to disable buttons.
/// </summary>
public bool IsGuiEnabled
{
get
{
return isGuiEnabled;
}
set
{
isGuiEnabled = value;
OnPropertyChanged("IsGuiEnabled");
}
}
public ICommand DeviceCommand
{
get
{
return new RelayCommand(this.CallExternalDevice, this.IsGuiEnabled);
}
}
private async void CallExternalDevice(object obj)
{
IsGuiEnabled = false;
try
{
await Task.Factory.StartNew(() => Thread.Sleep(3000));
}
finally
{
IsGuiEnabled = true;
}
}
}
Because you run CallExternalDevice()
on the main thread, the main thread won't have time to update any UI until that job is done, which is why the buttons remain enabled. You could start your long-running operation in a separate thread, and you should see that the buttons are disabled as expected:
private void CallExternalDevice(object obj)
{
this.disableGui = true;
ThreadStart work = () =>
{
// simulate call to external device (USB missile launcher),
// which takes a few seconds and pauses the app
Thread.Sleep(3000);
this.disableGui = false;
Application.Current.Dispatcher.BeginInvoke(new Action(() => CommandManager.InvalidateRequerySuggested()));
};
new Thread(work).Start();
}