I want to do something that sounds extremely simple, yet I find it very hard to achieve.
Let's assume I have some content which is bound to a slow-loading operation. For example, an observable list which is retrieved from a local SQL and takes a few seconds. While this is happening, I want to overlay the content presenter (e.g. a Groupbox
) with a "Loading ..." text or any other 'please wait' type of content.
I quickly came to the conclusion that simply switching a boolean flag bound to the UI, before and after the operation doesn't work. The UI does not get refreshed until the entire operation completes. Maybe because the operation is CPU-intensive, I don't know.
I am now looking into Adorner
s, but very little information comes up with I search for it in the context of a 'busy indicator' overlay. There are just a few solutions on the Internet, from about 5 years ago and I can't get any of them to work.
The question:
As simple as it sounds - how to temporarily show something on the screen, while the View Model is working to update the bound data?
I quickly came to the conclusion that simply switching a boolean flag bound to the UI, before and after the operation doesn't work. The UI does not get refreshed until the entire operation completes. Maybe because the operation is CPU-intensive, I don't know.
Yes, it should work provided that you are actually executing the long-running operation on a background thread.
Please refer to the following simple example.
View:
<Window x:Class="WpfApplication2.Window1"
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:WpfApplication2"
mc:Ignorable="d"
xmlns:s="clr-namespace:System;assembly=mscorlib"
Title="Window1" Height="300" Width="300">
<Window.DataContext>
<local:Window1ViewModel />
</Window.DataContext>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<Grid>
<TextBlock>Content...</TextBlock>
<Grid Background="Yellow" Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">Loading...</TextBlock>
</Grid>
</Grid>
</Window>
View Model:
public class Window1ViewModel : INotifyPropertyChanged
{
public Window1ViewModel()
{
IsLoading = true;
//call the long running method on a background thread...
Task.Run(() => LongRunningMethod())
.ContinueWith(task =>
{
//and set the IsLoading property back to false back on the UI thread once the task has finished
IsLoading = false;
}, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}
public void LongRunningMethod()
{
System.Threading.Thread.Sleep(5000);
}
private bool _isLoading;
public bool IsLoading
{
get { return _isLoading; }
set { _isLoading = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Here is an example of how you can setup a View with a "Loading" display while the ViewModel\Model are working on some long task.
Window
<Window x:Class="Loading.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:Loading"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:VisibilityConverter x:Key="visibilityConverter" />
</Window.Resources>
<Window.DataContext>
<local:ViewModel x:Name="viewModel" />
</Window.DataContext>
<Grid>
<Button Content="Perform" VerticalAlignment="Bottom" HorizontalAlignment="Center" Width="100" Height="30" Command="{Binding HandleRequestCommand}" />
<Border Visibility="{Binding Path=IsLoading,Converter={StaticResource visibilityConverter}}" Background="#AAAAAAAA" Margin="5">
<TextBlock Text="Loading..." VerticalAlignment="Center" HorizontalAlignment="Center" />
</Border>
</Grid>
VisibilityConverter.cs (Simple helper converter)
class VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isLoading;
public ViewModel()
{
HandleRequestCommand = new Command(HandleRequest);
}
public bool IsLoading
{
get
{
return isLoading;
}
set
{
if (value != isLoading)
{
isLoading = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsLoading)));
}
}
}
public ICommand HandleRequestCommand
{
get;
}
public void HandleRequest()
{
IsLoading = true;
Task.Factory.StartNew(LongRunningOperation);
}
private void LongRunningOperation()
{
// *** INSERT LONG RUNNING OPERATION ***
Dispatcher.CurrentDispatcher.Invoke(() => IsLoading = false);
}
}
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