UI not updating from EventAggregator - c#

For a project I use a very simple progress overlay.
It just displays a small marquee progressbar and covers the screen.
So in my ShellView I have
<Border Grid.Row="0"
Grid.RowSpan="11"
Grid.Column="0"
Grid.ColumnSpan="11"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Panel.ZIndex="3"
Background="#9E000000"
BorderBrush="Black"
BorderThickness="3"
Visibility="{Binding IsProgressing,
Converter={StaticResource BoolToVisibility}}">
<!-- omitted progressbar, text etc -->
</Border>
And I have a very simple event, which just sets the Visibility (IsProgressing) binding and some text to show.
Whenever I want to have a progressbar, I just publish that event, like
_eventAggregator.Publish(new ProgressingChange(true, "Loading ..."));
This works very well so far, besides one case:
For the application I use events for navigation of my screens.
So there is another event which I publish, like:
_eventAggregator.Publish(new NavigationEvent(typeof(TargetViewModel)));
which just sets the target screen:
public void Handle(NavigationEvent navigate)
{
var target = _screenFactory.FromType(navigate.TargetScreen);
this.ActivateItem(target);
}
One of my Screens has lots of items and takes about 3 seconds to load.
So I wanted to show my Progress overlay while the screen is loading.
This is what does not work. Both the new Screen and the Overlay are
showing simultaneously when those events are combined.
This is:
_eventAggregator.Publish(new ProgressingChange(true, "Loading ..."));
_eventAggregator.Publish(new NavigationEvent(typeof(LongLoadingViewModel)));
For debugging reasons I did not deactivate the Progressing overlay to the
what is happening.
So the screen is loaded, nothing is shown on the screen for about 3 seconds, and then
both the Progress overlay and the new screen are shown.
I have tried
sleeping after publishing the ProgressingChange event
sleeping in both event handlers
running the publish of both events on separate threads
running only one of those publishes on a separate threads
tried to force UI update like this here force UI update
What am I missing?
What is happening here?
How can I get that to work?
-edit-
Here is the code of my Handler method:
public void Handle(ProgressingChange progressing)
{
this.IsProgressing= progressing.IsProgressing;
this.ProgressingText= progressing.ProgressingText;
NotifyOfPropertyChange(() => IsProgressing);
NotifyOfPropertyChange(() => ProgressingText);
// of course, there is Notify in the setters themselves, too
}
And I used this code from the source linked above to force UI updates,
but that did not work
void AllowUIToUpdate() {
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Render, new DispatcherOperationCallback(delegate(object parameter)
{
frame.Continue = false;
return null;
}), null);
Dispatcher.PushFrame(frame);
}
Also, I tried publishing in a critical section to force the first
publish to be executed before the second one, but that did not work either.
-edit2-
Code that at least shows the Progress overlay
_eventAggregator.Publish(new ProgressingChange(true, "Activating ..."));
Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => _eventAggregator.Publish(new NavigationEvent(typeof(LongLoadingViewModel)))), DispatcherPriority.ContextIdle, null);

It might be a hack, but you could try to first wait for the Border (porgress bar) to be rendered before the navigation-event is published. To do this, you might be able to adapt the solution that is given here to execute some code when the UI-thread is no longer busy (see link for full explanation):
Dispatcher.BeginInvoke(new Action(() => Trace.WriteLine("DONE!", "Rendering")), DispatcherPriority.ContextIdle, null);
If this works, then at least you have something to start off making the code a bit cleaner.

Related

WPF Scroll To End of FlowDocument

I'm having trouble with getting a FlowDocument wrapped in a FlowDocumentScrollViewer to scroll to the end when its data changes.
This is my XAML code
<core:CustomFlowDocumentScrollViewer x:Name="ScrollViewer" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<FlowDocumentScrollViewer.Document>
<FlowDocument PagePadding="0">
<Paragraph Name="Paragraph"></Paragraph>
</FlowDocument>
</FlowDocumentScrollViewer.Document>
</core:CustomFlowDocumentScrollViewer>
The core:CustomFlowDocumentScrollViewer implements the following snippet https://stackoverflow.com/a/561319/13567181, just so I can call ScrollToBottom() later.
In my code-behind I'm clearing the Paragraph and adding new lines to it
private void PopulateFlowDocument(IEnumerable<LoggingEvent> list)
{
Paragraph.Inlines.Clear();
foreach (var loggingEvent in list)
{
var parsedRun = FormatLoggingEvent(loggingEvent);
Paragraph.Inlines.Add(parsedRun);
Paragraph.Inlines.Add(Environment.NewLine);
}
}
Once PopulateFlowDocument completes, I call ScrollToEnd - The control performs some level of scrolling but does not work reliably. My datasource always returns 5000 rows but the scroll view only scrolls to line ~3750 (sometimes more, sometimes less).
Does FlowDocument work asynchronously internally???
Approach 2
Apart from the solutions available on SO, I've also tried the following suggestion from the MSDN forum.
void paragraph_Loaded(object sender, RoutedEventArgs e)
{
Paragraph paragraph = (Paragraph)sender;
paragraph.Loaded -= paragraph_Loaded;
paragraph.BringIntoView();
}
Like above, the Loaded event triggers to early and hence the scrolling does not work reliably.
What I'm looking for is an event/notification when the entire document has been updated so that I can reliably scroll to its very bottom.
I ended up achieving what I wanted using the dispatcher. I borrowed the idea from here
Dispatcher.Invoke(new Action(() =>
{
SearchResultTextBox.ScrollToEnd();
}), DispatcherPriority.ContextIdle, null);
It seems that the FlowDocument is using the dispatcher too and so the scroll needs to be low-priority enough so that its scheduled after the actual document modifications.

