Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

wpf

xaml

adorner

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?

like image 268
hyankov Avatar asked Jan 17 '17 22:01

hyankov


2 Answers

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));
    }
}
like image 186
mm8 Avatar answered Oct 20 '22 16:10

mm8


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);
    }
}
like image 2
Patrick B. Avatar answered Oct 20 '22 16:10

Patrick B.