I have a WPF app that simply contains a Button and a Textbox to display some output. When the user clicks the Button, a thread starts which disables the Button, prints stuff to the output Textbox, then the thread stops (at which point I want the Button to be enabled again).
The application appears to disable the Button properly, as well as update the Textbox properly. However, it always fails to re-enable the Button properly when the thread completes! Can anyone tell me what I'm doing wrong?
Here's a snippet of my xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Grid.Row="0" HorizontalAlignment="Center" Command="{Binding ExecuteCommand}">E_xecute</Button>
<Label Grid.Row="1" Content="Output Window:" HorizontalAlignment="Left"/>
<TextBlock Grid.Row="2" Text="{Binding Output}"/>
</Grid>
Here's my ViewModel code (I'm using Josh Smith's MVVM design):
public class WindowViewModel : ViewModelBase
{
private bool _threadStopped;
private RelayCommand _executeCommand;
private string _output;
public WindowViewModel()
{
_threadStopped = true;
}
public string Output { get { return _output; } set { _output = value; OnPropertyChanged("Output"); } }
public ICommand ExecuteCommand
{
get
{
if (_executeCommand == null)
{
_executeCommand = new RelayCommand(p => this.ExecuteThread(p), p => this.CanExecuteThread);
}
return _executeCommand;
}
}
public bool CanExecuteThread
{
get
{
return _threadStopped;
}
set
{
_threadStopped = value;
}
}
private void ExecuteThread(object p)
{
ThreadStart ts = new ThreadStart(ThreadMethod);
Thread t = new Thread(ts);
t.Start();
}
private void ThreadMethod()
{
CanExecuteThread = false;
Output = string.Empty;
Output += "Thread Started: Is the 'Execute' button disabled?\r\n";
int countdown = 5000;
while (countdown > 0)
{
Output += string.Format("Time remaining: {0}...\r\n", countdown / 1000);
countdown -= 1000;
Thread.Sleep(1000);
}
CanExecuteThread = true;
Output += "Thread Stopped: Is the 'Execute' button enabled?\r\n";
}
}
You'll need to help WPF know that the executable state of the command has changed. The simple way to do this is:
CommandManager.InvalidateRequerySuggested()
inside CanExecuteThread:
set
{
_threadStopped = value;
CommandManager.InvalidateRequerySuggested()
}
EDIT (now that I have time): the actual problem is likely that you're not notifying when the CanExecuteThread
property changes. It should raise PropertyChanged
in order for WPF to detect the change:
public bool CanExecuteThread
{
get { return _threadStopped; }
set
{
if (_threadStopped != value)
{
_threadStopped = value;
this.OnPropertyChanged(() => this.CanExecuteThread);
}
}
}
The above assumes your ViewModel
base class has an OnPropertyChanged
method.
That said, I also wanted to point out that you could simplify things by simply using a BackgroundWorker
:
public class WindowViewModel : ViewModel
{
private readonly BackgroundWorker backgroundWorker;
public WindowVieWModel()
{
backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += delegate
{
// do work here (what's currently in ThreadMethod)
};
backgroundWorker.RunWorkerCompleted += delegate
{
// this will all run on the UI thread after the work is done
this.OnPropertyChanged(() => this.CanExecuteThread);
};
}
...
public bool CanExecuteThread
{
get { !this.backgroundWorker.IsBusy; }
}
private void ExecuteThread(object p)
{
// this will kick off the work
this.backgroundWorker.RunWorkerAsync();
// this property will have changed because the worker is busy
this.OnPropertyChanged(() => this.CanExecuteThread);
}
}
You could refactor this further to be even nicer, but you get the idea.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With