I'm using the wpf toolkit busy indicator which provides an overlay on top of my UI while a background operation is taking place. There's a progress bar in the control which is set to indeterminate which is fine while my background task is going on. Once the background task is complete, the UI needs to update which can take 1-2 seconds. This of course causes the progress bar to freeze which looks ugly.
My question is, how can I spin up the busy indicator on a background thread so that the progress bar carries on moving all the way up until the UI becomes responsive? I'm open to other solutions as well as long as the progress bar doesn't freeze.
Here's some sample code:
<xctk:BusyIndicator IsBusy="{Binding IsBusy}" Style="{StaticResource BusyIndicatorStyle}">
<DockPanel Margin="3">
<TextBlock DockPanel.Dock="Top" Style="{StaticResource WorkspaceHeaderStyle}" Text="User Management"/>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ToolBar Grid.Row="0">
<Button Content="Save changes" Command="{Binding SaveChangesCommand}"/>
</ToolBar>
<TabControl Grid.Row="1">
<TabItem Header="Users" DataContext="{Binding UsersViewModel}">
<users:UsersView />
</TabItem>
<TabItem Header="Roles" DataContext="{Binding RolesViewModel}">
<roles:RolesView />
</TabItem>
</TabControl>
</Grid>
</DockPanel>
</xctk:BusyIndicator>
private void LoadDays()
{
ProgressIsBusy = true;
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
var loadDaysTask = GetLoadDaysTask(uiScheduler);
loadDaysTask.ContinueWith(antecedent =>
{
RaisePropertyChanged(() => ForecastViewModel);
RaisePropertyChanged(() => AverageHandleTimeViewModel);
RaisePropertyChanged(() => GeneralOptionsViewModel);
RaisePropertyChanged(() => ScheduledHoursViewModel);
IsUserEditing = true;
ProgressIsBusy = false;
}, TaskScheduler.FromCurrentSynchronizationContext());
loadDaysTask.Start();
}
No, It is not possible to show the BusyIndicator animations when UI is updating / using Background thread or something.
Refer the related post here.
busy indicator during long wpf interface drawing operation
You can show some static image to indicate the busy sign until your UI updation complete.
I don't believe that you can do that. You can't access UI controls from a background thread. Also, you may find that it is the UI thread that is busy rendering or notifying and making the progress bar freeze.
I have a similar setup in a large scale WPF application and it suffers from the same problem. The busy indicator displays fine while data is being fetched from the database, but then when the application starts to render the data in the UI, the busy indicator freezes.
I even tried using an animated Gif to get around this issue, but of course they don't animate (by themselves) in WPF applications. Having written the code to animate the frames in the Gif, I was very disappointed to find that it also suffered from the same problem.
Good luck all the same.
Related
I am developing a desktop application for managing a forest catalog. When starting the application, certain data must be displayed from a MySql database, which slows down the start of the application, so I want to show a dialog that shows an animated gif until the data is finished loading.
The problem is that the application freezes and the dialog is not shown until the data loading is finished. I've searched other posts but can't find the solution. I appreciate any help, thanks.
This is my MainWindow XAML:
<mui:ModernWindow x:Class="ModernUINavigationApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mui="http://firstfloorsoftware.com/ModernUI"
Title="Media Copy Manager" IsTitleVisible="True"
WindowStartupLocation="CenterScreen"
ContentSource="/gui/Pages/PHome.xaml" WindowState="Maximized">
<mui:ModernWindow.MenuLinkGroups>
( ...)
</mui:ModernWindow.MenuLinkGroups>
Here is my PHome.xaml page:
<UserControl x:Class="MCP.gui.Pages.PHome"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mui="http://firstfloorsoftware.com/ModernUI"
mc:Ignorable="d">
<Grid Style="{StaticResource ContentRoot}" Margin="0 0 0 0" Name="_contentRoot">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TabControl Grid.Row="0" Grid.Column="0" Name="_TAB" TabStripPlacement="Left"/>
</Grid>
This is my Home.cs code :
{
public PHome()
{
InitializeComponent();
this.Loaded += ContentLoaded;
}
private void ContentLoaded(object sender, RoutedEventArgs e)
{
Thread t = new Thread(() =>
{
Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
(Action)(() =>
{
new LoadingDialog().Show();
}));
});
t.SetApartmentState(ApartmentState.STA);
t.IsBackground = true;
t.Start();
Task.Run(() =>
{
_TAB.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
(Action)(() =>
{
Populate_Tab(_TAB);
}));
});
}
private async void Populate_Tab(TabControl tabControl)
{
tabControl.Items.Clear();
tabControl.ClipToBounds = true;
List<categoria> ListaCategorias = await DBManager.CategoriasRepo.ListAsync;
foreach (categoria categ in ListaCategorias)
{
TabItem tabitem = new TabItem();
tabitem.Header = categ.categoria1;
Thread.Sleep(1000); //Do some long execution process
tabControl.Items.Add(tabitem);
}
}
}
Here is my LoadingDialog XAML:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mui="http://firstfloorsoftware.com/ModernUI"
mc:Ignorable="d" d:DesignWidth="300"
Title="ModernDialog" Background="{x:Null}" Height="197.368">
<StackPanel>
<TextBlock>LOADING</TextBlock>
<mui:ModernProgressRing IsActive="True" Name="_LoaderGif" Width="100" Height="100" Style="{StaticResource ThreeBounceProgressRingStyle}" Margin="47,43,68,65" />
</StackPanel>
</mui:ModernDialog>
You need to get a better understanding of the methods you're using, specifically the ones you are using to try and move things onto other threads. Before I explain, keep these things in mind:
The main, default thread in a WPF application can be called the UI thread. All the code for the rendering and layout of the application is processed on this thread. All the code you write is also processed on this thread unless you say otherwise.
A thread runs one method at a time, so any any long-running method on the UI thread will prevent it from running the methods necessary to layout and render the application (i.e. it will "freeze").
Changes to UI elements- the visual aspects of the application- must be done from the UI thread.
The thing you don't seem to understand is Dispatcher.Invoke.
Just like Task.Run moves code execution onto a new thread, Dispatcher.Invoke moves code execution back onto the UI thead.
Let's take your method ContentLoaded. In your code, you attempt to use both the Thread and Task classes to move some of your code onto a separate thread. But all you do from those new threads is immediately call Dispatcher.Invoke, which executes code back on the UI thread. This actually makes your app slower, in theory, because it's wasting time making new threads and jumping between them for no reason.
What you need to do is take Populate_Tab and separate it into two (or more) different methods: one that effects the UI and one that doesn't.
Calls like tabControl.Items.Clear and tabitem.Header = categ.categoria1 have to be done on the UI thread because they directly reference UI elements, but your long-running operations, like accessing your database, can and should be moved onto another thread.
The sequence shoud be:
Show the loading icon.
Start a long running operation on another thread. I would recommend using Task with async and await, as that is the current best practice.
Get all the data you need and make any calculations on that other thread.
Return the needed data back to the UI thread (using await) and update the UI as needed.
Remove the loading icon.
The following XAML shows the content of a Windows Phone 8.1 Universal app page. The idea is to have a bar on top of a pivot. But the pivot is overlapping the bar whereas the sample grid in blue is working as expected.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Background="{StaticResource PhoneAccentBrush}" Height="50" />
<Pivot Grid.Row="1" Background="Green" Width="200" HorizontalAlignment="Left" />
<Grid Grid.Row="1" Background="Blue" Width="200" HorizontalAlignment="Right" />
</Grid>
The resulting Page looks like this:
Where is this "negative margin" coming from?
How can it be avoided?
BTW: Just setting a margin is not a solution as it introduces other problems, for example a jumping GUI when using a SemanticZoom control, I need to know the root cause of the problem to solve it...
Info: It seems that the Windows Phone Pivot automatically changes the margin depending on the current screen settings (status bar shown/hidden). So if your app changes the state of the status bar in some circumstances you will end up having a jumping/changing pivot control.
I've found a solution and created a simple attached property for the Pivot control.
The attached property can be used this way:
<Pivot controls:PivotExtensions.DisableAutoMargin="True">
<PivotItem Header="A">
...
</PivotItem>
<PivotItem Header="B">
...
</PivotItem>
</Pivot>
The class with the attached property can be found here:
http://mytoolkit.codeplex.com/SourceControl/latest#MyToolkit.Extended.WinRT/Controls/PivotExtensions.cs
Downside: You cannot override the template anymore as the attached property changes the template already...
Overriding the control template doesn't seem to work; there is some code in the control that's setting the margin after construction. Also setting the Margin on the offending grid on the Loaded event doesn't work either, the negative margin code runs after that.
The only way I found to make the odd behavior go away was to ask your UI to overlap the status bar:
ApplicationView.GetForCurrentView()
.SetDesiredBoundsMode(ApplicationViewBoundsMode.UseCoreWindow);
Then the Pivot control figures that it doesn't need to try and cover the status bar. Note that you'll now have to give room in your page layout to prevent overlap with the status bar.
I apologize if this is a duplicate, but I have not been able to find a question with a similar situation. If this is a duplicate please provide a link.
I would like to show a "Loading..." overlay in my WPF application, when I am dynamically creating a lot of tabs. The overlay visibility is bound to a property called "ShowIsLoadingOverlay". However, the overlay is never shown.
Due to the fact that the tabs are visual elements I can't move the creation into a BackgroundWorker.
I have created a small prototype trying to explain the situation. This is the xaml:
<Window x:Class="WpfApplication5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label HorizontalAlignment="Center" VerticalAlignment="Center"
Visibility="{Binding ShowIsLoadingOverlay, Converter={StaticResource BooleanToVisibilityConverter}}"
Content="Loading..." />
<Button Grid.Row="1" Content="Load" Click="Button_Click" />
</Grid>
</Window>
And this is the code behind:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private bool m_ShowIsLoadingOverlay;
public bool ShowIsLoadingOverlay
{
get
{
return m_ShowIsLoadingOverlay;
}
set
{
if ( m_ShowIsLoadingOverlay == value )
{
return;
}
m_ShowIsLoadingOverlay = value;
NotifyPropertyChanged( "ShowIsLoadingOverlay" );
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void Button_Click( object sender, RoutedEventArgs e )
{
ShowIsLoadingOverlay = true;
CreateTabs();
ShowIsLoadingOverlay = false;
}
private void CreateTabs()
{
// Simulate long running process to create tabs
Thread.Sleep( 3000 );
}
// Implementation of INotifyPropertyChanged has been left out.
}
The problem is that the overlay is never shown. I know that it has something to do with the UI not updated correctly before and after the ShowIsLoadingOverlay property has changed. And I believe it also has something to do with the lack of using the dispatcher.
I have tried many, many combinations of Dispatcher.Invoke, Dispatcher.BeginInvoke surrounding when changing the property and/or surrounding the CreateTabs call. And I have tried changing the DispatcherPriority to "force" the overlay to show before starting to create the tabs. But I just can't make it work...
Could you please tell me how to accomplish this task? And more importantly; provide an explanation, because I do not get this.
In advance,
thank you.
Best regards,
Casper Korshøj
You cannot manipulate UI controls in a background thread. If you are using the main UI thread to create your TabItems, then you also cannot have a 'Busy' or 'Loading' indicator... this will only work if you are using a alternative thread for your long running process. This is because your 'Busy' indicator will only become updated once the long running process has completed if it runs on the same UI thread.
new to WPF and c# hobbyist...
For some reason, I can't get my loadingAnimation (or any other) function to run immediately after a button press and before a SOAP call.
My xaml:
<Grid>
<TextBox Height="220" HorizontalAlignment="Left" Margin="12,79,0,0" Name="txtResults" VerticalAlignment="Top" Width="337" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="12,29,0,0" Name="txtServiceTag" VerticalAlignment="Top" Width="120" />
<CheckBox Content="This computer's service tag" Height="16" HorizontalAlignment="Left" Margin="151,32,0,0" Name="chkThisST" VerticalAlignment="Top" Checked="chkThisST_Checked" Unchecked="chkThisST_Unchecked"/>
<Button Content="Get Info" Height="23" HorizontalAlignment="Left" Margin="12,324,0,0" Name="btnGetInfo" VerticalAlignment="Top" Width="75" Click="btnGetInfo_Click" />
<my:LoadingAnimation HorizontalAlignment="Center" Margin="128,154,419,127" VerticalAlignment="Center" Name="loadingAnimation" Visibility="Hidden" />
</Grid>
My .cs:
private void btnGetInfo_Click(object sender, RoutedEventArgs e)
{
txtResults.Text = "Retrieving information...";
ShowHideLoading();
SoapCall();
ShowHideLoading();
}
My SoapCall() seems to be running before txtResults.Text has time to populate. SoapCall() takes about 5 seconds to return a message. I've messed around with the order of objects in , but to no avail.
Any help is appreciated!
The reason is that the SoapCall() is blocking the UI thread. In other words, until it is finished - no UI operations will be called.
You can solve this by putting the SoapCall() inside a BackgroundWorker.
Then, the ShowHideLoading can be put inside the RunWorkerCompleted event.
Here is an example on how to use the BackgroundWorker
When you perform actions on the main thread (in this case, the thread that calls the _Click method.), the UI will not actually update until the call finishes, and the framework alerts the UI to redraw itself. The reason you're never seeing the update is because the SoapCall is blocking the main thread from performing any updates.
For this to work properly, I would recommend updating the UI, forking a new thread to perform the Soap call, then when the operation completes, reset the UI back to its original state.
You may also want to look at how to use the Dispatcher object correctly, because WPF requires that all UI updates be made on the main thread. The Dispatcher allows you to force a snippet of code to run on a particular thread.
I have WP7 application with several pages. When a user navigates through them it takes some time to load information. So before showing him/her the page I'd like to show “Loading…” message.
I created progress bar and placed it on the page:
<StackPanel x:Name="progressBarMain" Grid.Row="1" Grid.ColumnSpan="2" Visibility="Collapsed">
<TextBlock Text="Loading..." HorizontalAlignment="Center" VerticalAlignment="Center" />
<ProgressBar Margin="10" Height="30" IsIndeterminate="True"/>
</StackPanel>
And I'm trying to show it (and hide everything else) in the page's constructor, and hide it (and show everything else) in Page.Loaded handler.
public SomePage()
{
InitializeComponent();
Loaded +=OnSomePageLoaded;
progressBarMain.Visibility = Visibility.Visible;
ContentPanel.Visibility = Visibility.Collapsed;
}
private void OnSomePageLoaded(object sender, RoutedEventArgs e)
{
progressBarMain.Visibility = Visibility.Collapsed;
ContentPanel.Visibility = Visibility.Visible;
}
But it doesn’t' work.
Any ideas? Thank you!
Alex demonstrates showing a progress bar while the app is starting up here.
Creating a Splash Screen with a progress bar for WP7 applications. - Alex Yakhnin's Blog
Although you cannot directly manipulate the splash screen (which is static), you can display a popup (by the way, that is exactly what is done in Alex's solution) and wait for a background (read: loading) operation to complete.
Yes, you'll need to create a separate XAML Pop-up page that is loaded when the app boots up. For more details on Splash Screens, there is a code sample from MSDN:
"Code Sample for Splash Screen"