I have a form with 2 controls on it. Control 1 has several search criteria and the user can click a button to start the search. Control 2 has a GridView that will display the search results and also has a PictureBox with an animated Gif in it.
Control 1 has a method in it that has a nested foreach loop that takes upwards of 5 minutes to complete (depends upon the search criteria, sometimes it finishes almost instantly).
What happens is that I call a method on Control 2 that makes the PictureBox visible and the animated Gif starts running. As soon as the foreach loop is hit that takes a while, the image completely stops. Whenever I show any MessageBoxes the image starts running again (I presume it is because the MessageBox is blocking). As soon as the MessageBox is closed, the image stops animating.
I presume I will need to utilize something with threading, but everything I am trying (ImageAnimator, Thread calling a method that calls Invoke, etc.) all seem to not work. Any hints what events/libraries I need to work with in order to make this work? Any good examples to follow?
You long running code runs in the GUI-Thread and blocks the internal message loop, so any GUI is stopping. Try to run your long running code in a separate worker thread.
2 words: BackgroundWorker class.
http://msdn.microsoft.com/en-us/library/waw3xexc.aspx
Related
I have an winforms application with two controls in which I do custom drawing. The main control shows a small section of a long continuous image of a road, while the other control shows a horizontally-squashed representation of the entire road image. The second control is used to navigate the first: you can click anywhere in the overview to scroll to that location in the main view. Here's a screenshot, where the main view is on top and the overview is on bottom:
There's a cyan line in the overview control that indicates the position of the imagery being shown in the main view. (In this screenshot, it's just over 1/3 the width from the left.) The user can click and drag that line in the overview, which will cause the main view to scroll.
I've noticed something odd about the redrawing: When I'm dragging the cyan line in the overview control, the main view gets redrawn much more often than the overview, even though both are invalidated in response to the mouse move. I know that WM_PAINT messages only get sent when there are no other messages in the queue, but I don't understand why one control would get redrawn more often than the other if they're both being invalidated at the same time.
Actually, as I was typing that last sentence I think I stumbled on the reason. Tell me if this sounds right:
Each control has its own message queue, and the main control isn't receiving any input-related messages, so its message queue is empty more often than that of the overview, which has to process all the mouse events. So it receives the WM_PAINT message more often than the overview.
So at this point, I guess the question is, "Does that make sense? Is that what's happening?"
The theory is not sound, there is only one message queue per thread. What you see is almost certainly caused by the way WM_PAINT is generated. It is only delivered when the queue is empty. That makes it a "low priority" message, user input always goes first. Important that it works that way, you would not want user input to get lost, or the message queue to explode, because painting code is slow.
So, roughly, you called Invalidate() twice. The bottom-most window gets the Paint event but by the time it is finished there is yet another mouse event waiting to be processed. So the second window does not get its paint event and you'll invalidate a window that is already invalidated. Only when you slow down the mouse, or stop moving it, can it catch up.
Easy to see from Task Manager, you'll see the UI thread of your program burning 100% core.
Forcing a paint anyway is possible, but you have to call Update() instead of Invalidate(). Your program still burns 100% core but now instead of skipping paints it will be less responsive to the mouse. That sounds dangerous, with the potential to flood the message queue, but it is not. WM_MOUSEMOVE does not get added to the message queue either. Like WM_PAINT, it only gets generated when the queue is empty. You'll get it first.
I am trying to create a project where I have a picture button and once the user clicks on it the picture changes to something else.
But once it changes I want that picture to go away and go back to the original before it was clicked.
I am stuck at the point to create that "timer" for the second picture to show. Also the second picture that shows up is a random picture from 2 other pictures. So it needs to change pictures randomly after every button click.
Once idea that comes to mind is that you could try to use a BackgroundWorker implementation.
Click on Picture:
Change the image to whatever you want
Start the background worker
Background Worker Implementation:
Thread.Join() for however long you want it keep that image
return when done
Background Worker Task Completed:
Set the image back to the original picture
It's not the true intent of the BackgroundWorker, but it will probably solve the problem you are trying to get around (not tying up the UI thread).
There are lots of articles about how to keep the UI thread free in WPF. Mostly, using async-await to perform long operations in the background. But what can I do when the UI generation itself is heavy?
Say I have a view model that holds a list of items in a property called "Items" of type List, and a view that presents them using an ItemsControl that is bound to that list and generates ui for each item using a data template. I am reading information from the server and while I am getting the data, there is some sort of animated "Busy" indicator that is bound to a IsBusy property in the view model. All is well with that approach and it keeps the UI responsive.
Once the data had returned and the task is done, I am also generating the observable objects (ItemType) for each item in a background task (since no UI is bound to them yet, this is not a problem), and copy the information from the result that came from the server. Still - all nice and responsive.
The last step must happen in the UI thread. Once the List is ready, placing it inside the Items property yields a notification that causes the ItemsControl to start generating UI for each item, and this happens (and must happen) on the UI thread. Since this is a heavy task (there are not a LOT of items but each items has a pretty heavy UI template), the UI hangs and the busy indicator no longer animates.
My question is this: is there a way to keep the UI responsive while the items control is populating? This currently takes between 0.5 - 3 seconds and its too long a task for the UI to remain stuck.
EDIT:
Just to make it clear - this is not a question about Virtualization. I have no problem with the view taking long to create, I just want to find a way to keep to UI responsive while it is being created. I thought about perhaps drawing it while its hidden, or even collapses, but it does no change anything since the UI thread is still busy while doing that, and the busy animation still gets stuck, and the entire UI along with it.
Thanks
Yes. Don't draw all the items at once - only draw those you need to draw immediately. This is otherwise known as UI virtualisation.
Microsoft give a good example of how to implement virtualisation using a canvas at the following link: Virtualized WPF Canvas
Update after comment:
If you hide the panel, the items are actually drawn on to the panel but are invisible. To stop the items appearing one by one, set the panel to Visibility.Hidden and then to Visibility.Visible once you've drawn the items. The panel appears blank and then is suddenly populated.
I have created an animation that takes 400 milliseconds to run, and attached it to an EventTrigger for the Loaded RoutedEvent of my Window.
But I think the window doesn't show up right away after it's loaded*, so I can't see the animation at all.
What are some common patterns for running animations when a Window loads?
And also, when should I run animations in separate threads?
* It probably draws the window after it is loaded and it shows the window after it's finished drawing, clarification would be helpful.
400 Miliseconds is pretty fast, so a loading window can take longer. I'd suggest keeping it in the same thread, but using a timer event.Timer Interval to delay it until the window is fully loaded.
I use the time events for splash screens, etc... and would do the same, I am sure, for a timed animation. It just takes a little practice. My suggestion is to use a long delay on the time to make sure it acts the way you want... then reduce the timer to put it where you want in the load screen.
I have tried the solutions suggested in the following answer:
https://stackoverflow.com/a/8886941/494094
Window.ContentRendered
UIElement.LayoutUpdated
Window.Activated
So far, Window.Activated event seems to suit me best. The only caveat is that none of those events are RoutedEvents, so you need to define the animation in the code behind like the following:
this.Activated += (sender, args) => ((Storyboard) FindResource("MyAnimation")).Begin();
I'm trying to slide a control to the right when I move the cursor on that control and slide back when the cursor is out.
Since the form contains many control with the same animation, I've decided to let those control slide on different threads. The problem is, when I create a thread and use Control.Invoke() to change the location of the control, the main UI freezes until the animation is completed.
What am I doing wrong? Or is there any way to work around?
Edit: Also used Control.BeginInvoke(). The result is the same.
That happens when you try to move the control faster than UI thread can handle. The below library is a good example of how to do animations with a SINGLE global FRAME-LIMITED timer for all animations.
WinForm Animation Library [.Net3.5+]
A simple library for animating controls/values in .Net WinForm (.Net
3.5 and later). Key frame (Path) based and fully customizable.
https://falahati.github.io/WinFormAnimation/
new Animator2D(
new Path2D(c_control.Location.X, c_control.Location.Y, c_control.Location.X + 100, c_control.Location.Y, 250))
.Play(c_control, Animator2D.KnownProperties.Location);
This moves the c_control control 100 pixels to the right in 250ms.