Showing a progress dialog in Win UI 3 while blocking user interaction

Viewed 33

I created a Win UI 3 application with multiple pages using MVVM.

sample Page.Xaml.cs:

public sealed partial class InformationPage : Page
{
    public InformationViewModel ViewModel
    {
        get;
    }

    public InformationPage()
    {
        ViewModel = App.GetService<InformationViewModel>();
        InitializeComponent();
    }
}

sample ViewModel.cs:

public partial class InformationViewModel : ObservableRecipient, INavigationAware
{
    private readonly IDetails _DetailsService;

    public Core.Models.InformationModel? Information;

    public ObservableCollection<Core.Models.DetailsModel> Source
    {
        get;
    } = new ObservableCollection<Core.Models.DetailsModel>();

    public InformationViewModel(IDetails DetailsService)
    {
        _DetailsService = DetailsService;
    }

    public void OnNavigatedFrom()
    {
    }

    public async void OnNavigatedToAsync(object parameter)
    {
        var data = await _DetailsService.GetDetailsAsync(Views.ShellPage.CurrentMID, Views.ShellPage.CurrentPID);

        if (!Source.Any() || Source[0] != data)
        {
            Source.Clear();
            Source.Add(data);
            Views.ShellPage.Details = Source[0];
        }

        if (Views.ShellPage.Details != null)
        {
            Information = Views.ShellPage.Details.Information;
        }
    }

Each page parses different queries from a server and some pages require multiple queries.

The server is slow in responding, each query usually takes between 1 - 7 seconds and multiple queries sometimes reach one minute of wait time.

Querying the data happens at ViewModel. When navigation to a page is triggered, it first requests the data and after receiving it, the application will show the selected page.

During this time, the application seems frozen or bugged and nothing works. it may seem there is a problem with the application, so I have decided to show a loading screen while this happens to inform the user that something is going on and the application is doing its work.

so I tried using a ContentDialog and removed all buttons with just a text and an indeterminate progress bar. but ContentDialog does have a DefualtButton property which triggers when pressing Enter key, so the application crashes, while this dialog is showing, or the dialog closes if I define it.

The goal is to show the user a loading screen while blocking it from interacting with other parts of the application until ViewModel has finished receiving the data, on which it will be automatically dismissed.

What solution should I try?

2 Answers

You can set IsPrimaryButtonEnabled and IsSecondaryButtonEnabled to false to prevent keyboard interaction with the ContentDialog.

Your call to the server shouldn't block the UI thread. So, your app shouldn't freez or stop working. This is a sample app using the ProgressBar and faking a async server call but the UI is not blocked.

MainWindowViewModel.cs

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;

namespace WinUI3ProgressBarTest;

public partial class MainWindowViewModel : ObservableObject
{
    [ObservableProperty]
    private ObservableCollection<string> items = new();

    [RelayCommand]
    private async Task LoadItems()
    {
        IEnumerable<string> loadedItems = await LoadItemsFormServerAsync(100);

        foreach (string item in loadedItems)
        {
            Items.Add(item.ToString());
        }
    }

    private static async Task<IEnumerable<string>> LoadItemsFormServerAsync(int itemsCount)
    {
        return await Task.Run(async () =>
        {
            List<string> loadedItems = new();

            for (int i = 0; i < itemsCount; i++)
            {
                loadedItems.Add(i.ToString());
                await Task.Delay(100);
            }

            return loadedItems;
        });
    }
}

MainWindow.xaml.cs

using Microsoft.UI.Xaml;

namespace WinUI3ProgressBarTest;

public sealed partial class MainWindow : Window
{
    public MainWindow()
    {
        this.InitializeComponent();
    }

    public MainWindowViewModel ViewModel { get; } = new();
}

MainWindow.xaml

<Window
    x:Class="WinUI3ProgressBarTest.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"
    mc:Ignorable="d">

    <Grid RowDefinitions="Auto,Auto,*">
        <Button
            Grid.Row="0"
            Command="{x:Bind ViewModel.LoadItemsCommand}"
            Content="Load items" />
        <ProgressBar Grid.Row="1" IsIndeterminate="{x:Bind ViewModel.LoadItemsCommand.IsRunning, Mode=OneWay}" />
        <ListView Grid.Row="2" ItemsSource="{x:Bind ViewModel.Items, Mode=OneWay}" />
    </Grid>

</Window>
Related