ProgressRing does not visible when trying to update UI by running some other process in WPF

123 Views Asked by At

I am working on WPF application. I am using MahApps Metro controls and theme in my application. I want to show a Loading indicator when some other process is running. This process will also update the UI. The problem is loading Indicator does not display.

<mah:ProgressRing x:Name="loadingIndicator" Grid.RowSpan="2" 
                          Foreground="{DynamicResource AccentColorBrush}"
                          IsActive="{Binding IsLoading}" />

private async void ExecuteButton_Click(object sender, System.Windows.RoutedEventArgs e)
    {
        if (DataContext is ExecutionWindowViewModel viewModel)
        {
            if (outputTab.Items.Count > 0)
            {
                outputTab.Items.Clear();
            }
            var selectedDatabases = viewModel.Databases.Where(x => x.IsSelected == true).ToList();
            viewModel.IsLoading = true;
            await Task.Run(() =>
            {
                this.Dispatcher.Invoke(new Action(() =>
                {
                    foreach (var database in selectedDatabases)
                    {
                        TabItem tabItem = new TabItem();
                        tabItem.Header = database.DBShortName;
                        DataGrid sqlOutput = new DataGrid();
                        sqlOutput.AutoGenerateColumns = true;
                        sqlOutput.IsReadOnly = true;
                        var test = viewModel.SQLQueryExecution(database.ConnectionString);
                        sqlOutput.ItemsSource = viewModel.SQLQueryExecution(database.ConnectionString).AsDataView();
                        tabItem.Content = sqlOutput;
                        outputTab.Items.Add(tabItem);
                    }
                }));
            });
            viewModel.IsLoading = false;
        }
    }

loading property is like

private bool _isLoading = false;
    public bool IsLoading
    {
        get => _isLoading;
        set
        {
            if (_isLoading != value)
            {
                _isLoading = value;
                OnPropertyChanged(nameof(IsLoading));
            }
        }
    }

On the button Click event, I want to execute multiple SQL queries, and based on the result I want to create multiple tabs.

Can anyone help me why ProgressRing is not visible?

I have also tried the below code and it is working correctly,

private async void ExecuteButton_Click(object sender, System.Windows.RoutedEventArgs e)
    {
        if (DataContext is ExecutionWindowViewModel viewModel)
        {
            if (outputTab.Items.Count > 0)
            {
                outputTab.Items.Clear();
            }
            var selectedDatabases = viewModel.Databases.Where(x => x.IsSelected == true).ToList();
            
            viewModel.IsLoading = true;
            await Task.Delay(10000);
            viewModel.IsLoading = false;
        }
    }
1

There are 1 best solutions below

3
Gilad Waisel On

It is OK to execute the data base access in a different Task as you did. But you should not use the dispatcher invoke method the way you did it. When you use the dispatcher the data base access is done under the UI thread and it blocks the UI update. While the task is running , you can use the dispatcher invoke for the update of UI related properties only. By doing so you will not block the UI with lengthy SQL calls and still be able to update the UI. The IsLoading property is actually set to false immediately! The Sequence is as following: The task is starting, it sends to the dispatcher an action to be execute and the task is terminating and setting the IsLoading to false. After that the SQL part is executed under the UI thread with the action you set before.

I include a working code that is slightly modified.

<Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <ProgressBar Grid.Row="0" IsIndeterminate="{Binding IsLoading}" />
        <TabControl Grid.Row="1" Name="outputTab"/>
        <Button Grid.Row="2" Click="Button_Click" Width="100">Load</Button>
    </Grid>


public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new ExecutionWindowViewModel();
        }
        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            if (DataContext is ExecutionWindowViewModel viewModel)
            {
                if (outputTab.Items.Count > 0)
                {
                    outputTab.Items.Clear();
                }
                var selectedDatabases = viewModel.Databases;
                viewModel.IsLoading = true;
                await Task.Run(() =>
                {
                        foreach (var database in selectedDatabases)
                        {
                            
                            viewModel.SQLQueryExecution();
                            
                            this.Dispatcher.BeginInvoke((Action)(() => {
                                DataGrid sqlOutput = new DataGrid();
                                sqlOutput.AutoGenerateColumns = true;
                                sqlOutput.IsReadOnly = true;
                                TabItem tabItem = new TabItem();
                                tabItem.Header = database.ToString();
                                tabItem.Content = sqlOutput;
                                outputTab.Items.Add(tabItem); }));

                        }
                        this.Dispatcher.BeginInvoke((Action)(() => { viewModel.IsLoading = false; }));
                    
                });
                
            }
        }
    }




public class ExecutionWindowViewModel: INotifyPropertyChanged
    {
        public ExecutionWindowViewModel()
        {
            Databases = new List<string>();
            Databases.Add("DB1");
            Databases.Add("DB2");
            Databases.Add("DB3");
        }
        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged( String propertyName )
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        bool _isLoading;
        public bool IsLoading
        {
            get { return _isLoading; }
            set {
                    _isLoading = value;
                    NotifyPropertyChanged(nameof(IsLoading));
                }
        }
        public List<string> Databases { get; set; }

        public  void  SQLQueryExecution()
        {
            Thread.Sleep(3000);
        }

    }

As of the comments I am adding another flavor for the code behind. But also the first one I posted is working and is legitimate. I also agree that this is not the best practice regarding the VM but this is not the issue here.

private async void Button_Click(object sender, RoutedEventArgs e)
        {
            if (DataContext is ExecutionWindowViewModel viewModel)
            {
                if (outputTab.Items.Count > 0)
                {
                    outputTab.Items.Clear();
                }
                var selectedDatabases = viewModel.Databases;
                viewModel.IsLoading = true;
                await Task.Run(() =>
                {
                        foreach (var database in selectedDatabases)
                        {
                            
                            viewModel.SQLQueryExecution();
                                this.Dispatcher.Invoke(() => {
                                    DataGrid sqlOutput = new DataGrid();
                                sqlOutput.AutoGenerateColumns = true;
                                sqlOutput.IsReadOnly = true;
                                TabItem tabItem = new TabItem();
                                tabItem.Header = database.ToString();
                                tabItem.Content = sqlOutput;
                                outputTab.Items.Add(tabItem); });
                        }
                });
                viewModel.IsLoading = false;
               

            }
        }