How to make UI changes while loading next screen in Xamarin.Forms

I have a screen that takes some time to load. All I want to do is display a simple "Now Loading" message before the next screen loads. How do I achieve this? Here is the code I currently have trying to accomplish this:
void BT_User_Clicked(object sender, EventArgs e)
{
// Start a new task (this launches a new thread)
var t = Task.Run(() =>
{
// Do some work on a background thread, allowing the UI to remain responsive
Navigation.PushAsync(new UserList());
});
BT_User.IsEnabled = false;
BT_User.Text = "Now Loading";
}
When running this code it Almost achieves what I want. It displays the "Now Loading" text, and in the background, it calls the next screen(I can see this only by setting a breakpoint in the load of the next screen). Problem is, the next screen never shows itself. I'm guessing this is because it is loaded on a background thread but not the main UI thread.
If you throw in a new Screen from a background thread, it won't work. Changes in the UI need to be done in the Main UI Thread.
However Xamarin.Forms provides you with a helper:
In your background thread simply call:
var t = Task.Run(() =>
{
// Do some work on a background thread, allowing the UI to remain responsive
Xamarin.Forms.Device.BeginInvokeOnMainThread(() => {
Navigation.PushAsync(new UserList());
});
});
Regarding your Loading overlay, you either need a plugin capable of handling popup dialogs (for instance Acr.UserDialogs) or you need to implement your own dialog system, if you don't want to be dependent on third party libraries.
I already have done the latter and provided a brief explanation on how to do it over here:
Display a popup with xamarin forms

how to show a loading animation when open a form that is very busy creating its child controls?

I have the following situation, I have a Form app that needs to load a lot of UI controls. I have a loading animation that it is over every other control.
I want the animation stay above and running while the loading process occurs.
At the end of the process the panel with the animation could dissapear.
As the UI is very busy creating and positioning every new control only the box where the panel is positioning appears. The animation never moves. (A gif image).
If I disable the form as I read in a related SO question, I could see the panel with the gif but it doesn't move. It remain static and greyed.
I made an attempt to run another form with the animation above the other busy form but the way that I made it sometimes work and sometimes don't.
ReallyBusyForm.Show();
ReallyBusyForm.Focus();
var floatingWindow = new FloatingLoadingWindow();
Task.Run(() => floatingWindow.ShowDialog());
await Task.Run(() => ReallyBusyForm.LoadYourControls());
Invoke((MethodInvoker)(() =>
{
while (Application.OpenForms["FloatingLoadingWindow"] == null)
{
Thread.Sleep(200);
}
floatingWindow.Close();
}));
I think that sometimes work and sometimes don't due that there is non warranty that the floatingWindow.ShowDialog() ran before the loading of controls.
If it ran before I have the visual effect that I need. If it ran after I see how the ReallyBusyForm is creating and adding every child control.
If I do a synchronious call then I block the load of controls.
floatingWindow.ShowDialog(); //this call obviously block the execution of what comes next
await Task.Run(() => ReallyBusyForm.LoadYourControls());
In consequence I need a proper way to show a loading animation when open a form that is very busy creating its child controls.

Setting Visibility property does not show control

