Reputation: 607
I have a MVVM based application(Not using any particular mvvm library or framework) with a few buttons I want to enable and disable depending on if a method is running. I set up a bool property on the ViewModel, "ProcessRunning":
bool _ProcessRunning = false;
/// <summary>
/// Whether A Process Is Currently Taking Place
/// </summary>
public bool ProcessRunning
{
get
{
return _ProcessRunning;
}
set
{
if (_ProcessRunning != value)
{
_ProcessRunning = value;
RaisePropertyChanged("ProcessRunning");
}
}
}
Then I have a series of buttons with styles binding them to this property as such:
Button:
<Button Name="btn_AddMore" Content="More" Style="{StaticResource ExecuteButtons}"
ToolTip="Enumerate Customer Folders To Get More Customer And Job Options"
Command="{Binding AddFolderOptions}"/>
Style:
<Style TargetType="Button" x:Key="ExecuteButtons">
<Setter Property="Width" Value="80"/>
<Setter Property="Height" Value="40"/>
<Setter Property="FontFamily" Value="Arial"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="0,0,0,0"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="FontWeight" Value="700"/>
<Setter Property="BorderBrush" Value="#FFA0A0A0"/>
<Setter Property="Margin" Value="12,0,0,0"/>
<Setter Property="Background" Value="#FFEEEEEE"/>
<Setter Property="IsEnabled" Value="{Binding ProcessRunning, Converter={StaticResource Inverse}, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Cursor" Value="Hand"/>
</Trigger>
</Style.Triggers>
</Style>
And the binding works as expected apart from when I change it during a method that does other things as such:
private void AddOptionsFromFolders(object Parameter)
{
try
{
ProcessRunning = true;
Transfer_Workers.Program.Processes.AddFolderChoices(ref _LocalSettings);
In this case the View isn't updated until after AddOptionsFromFolders has exited.
I found through a couple other questions that the UI thread is being block during this method call and doesn't get freed up to update until the method is complete so I tried this:
private void AddOptionsFromFolders(object Parameter)
{
try
{
ProcessRunning = true;
//Transfer_Workers.Program.Processes.AddFolderChoices(ref _LocalSettings);
Thread test = new Thread(() => { Thread.Sleep(2000); ProcessRunning = false; });
test.Start();
In this case the UI is updated when ProcessRunning is set to true but when the "test" thread completes and should set ProcessRunning to false, the UI doesn't update.
I have my ViewModel inherited from a main view model and that from a base view model where I have RaisePropertyChanged declared:
internal void RaisePropertyChanged(string prop)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
I can't seem to figure out why the UI doesn't update when ProcessUpdated is set back to false. No Exceptions are thrown and there are no warnings or errors within the output window (Visual Studio 2013 Pro).
My intended outcome is that while the method is running the buttons will be disabled and while it's not the buttons are enabled. If anyone can shed some light on the issue at hand here or even suggest a different way of notifying the View of a property being changed in a more async fashion I would greatly appreciate it.
Also if there's anything I wasn't clear on or information I left out just ask and I will get that to you asap.
Upvotes: 0
Views: 723
Reputation: 31616
One should avoid reading and writing directly to variables which may be tied to the GUI thread.
For this situation, inside and at the end of your thread, use the dispatcher to notify the gui thread of the change such as
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Background,
new Action(() => ProcessRunning = false));
I ran into similar when I tried to read from a background thread. Read about my experience here C# WPF: Linq Fails in BackgroundWorker DoWork Event
Also keep in mind that with multi-threading, you may be running into a race condition.
Can one truly eliminate that another thread may be changing the flag as well? Or that the background thread is actually finishing faster than expected and setting the value first before it was initially set?
Race conditions can be insidious and they do occur.
Upvotes: 0
Reputation: 26213
This is because you're updating the property from a background thread. The UI can't be updated from a thread other than the UI thread, so your property change is ignored.
You can do this using async/await:
ProcessRunning = true;
await Task.Run(() =>
{
// do work here
});
ProcessRunning = false;
The synchronisation context is captured when the Task
is awaited, so it resumes execution on the UI thread.
For disabling buttons, however, you should probably just ensure that CanExecute
on your command returns false until the command has finished executing.
Upvotes: 2