BusyIndicator for TabControl

445 Views Asked by At

I'm working with a TabControl which has a few TabItems. Inside these TabItems, there are several charts being bound to a large data source. So, every time the user tries to navigate between the tabs, the system would freeze to take a few seconds to load the contents of the tab. Also, these charts were configured and bound to the data as soon as the system starts. Not when the user clicks the tabs.

I would like to show the Loading.... logo with a transparent background while the user waits. But because of all of these charts were configured at the start of the system (before the UI shows up), I cannot show the BusyIndicator at this stage. It seems like the UI is taking some time to prepare these charts because there are no events which tries to call the backend side except the two-way binding.

My TabControl looks like this:

<TabControl x:Name="TabMain"
 Grid.RowSpan="2"
 ....
 ....
 Style="{DynamicResource TabControl}">
 <TabItem x:Name=".." DataContext="{Binding Source={StaticResource Locator}}" ....>
 <TabItem x:Name=".." ....>
 ....
</TabControl>

I've been finding a way to show the BusyIndicator at the appropriate time without being have to hard-code the delay time. Any idea how to achieve this?

2

There are 2 best solutions below

4
Wil Liam Yap On BEST ANSWER

Use SelectionChanged event and add a loading logo, Rectangle in this example.

XAML

<TabControl x:Name="TabMain" SelectionChanged="tabMain_SelectionChanged"
 Grid.RowSpan="2"
 ....
 ....
 Style="{DynamicResource TabControl}">
 <TabItem x:Name=".." DataContext="{Binding Source= {StaticResource Locator}}" ....>
 <TabItem x:Name=".." ....>
 ....
</TabControl>
<Rectangle x:Name="LoadingLogo" Fill = "White" Visibility="Collapsed" Opacity=0.5/>

C#

private void tabMain_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (e.Source is TabControl) //if this event fired from TabControl then enter
    {
            //show loading logo
            LoadingLogo.Visibility = Visibility.Visible;

            //hide the loading logo with lower priority, so that it closes after UI finished loading
            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(()=> { LoadingLogo.Visibility = Visibility.Collapsed; }));
    }
}

More info about Dispatcher.Invoke.

In the case of Rectangle not showing due to UI loading, you might want to use a borderless translucent Window as your loading logo.

1
Andy On

You should have provided a minimal reproduction so people trying to help can prove their code without spending hours.

It seems you're not binding viewmodels to give your tabitems but they have datacontext set.

What I guess is happening is the user chooses to navigate. The new tabitem is rendered using it's data. That rendering takes time.

The tabcontrol inherits from selector and hence has the selectionchanged event.

https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.selector.selectionchanged?view=netframework-4.8

You could handle that and show your busy indicator.

private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (e.Source is TabControl)  // in case you have a combo or something inside it
    {
       // Make your throbber visible
    }
}

There is a complication here though - and again a minimal reproducible sample would have helped you out because I could check what happens. But I can't.

You might find your UI thread is busy rendering that tabitem and your throbber doesn't show up. In which case you'll have to work out how to delay presenting your data to the tabitem when it's chosen.

Once your tabitem shows up you then need to hide that throbber by setting visibility collapsed. One way to do that is to defer that process until the dispatcher isn't busy.

        Dispatcher.InvokeAsync(new Action(() => { // Make throbber collapse; })
            , DispatcherPriority.Background);