I am having problems with visibility of the spinner control.
The control itself works... If I set it to visible right after Initialize it shows and animates as expected.
But if I try to show it from the code it never gets drawn...
the .cs file (presenter)
private void SaveDocument(Document aDocument)
{
if (AllowFlag != null)
{
this.View.ShowDocumentProgressSpinner(true);
this.Save(aDocument);
this.View.ShowDocumentProgressSpinner(false);
}
}
the xaml.cs file
void IDocumentView.ShowDocumentProgressSpinner(bool show)
{
if (show)
{
this.DocumentProgressSpinner.Visibility = Visibility.Visible;
}
else
{
this.DocumentProgressSpinner.Visibility = Visibility.Hidden;
}
}
If i set the visibility to visible right after initialize the spinner works!
part of the xaml of the main control (the spinner is custom control)
...
<Viewbox Grid.Row="3" Width="30" Height="30"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<my:DocumentProgressSpinnerView x:Name="DocumentProgressSpinner" />
...
Probably another threading problem, but I have also tried:
Thread.CurrentThread == Dispatcher.CurrentDispatcher.Thread
TRUE
Dispatcher.FromThread(Thread.CurrentThread).CheckAccess()
TRUE
The control gets invoked, because the "windows spinner" gets activated, just the control never gets shown...
The problem is that you are running your save operation on the dispatcher thread and during the save operation the dispatch thread is blocked the whole time. It's only after your save operation has finished that the UI is updated and thus you will never see the "waiting" state. Instead you should spin off a new thread and from within the event dispatch and set the wait indicator to visible. In the separate thread perform the save operation and once the saving is done, use the dispatcher to hide the wait indicator again on the Dispatcher thread.
See this answer for more details on how to implement this.

Wait until UIElement is drawn (without updatelayout)

I have a function that takes around 5 seconds to complete if a button is clicked. If the button is clicked, I want to display some kind of notification to indicate that the buttonclick is being processed, something like
<Button Click="OnButtonClick" Content="Process Input" />
<Border x:Name="NotificationBorder" Opacity="0" IsHitTestVisible="False"
Width="500" Height="100" Background="White">
<TextBlock Text="Your input is being processed" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
And in code-behind on the button click:
private void OnButtonClick(Object sender, RoutedEventArgs e)
{
DoubleAnimation da = new DoubleAnimation
{
From = 5,
To = 0,
Duration = TimeSpan.FromSeconds(2.5),
};
// Making border visible in hopes that it's drawn before animation kicks in
NotificationBorder.Opacity = 1;
da.Completed += (o, args) => NotificationBorder.Opacity = 0;
NotificationBorder.UpdateLayout(); //Doesn't do anything
UpdateLayout(); // Doesn't do anything
NotificationBorder.BeginAnimation(OpacityProperty, da);
// Simulate calculationheavy functioncall
Thread.Sleep(5000);
}
Somehow UpdateLayout() isn't rendering fast enough, the notification is only displayed after the 5 seconds of Thread.Sleep are over.
Dispatcher.Invoke((Action)(() => NotificationBorder.Opacity = 1), DispatcherPriority.Render); won't work either.
Additionally, I can't let Thread.Sleep run in a separate worker thread - In the real application, it needs to read data from Dispatcher-owned objects and (re)build parts of the UI.
Is there a way to make it visible before Thread.Sleep() is called?
The problem is any long activity on main thread (UI thread) will lead freeze in UI, so you will not see any animation.
You need to do your calculations in a separate thread. I you need to access to Dispatcher-owned objects you can use uiObject.Dispatcher.BeginInvoke or Invoke. BackgroundWorker can help you the way to execute some long calculations and subscribe to the event Completed which will be fired by BackgroundWorker on the UI thread automatically.
If you are ok with UI freeze, change some property and other stuff re-schedule with Dispatcher.BeginInvoke(DispatcherPriority.Background,...), like
NotificationBorder.Opacity = 1;
Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)(()=>Thread.Sleep(5000)));
If you want the UI to update, you need to change your long-running operation to be asynchronous, which means properly decoupling the UI dependencies from whatever logic or perhaps IO-bound operations are taking all the time. If you don't do this, none of the UI updates occurring in your long-running operation will actually be reflected in the UI until it's done, anyway.
If you're running with .NET 4.5, here is my preferred pattern for how to do this:
public async Task DoComplexStuff()
{
//Grab values from UI objects that must be accessed on UI thread
//Run some calculations in the background that don't depend on the UI
var result = await Task.Run((Func<string>)DoComplexStuffInBackground);
//Update UI based on results
//Possibly do more stuff in the background, etc.
}
private string DoComplexStuffInBackground()
{
return "Stuff";
}
This is somewhat 'backwards' from the older style BackgroundWorker approach, but I find it tends to result in cleaner logic. Instead of writing your background code, with explicit Invokes for UI updates, you write your UI code around explicit background work calls via Task.Run(). This also tends to lead to a natural separation between the UI-centric code and the underlying logic code, as you need to refactor your logic into methods that can be called in this way. That's a good idea for long-term maintainability anyway.
So you can dispatch both action (UI and your calculations) into the dispatcher and they will be executed in a line
Dispatcher.BeginInvoke(do ui things);
Dispatcher.BeginInvoke(do your logic things);
But you'd better move your calculation into the background thread.

Categories