My app is blocking when i want to navigate to certain view, so i want to show a load icon. The problem is the icon never shows when expected. The view changes and i never see the icon, but if i go back the icon is there.
I tried using an async task to do the navigation, but the navigation doesn't work in a task, i guess.
Any suggestions or ideas?
XAML Code:
<UserControl xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:fa="http://schemas.fontawesome.io/icons/">
<Grid>
<Listbox ItemsSource={Binding ItemsList}>
</Listbox>
<Canvas Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="-550, 180, 0, 0">
<fa:ImageAwesome Visibility="{Binding LoadingIcon, UpdateSourceTrigger=PropertyChanged}"
Icon="Spinner"
Spin="True"
Canvas.Left="56"
Canvas.Top="-17" />
</Canvas>
</Grid>
<UserControl.InputBindings>
<KeyBinding Key="Enter" Command="{Binding NavigateToMainMenuCommand}"/>
</UserControl.InputBindings>
</UserControl>
ViewModel:
public class LoginViewModel: ViewModelBase, INotifyPropertyChanged, INavigationAware
{
public InicioContableViewModel(IRegionManager regionManager,
IEventAggregator eventAggregator)
{
_regionManager = regionManager;
_eventAggregator = eventAggregator;
NavigateToMainMenuCommand = new DelegateCommand(NavigateToMainMenu);
LoadingIcon = Visibility.Hidden;
}
public DelegateCommand NavigateToMainMenuCommand { get; private set; }
private Visibility loadingIcon;
public Visibility LoadingIcon
{
get
{
return loadingIcon;
}
set
{
SetProperty(ref loadingIcon, value, nameof(LoadingIcon));
NotifyPropertyChanged(nameof(LoadingIcon));
}
}
private void NavigateToMainMenu()
{
LoadingIcon = Visibility.Visible;
string mainUri = nameof(SomeRoute.MainMenu);
_regionManager.RequestNavigate(Regions.MainRegion, mainUri);
}
}
In your Icon's properties, try to set Build Action to Resource.
WPF uses the UI thread to do things like handle mouse movement, change icons when you tell it to and to switch out one view for another. All one thread.
When that thread is busy doing something then nothing is likely to change in your UI until it's finished.
Hence, if you tell it to make something visible AND navigate, you can well find all that happens visually is the navigation. Because it has no time to show your icon.
Try making your method async and giving it a little time:
private async void NavigateToMainMenu()
{
LoadingIcon = Visibility.Visible;
await Task.Delay(60);
string mainUri = nameof(SomeRoute.MainMenu);
_regionManager.RequestNavigate(Regions.MainRegion, mainUri);
}
That task.delay should give it enough time to redraw a bit of ui.
If you need a loading icon and the thing you are navigating to is blocking then I suspect you have other problems. It's likely that whatever this new view is doing to initialise should be asynchronous. Maybe with data access etc on background threads.
You may most likely make use of the functions:- InvalidateVisual() or UpdateLayout(). Those will force to redraw and may resolve your icon visibility issues.
My app is blocking when i want to navigate to certain view, so i want to show a load icon.
If the app is blocking, you cannot show the icon, because, you know, the app is blocked. You have to remove the blocking, that is, make navigation itself quick, and do whatever initialization asynchronously. The view you're navigating to may need stuff from a database or connection to a usb device, but it won't need it immediately. You can still query the database or whatever in the background after the view is shown.
show wait indicator-> navigate -> creates view model -> starts initializion task -> initialization completes -> hide wait indicator
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 a MVVM WPF application from which I show a splash screen while some long task is done.
This splash screen is a typical WPF window and is very simple, it only has a label to show custom text such as "Loading..." and a spinner.
Its code-behind only has the constructor in which it is only performed the InitializeComponent and and event Window_Loaded.
From my main MVVM WPF application, when I perform a long task I instantiate this window and show the splash screen.
So now, I want to customize the text shown in the label in the splash screen. So what is the best approach?
What I have done is to pass as a parameter a string (custom message) to the constructor when I instantiate the window (Splash Screen).
Window mySplash = new SplashScreen("Loading or whatever I want");
As this splash screen is very simple, only a splash, I think it has no sense to apply MVVM here, so in the code-behind I create a private property which I set with the string passed as parameter to the constructor. Then, I bind the label in view with this private property and finally I implement INotifyPropertyChanged in the code-behind (view). There is no model, model view here.
Is that the correct way to do it? or is there any other way?
I know there is other solutions, like making public the label in the view by adding this:
x:FieldModifier="public"
and then access it once I instantiate the splash screen but I do not like this solution, I do not want to expose label outside.
ATTEMPT #1:
From my view model in main MVVM WPF application I perform below:
Window splashScreen = new SplashScreen("Loading ...");
Splash screen window:
<Window x:Class="My.Apps.WPF.SplashScreen"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<!-- Grid row and column definitions here -->
<Label Grid.Row="0" Content="{Binding Path=Message}"/>
</Grid>
</Window>
Splash screen code-behind:
public partial class SplashScreen: Window
{
public string Message
{
get
{
return (string)GetValue(MessageProperty);
}
set { SetValue(MessageProperty, value); }
}
public static readonly DependencyProperty
MessageProperty =
DependencyProperty.Register("Message",
typeof(string), typeof(System.Window.Controls.Label),
new UIPropertyMetadata("Working, wait ..."));
public SplashScreen(string message)
{
InitializeComponent();
if (!String.IsNullOrEmpty(message))
this.Message = message;
}
}
I have set a default value for Label.It will be used if it is not passed as a parameter to the constructor.
It is not working, in the xaml preview is not showing the default message in the label in the Visual Studio IDE environment. Also for some reason, when I pass as a parameter the custom message from the view model, it is not shown in the label. What am I doing wrong?
You did not set DataContext for your grid:
<Window x:Class="My.Apps.WPF.SplashScreen"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="Splash"
>
<Grid DataContext="{Binding ElementName=Splash}">
<!-- Grid row and column definitions here -->
<Label Grid.Row="0" Content="{Binding Path=Message}"/>
</Grid>
</Window>
In my WPF app, I've to show different controls in the window for different user actions. Like, when user selects an image, I show a Image control and when the user selects a text file, I show a TextBox control. Similarly there are many controls for different user selections.
To do this I'm first doing Visibility = Visibility.Collapsed for all controls using a foreach loop, then doing Visibility = Visibility.Visible for the controls that I've to show.
Is there a more efficient way of doing this? The window flickers and is not really snappy when changing controls as there are many controls.
I always use a ContentControl for this. It produces very clean xaml code and is superfast, I've never seen any flicker even in rather loaded views. It does require any extra frameworks beyond WPF.
<ContentControl Content="{Binding Selected}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type viewModels:FirstOne}">
<!-- View code for first view goes here -->
<TextBlock>Hi</TextBlock>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:SecondOne}">
<!-- View code for second view goes here -->
<Image Source="{Binding Image}" />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
And then I have a MainViewModel handing the navigation with a property
class MainViewModel: ViewModel, INavigation
{
public ViewModel Selected
{
get { return _selected; }
private set
{
_selected = value;
RaisePropertyChanged();
}
public void Show(ViewModel viewModel) { Selected = viewModel; }
}
}
The INavigation interface is just something like so
interface INavigation { void Show(ViewModel viewModel); }
An example view model
class FirstOne: ViewModel
{
private readonly INavigation _navigation;
public FirstOne(INavigation navigation) { _navigation = navigation; }
public void ButtonClicked()
{
_navigation.Show(new SecondOne());
}
}
First, you may want to avoid doing Visibility=Visibility.Collapsed for controls that you are about to make visible. That could reduce some unnecessary screen activity.
Second, rather than having the user experience all the individual visibility changes in a serial fashion, you might want to keep your controls in a parent control that you can hide before the visibility changes take place and then unhide once the changes are complete. You could even use an opacity animation to fade it out and then back in. Of course, without seeing or understanding more about your app, I can't tell what's really appropriate.
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 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"