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.
Related
As I understand it, WPF "messages" (e.g. a button click handler) are added to an internal prioritized queue. A single UI thread is then responsible for processing the queued messages.
Unfortunately my knowledge of WPF is not deep enough to understand the internal working of the framework. So my question is, given that there is only 1 thread processing messages...
What is the internal sequence of events (high level) that is leading to the tabs becoming unresponsive?
Observed Behavior
If you click slowly, the TabControl behaves as expected.
To reproduce: click 1 tab every 4 seconds.
It appears that if you give the TabControl.SelectedIndex data binding an opportunity to complete, the control will behave as designed.
If you click tabs quickly, then some of the tabs will become unresponsive.
To reproduce: click as many tabs as you can within 3 seconds.
Additional Reading
Two selected tabs in tabcontroller
While the behavior is similar, this article is different because the symptom is the result of using a Tab + MessageBox.
Sample Code
The following code can be used to reproduce the behavior, whereby, WPF tabs become permanently selected.
Paste into MainWindow.xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="30" />
<RowDefinition Height="*" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<WrapPanel Grid.Row="0">
<TextBlock>
1. Click on as many tabs as possible within 3 seconds.<LineBreak/>
2. Wait until multiple tabs are selected.<LineBreak/>
3. Uncheck the `Simulate Bug` checkbox.<LineBreak/>
</TextBlock>
</WrapPanel>
<CheckBox Grid.Row="1" IsChecked="{Binding CanSimulateBug}" Content="Simulate Bug"/>
<TabControl x:Name="ColorWorkspaces" Grid.Row="2" SelectedIndex="{Binding SelectedTab, Mode=TwoWay}">
<TabItem x:Name="RedTab" Header="Red"/>
<TabItem x:Name="OrangeTab" Header="Orange"/>
<TabItem x:Name="YellowTab" Header="Yellow"/>
<TabItem x:Name="GreenTab" Header="Green"/>
<TabItem x:Name="BlueTab" Header="Blue"/>
<TabItem x:Name="VioletTab" Header="Violet"/>
</TabControl>
<TextBlock Grid.Row="3" Text="{Binding Status}"/>
</Grid>
Paste into MainWindow.xaml.cs:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private int _selectedTab;
private string _status;
private bool _canSimulateBug;
public MainWindow()
{
this.CanSimulateBug = true;
this.Status = String.Empty;
this.DataContext = this;
InitializeComponent();
}
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
public bool CanSimulateBug
{
get
{
return _canSimulateBug;
}
set
{
_canSimulateBug = value;
RaisePropertyChanged();
}
}
public string Status
{
get
{
return _status;
}
set
{
_status = value ?? string.Empty;
RaisePropertyChanged();
}
}
public int SelectedTab
{
get
{
return _selectedTab;
}
set
{
UpdateStatus($"SelectedTab changing... Value={value}");
if (this.CanSimulateBug)
{
SimulateBug(value);
}
_selectedTab = value;
UpdateStatus($"SelectedTab changed. Value={value}");
// This missing line was added as per
Felix's comment
RaisePropertyChanged();
}
}
private void UpdateStatus(string message)
{
var formattedMessage = $"[{Thread.CurrentThread.ManagedThreadId}] {DateTime.Now.ToLongTimeString()}: {message}";
this.Status = formattedMessage;
Debug.WriteLine(formattedMessage);
}
private void SimulateBug(int id)
{
var delay = TimeSpan.FromSeconds(3);
UpdateStatus($"Bug simulation started... ID={id}, Delay={delay}");
// IMPORTANT: If you comment out this following line
// ... the application will behave as expected.
Application.Current.Dispatcher.Invoke( // blocking call
DispatcherPriority.Background, // tells UI thread to execute this as lowest priority job
new Action(delegate { /* do nothing */ }));
Thread.Sleep(delay);
UpdateStatus($"Bug simulation complete. ID={id}");
}
}
Your complete code is executing on a single thread. You can't execute concurrent operations using a single thread. What you are currently doing is to block the main thread twice (too long) by invoking two potentially long-running operations synchronously:
Synchronous Dispatcher invocation using Dispatcher.Invoke:
Application.Current.Dispatcher.Invoke(() => {}, DispatcherPriority.Background);
Synchronous thread sleep:
Thread.Sleep(TimeSpan.FromSeconds(3));
While executing these synchronous operations, the main thread is not free to execute the logic (which is in this case part of the Selector, which is a superclass of TabControl) that manages the selection state of the hosted items.
The main thread is blocked by waiting for the Dispatcher to return and then by sending it to sleep i.e. suspending it.
As a result the Selector is not able to unselect the previously selected TabItem.
The Selector is able to cancel the selection procedure, which involves handling the selected item and unselecting every other item (in case multi-select is not supported). Obviously, the Selector cancels the unselection/processing of the pending items.
You should be able to test this by listening to the Selector.Unselected event which you attach to the TabItem. It should not be raised. Apparently the blocking of the main thread creates a race condition for the internal item validation of the Selector.
To fix this race condition it should be sufficient to increase the DispatcherPriority of the queued dispatcher messages to at least DispatcherPriority.DataBind (above DispatcherPriority.Input):
Application.Current.Dispatcher.Invoke(() => {}, DispatcherPriority.DataBind);
This is not the recommended fix, although it fixes the race condition and therefore the issue of multiple selected tabs as the critical code can now execute in time. The real underlying problem is the blocked main thread (which actually is a blocked Dispatcher).
You never want to block the main thread. Now you understand why.
For this reason .NET introduced the TPL. Additionally, the compiler/runtime environment allows true asynchronous execution: by delegating execution to the OS, .NET can use kernel level features like interrupts. This way .NET can allow the main thread to continue e.g., to process essential UI related events like device input.
Part of the interface between OS level and framework level is the Dispatcher and the InputManager. The Dispatcher basically manages the associated thread. In an STA application this is the main thread. Now, when you block the main thread using Thread.Sleep, the Dispatcher can't continue to work on the message queue that contains handlers that are executed on the associated dispatcher thread (main thread).
The Dispatcher is now unable to execute the input events posted by the InputManager. Since the dependency property system (on which routed events and the data binding engine is based on) and usually the code of UI controls are also executed on the Dispatcher, they are also suspended.
The combination of the very low dispatcher priority DispatcherPriority.Background in conjunction with the long Thread.Sleep makes the problem even worse.
The solution is to not block the main thread:
Post work to the Dispatcher asynchronously and allow the application to continue while the job is enqueued and pending by calling Dispatcher.InvokeAsync:
Application.Current.Dispatcher.InvokeAsync(() => {}, DispatcherPriority.Background);
Execute blocking I/O bound operations asynchronously using async/await:
await Task.Delay(TimeSpan.FromSeconds(3));
Execute blocking CPU bound operations concurrently:
Task.Run(() => {});
Your fixed code would look as followed:
private async Task SimulateNoBugAsync(int id)
{
var delay = TimeSpan.FromSeconds(3);
UpdateStatus($"Bug simulation started... ID={id}, Delay={delay}");
// If you need to wait for a result or for completion in general,
// await the Dispatcher.InvokeAsync call.
Application.Current.Dispatcher.InvokeAsync(() => {}, DispatcherPriority.Background);
await Task.Delay(delay);
UpdateStatus($"Bug simulation complete. ID={id}");
}
I have two windows in a single application.
The first window has one button. When I click the button, the second window opens. (The first window needs to close or hide). Then I need to close my second window and need to stop the debugging. How to do it?
<Window x:Class="WpfApp5.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:WpfApp5"
xmlns:syncfusion="http://schemas.syncfusion.com/wpf"
xmlns:Control="clr-namespace:Syncfusion.Windows.Tools.Controls;assembly=Syncfusion.Tools.Wpf"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Button Content="Create" Height="50" Width="100" Click="btncreate"></Button>
</Grid>
MainWindow code-behind:
public partial class MainWindow : Window
{
public static TextBox textBox;
public MainWindow()
{
InitializeComponent();
}
private void btncreate(object sender, RoutedEventArgs e)
{
SecondWindow secondWindow = new SecondWindow();
secondWindow.Owner = this;
this.Hide();
secondWindow.ShowDialog();
}
}
Second window:
<Window x:Class="WpfApp5.SecondWindow"
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:WpfApp5"
mc:Ignorable="d"
Title="SecondWindow" Height="450" Width="800">
<Grid>
<StackPanel>
<TextBlock Text="New Project Loaded" HorizontalAlignment="Center" Margin="50"></TextBlock>
</StackPanel>
</Grid>
Your problem seems to be that you're running code which is showing a window, maybe modally.
At the same time, you're trying to shut the app down.
You've not shown this code but if your btnCreate handler has shutdown in it just before
secondWindow.ShowDialog();
You'd be trying to shut the app down effectively at the same time as showing that window as a dialog.
What you want is your code to shut the app down but not whilst the UI is busy doing something else like show a window.
You could do that by moving your shutdown so it's in say the contentrendered event handler of that window you're showing.
Another option is to schedule the shutdown using dispatcher.
Application.Current.Dispatcher.InvokeAsync(new Action(() =>
{
Application.Current.Shutdown();
}), DispatcherPriority.ContextIdle);
If you still have errors you could consider instead using.
Environment.Exit(0);
This is a rather brute force approach compared to shutdown but if you're only doing this whilst debugging that probably won't matter.
In any case, you should ensure you don't have code which shows another window or something from the window.closing events of any of your windows. Since closing the app will necessarily involve closing windows.
I have a window.xaml which has components with different kind of styling( border color red, opacity changed and so on). In one moment I want to create a screenshot and save to folder. But before that the window background should be transparent and someCanvas should be hidden.
How do I know when the styling method finished so I can take a screenshot?
public void SomeMethod()
{
ChangeWindowControlStyles();
//TODO: waint till 'ChangeWindowControlStyles' finished
TageScreenshotAndSave();
}
public void ChangeWindowControlStyles()
{
this.Background.Opacity = 0;
this.someCanvas.Visibility = Visibility.Collapsed;
//Some other stuff related to window content styling
}
public void TakeScreenshotAndSave()
{
//No multithreading happening here
//Just taking screenshot and saving to folder
}
EDIT
The window itself is transparent WindowStyle="None", that means it has no borders. In the start the window's Background.Opacity is set to 0.1 and all controls are visible (there are other controls than someCanvas that should always be visible).
Before screenshot is taken someCanvas is hidden and the Background.Opacity is set to 0.
Window.xaml
<Window
WindowStartupLocation="CenterScreen"
ResizeMode="NoResize"
WindowState="Maximized"
WindowStyle="None"
AllowsTransparency="True" >
<Window.Background>
<SolidColorBrush Opacity="0.1" Color="White"/>
</Window.Background>
<Grid Name="mainGrid" Background="Transparent" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="0">
<!--Main canvas, function holder-->
<Canvas Name="canvasAlwaysVisible" Margin="0" Panel.ZIndex="5">
<!-- Controls that are always visible -->
</Canvas>
<Canvas x:Name="someCanvas" Margin="0" Background="Transparent" Visibility="Visibility">
<!-- Controls with styling -->
</Canvas>
</Grid>
</Window>
EDIT 2
Another thing to mention is that inside TakeScreenshotAndSave there is also System.IO operations like - get all folders in directory, creation new directory and so on. Maybe .NET sees that and it is ran asynchronously.
Looks like I found the solution. I don't know why it works, will need to investigate more. That TakeScreenshotAndSave method that I mentioned in code sample was somehow running on different thread. When wrap that method inside Application.Current.Dispatcher it worked!
public void SomeMethod()
{
ChangeWindowControlStyles();
var m_dispatcher = Application.Current.Dispatcher;
m_dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.ApplicationIdle,
new System.Threading.ThreadStart(delegate
{
TakeScreenshotAndSave(); //Now running on UI thread
}));
}
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.
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.