Search code examples
wpfxamladorner

How to show 'Loading...' overlay while the View Model reloads the bound data


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 Adorners, 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?


Solution

  • 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));
        }
    }