I have an WPF screen with 2 labels: busy and ready.
When I run an update within a method call (RunProcess), triggered with a button, I want to change the visibility at the start to show the busy label and hide the ready label, and show ready + hide busy at the end of the process which can take a few minutes to run.
I defined the Binding in my XAML
<Label Name="Busy" Content="Busy" Visibility="{Binding Path=BusyVisibility, UpdateSourceTrigger=PropertyChanged}" />
<Label Name="Ready" Content="Busy" Visibility="{Binding Path=ReadyVisibility, UpdateSourceTrigger=PropertyChanged}" />
XAML INotifyPropertyChanged class:
public class Status : INotifyPropertyChanged
{
public Status()
{
_readyVisibility = true;
_busyVisibility = false;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public Visibility ReadyVisibility
{
get { return _readyVisibility; }
set
{
_readyVisibility = value;
NotifyPropertyChanged("ReadyVisibility");
}
}
private Visibility _readyVisibility;
//Same for the Busy label..
}
and my screen XAML class:
public partial class MainWindow : UserControl
{
private ScreenStatusVisibility _screenStatusVisibility = new ScreenStatusVisibility();
public MainWindow()
{
InitializeComponent();
DataContext = _screenStatusVisibility;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
ReadyVisibility = false;
BusyVisibility = true;
//processing ..
ReadyVisibility = true;
BusyVisibility = false;
}
}
When I click on the button, the Busy label is never hidden, even when I put a sleep or debug step by step. It seems that WPF doesn’t update the UI within a single method call.
GaryF65 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
3
What should help your problem, is to make the click handler async
and await your processing task. The excellent comment added by Selvin articulates the underlying issue of blocking the UI thread. Making the handler async
as shown below will prevent that from happening.
So, let’s modify your code into an example that will run. Clone
You’re showing label visibility bound to properties:
<Label Name="Busy" Content="Busy" Visibility="{Binding Path=BusyVisibility, UpdateSourceTrigger=PropertyChanged}" />
<Label Name="Ready" Content="Ready" Visibility="{Binding Path=ReadyVisibility, UpdateSourceTrigger=PropertyChanged}" Grid.Row="0" />
From this it’s a bit unclear whether these are looking at the correct binding context because on one hand your code reads ReadyVisibility = false
which implies a bool, when the property should be of type Visibility
. So I’m going to set up a functional binding context just to demo.
OK, so let’s add an IsEnabled
binding for the Button
.
<Button IsEnabled="{Binding IsButtonEnabled}" Content="Test" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" Height="50" Width="100" Click="Button_Click"/>
Binding setup for illustration
For demonstration purposes only, let’s make some sufficiently functional bindings so that we can look at what might be the real issue.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
base.DataContext = new Status();
}
new Status DataContext =>(Status)base.DataContext;
}
public class Status : INotifyPropertyChanged
{
public Visibility ReadyVisibility
{
get => _readyVisibility;
set
{
if (!Equals(_readyVisibility, value))
{
_readyVisibility = value;
OnPropertyChanged();
OnPropertyChanged(nameof(BusyVisibility));
OnPropertyChanged(nameof(IsButtonEnabled));
}
}
}
Visibility _readyVisibility = Visibility.Visible;
public Visibility BusyVisibility =>
Equals(ReadyVisibility, Visibility.Visible) ? Visibility.Hidden : Visibility.Visible;
public bool IsButtonEnabled => Equals(ReadyVisibility, Visibility.Visible);
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public event PropertyChangedEventHandler? PropertyChanged;
}
Then what I believe would go a long way toward making your click handler work the way you intend, is to make it async
so that you can await
the processing task.
public partial class MainWindow : Window
{
.
.
.
private async void Button_Click(object sender, RoutedEventArgs e)
{
DataContext.ReadyVisibility = Visibility.Hidden;
// Processing ..
await Task.Delay(TimeSpan.FromSeconds(2.5));
DataContext.ReadyVisibility = Visibility.Visible;
}
}