7

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?

hyankov
  • 4,049
  • 1
  • 29
  • 46
  • 1
    The trigger-happy Mod who is voting to Close - perhaps this question is so trivial, you could very clearly describe the solution in one or two sentences? – hyankov Jan 17 '17 at 22:15
  • Was the boolean flag a notification property? Was it a property, with a backing field and one that raised the property changed notifications? – Eric Jan 17 '17 at 22:33

2 Answers2

9

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));
    }
}
mm8
  • 163,881
  • 10
  • 57
  • 88
2

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);
    }
}
Patrick B.
  • 69
  • 1
  • Just use a ProgressRing and bind the isActive property to your viewmodel working property. Make sure the working property has change notification and that the RaisePropertyChanged event is raised. eg: IsActive="{Binding Working}" – SimperT Jan 17 '17 at 23:16
  • Basically the solution revolves around loading the data into a separate `Task` and from that task updating the UI via `Dispatcher.CurrentDispatcher`, right? – hyankov Jan 18 '17 at 06:48