How do I show an animation while creating user control - c#

In a WPF 4 app, I have a very big user control full of controls which takes something like 4s to initialize on a fast machine. During this time the application is of course not responsive at all.
Is there a way to show an animation in the main window while this control is initialized?
I understand that I cannot create it on another thread. But is there maybe a way to create it with a lower priority from the Dispatcher so that I could show a spinning wheel or so on the main window which would still spin?
(The only solution I can think of right now would be to break the user control into further pieces and load them only when needed. But this will take a lot of development time to change.)
Update1
To be more clear: It is a simple WPF window using tab pages. When a new tab page is opened I'm initializing the user control which holds the controls for this tab page. One of these user controls is so full of controls that it takes 4s until the new tab page will be shown.
So I thought showing a spinning wheel would be better than having a blocked application.

I think you are going to have to break this user control up into pieces. What you can do is use a BackgroundWorker to coordinate the 'building' of this user control. Each time the DoWork event is fired, use Dispatcher.BeginInvoke to create an add the next control to your UI. This technique is described in the following blog post:
http://loekvandenouweland.com/index.php/2010/12/wp7-add-user-controls-graphics-in-background-thread/
This will allow you to show an animation during loading.

Why can't you initialize it on another thread? I see two scenarios:
Initialization is slow for non-WPF reasons that be preloaded/precomputed on another thread before entering the core WPF initialization.
WPF itself is consuming 4 seconds of CPU time (though, that's really WTF levels of CPU time...). If so, you can start another STA thread with it's own message pump that can display an independant UI (e.g: a spinning wheel) until the primary thread is done loading.
You can create "dialogs" which implicitly create a new Dispatcher and run on a background thread, or you can explicitly create your own Dispatcher (=message pump).
I use the following method to do so:
public static Dispatcher StartNewDispatcher(ThreadPriority cpuPriority = ThreadPriority.Normal) {
using (var sem = new SemaphoreSlim(0)) {
Dispatcher retval = null;
var winThread = new Thread(() => {
retval = Dispatcher.CurrentDispatcher;
sem.Release();
Dispatcher.Run();
}) { IsBackground = true, Priority = cpuPriority };
winThread.SetApartmentState(ApartmentState.STA);
winThread.Start();
sem.Wait();
return retval;
}
}
This gives you real multi-threaded UI; but it also means you can't databind or in any other way directly communicate between the two UI's: after all, WPF objects have thread-affinity.
Before you go this route, verify there aren't any slow components you can preload using a profiler: option 1 (preload heavy stuff before WPF init) is simpler and cleaner.

A solution could be to move the slow part (loading data? that doesn't belong IN the control) to another thread as you mentioned.
Or use a VirtualizingStackPanel to lazy load what is needed.
Could you elaborate on the reason for the delay?

Related

The Thread is On WaitSleepJoin State and is NOT Running anymore

I have a multi-threaded application created on top of C#.
In my application i have 1 form with two panels.
pnlSearch
pnlSlide
Each one is controlled by each Thread:
TH_Slide
TH_Search
TH_Slide is called on the Form_OnLoad where i can display all information of my objects inside the pnlSlide.
while(true)
{
// do some infinite work to slide objects information.
Thread.Sleep(20000); // display for 20 secs.
}
The pnlSearch is hidden and will show up when TH_Search is called this is handled when the mouse moved inside the form. And inside the pnlSearch there is txtFilter textBox and a UserControl object as a holder to display the searched information dynamically.
Now the problem is when i type in the txtFilter of a pnlSearch and load the information from my database and display it to the UserControl.
The TH_Slide.ThreadState becomes WaitSleepJoin and would not running anymore hence the slide information in pnlSlide wont make any changes.
NOTE: Cross Thread Operation is handled using delegates therefore it would not throw any errors.
My application is now perfectly working by setting the
TH_Slide.IsBackground = true
TH_Slide.Priority = ThreadPriority.Highest
TH_Search.Priority = ThreadPriority.Normal
I am new to multi-threading programming and i can't fully understand why this algorithm solves my problem.

Is there a way to not have a WPF MainWindow method terminating to avoid threading?

I'm currently working on a WPF project that is trying to continuously update a listbox based on a network streamed source.
It is my understanding that a initializeComponent() method will only actually display the WPF window once the MainWindow() method has terminated. However I am trying to have a while(true) loop inside it in order to continuously listen for update signals from a server and update the listbox with appropriate values.
Each individual method is working fine, it's just that it doesn't open the WPF window in its current form due to the while loop. I am trying to avoid using a background update thread in order to update the list box because I am aware that I would have to implement functionality in order to pass the thread "ownership" of the listbox to the thread and I'm not 100% sure of how to do this.
Is there a workaround, or better yet, is there something obvious that I'm missing to achieve my required functionality?
The code is as follows:
public MainWindow()
{
TcpClient client = new TcpClient();
client.Connect(serverAddress, port);
NetworkStream stream = client.GetStream();
numberOfPumps = 0; //initialize as 0 on startup.
handshake(stream);
InitializeComponent();
updatePumpList(stream);
updateListBox();
while(true)
{
updatePumpList(stream);
updateListBox();
}
}
The updateListBox() method is simply adding items to the listbox from a dictionary.
private void updateListBox()
{
foreach(KeyValuePair<string, PumpItem> kvp in pumpDict)
{
pumpListBox.Items.Add(kvp.Key + ": " + kvp.Value.state);
}
}
Unfortunately, you just can't do it the way you want to. In a Windows application, you must let the main (UI) thread run. Any looping you do on that thread will hang the whole application until it's done. If the window's up, that looks like the window is frozen (because it is). If it's not up yet, it looks like a wait cursor, forever. No way around that. The thread has to be left alone to process input, update the window, etc. Even manually pumping the message loop (anybody remember MFC?) is a poor expedient. Windows applications work best if you leave the main thread to do its thing as the designers intended.
We do a lot of stuff on the main thread of course, but it's quick stuff that hands control back before the user notices any latency. Synchronous Internet access is never quick enough, and a polling loop that lasts for the lifetime of your process is out of the question.
You've got two options here, both of them pretty anodyne in practice.
You could use a DispatchTimer, with asynchronous internet access.
The other is the worker thread you're trying to avoid. They're not that bad. Just keep a reference to the Thread object around to abort on program shutdown, and the thread has to "invoke into" the UI thread when it does anything that'll touch any UI (including setting any property that will raise a PropertyChanged event).
It's not a big deal at all:
Action act = () => Status = newStatus;
App.Current.Dispatcher.Invoke(act);
Have you considered setting up an onLoad event handler for your WPF window which will trigger once the WPF window is displayed? The event handler could then run your while loop accordingly.
Alternatively, you could have a timer primed to fire an event a few moments after the end of the constructor, allowing the window to display and then the while loop to begin.

Can I freeze my UI rendering while my form loads?

Is there any way I can pause all UI Update commands in Winforms?
Or I have a slight feeling I'm trying to go about this the completely wrong way, so is there another way around my problem: I basically load a saved state of a control, which loads new controls all over it. However I do some of this in the UI thread, and some of the data loading from another thread, which then fills the UI.
So the effect I have when it is loading is that the user can see a few of the controls appearing in one place, then moving to another place on the form, changing values, etc.
I'd like to get a loading screen instead of this and load the controls in the background. It's quite a large application and its not THAT important so redesigning my code isn't really an option.
Can I simply stop all Update() commands on a control while a method is executing?
You can use the SuspendLayout and ResumeLayout methods to wrap the setup of UI in one operation (without the update of the rendering).
Basically (assuming SomeMethod is in the form class):
private void SomeMethod()
{
this.SuspendLayout();
// all UI setup
this.ResumeLayout();
}
it really depends on your form logic, in general you should not overload the Load or Show method with too much things so that the form can be shown and drawn quickly and always look responsive.
in some cases it could help to use the SuspendLayout and ResumeLayout methods, see here:
Control.SuspendLayout Method

WinForm And Looping

I have a WinForm set up and a process that loops until a button is pressed on the form.
When I try to run my code, the form does not even display. I suspect this is because the code gets stuck in the loop and doesn't get far enough to display the WinForm. How can I get the form to display and the loop to run after that point?
If you're looping because you need to do something with the GUI periodically while waiting for input, I suggest using a Timer control and its Tick event.
If you want to do non-GUI things while waiting, a more traditional timer is better suited to the task,
You should probably run the loop in a background thread. The BackgroundWorker class makes this pretty easy to do.
Don't do that.
Windows Forms (like most modern user interface development toolkits) is an event-driven framework. You should never use a loop that "waits" for something to happen; instead you want to use an event that triggers something to happen.
Essentially what's happening is this: WinForms has a loop running a message pump that listens for events from Windows and triggers C# events in response to them. Your code is executing on the same thread as that message pump (it has to, since in WinForms only one thread is allowed to touch any given control). So if you put that thread into a loop, the WinForms code that should be pumping messages isn't, and your user interface appears to hang, since it isn't responding to any messages from Windows. (If you keep clicking it, you will fill up the message queue and get a dialog box that says "This application has stopped responding, do you want to terminate?" or something like that.)
The correct solution is to do one of the following:
Use a Timer
Use a BackgroundWorker
Use a ThreadPool
Another solution that would work, but is not a good idea is:
Use Application.DoEvents() -- but please don't actually do this
Your form loading is freezing because the UI of a windows form runs in a single thread. And the logic that you put on the Load event of this form is running on that thread.
You can run your loop on a separate thread easily by using a BackgroundWorker component on your windows form. On the event DoWork of your background worker, you place the code that has the loop that should run without block your UI. On the Form.Load event, you can start the background worker component by calling the method RunWorkerAsync. On the event handler of your button, you place a code to stop the background worker by calling CancelAsync method.
The article How to: Implement a Form That Uses a Background Operation shows exactly how to accomplish it.
About your comment on not being able to update the Text of a TextBox from a your background worker component. It happens because it is not allowed to modify the state of a windows forms control from a different thread (your background worker code is running on a separated thread) MSDN documentation says:
Access to Windows Forms controls is not inherently thread safe. If you have two or more threads manipulating the state of a control, it is possible to force the control into an inconsistent state. Other thread-related bugs are possible, such as race conditions and deadlocks. It is important to make sure that access to your controls is performed in a thread-safe way.
A sample of how you can update the state of your windows forms controls from your background thread will be similar to the one below (assuming that the new value is already stored on a String variable named text):
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.textBox1.Text = text;
}
I borrowed this code snipped from How to: Make Thread-Safe Calls to Windows Forms Controls article. It can provide you more information about how to deal with multi-threaded windows forms.
You can use the form load event to trigger the start of the loop.
So it would handle the event Me.Load
However is it necessary for your loop to be happening inside of the UI?
This happens because your loop is keeping the window function from processing messages, such as those that tell it to repaint itself. Place a call to Application.DoEvents() inside of your loop to allow the UI to continue to function.
However, you need to ask yourself why you're looping like this in the first place. If you're, say, copying a bunch of files, this might make sense. For most tasks, though, responding to a timer tick should do the trick and won't block the UI.
You should run your loop in a background thread using the BackgroundWorker component.
Remember that the background thread cannot directly interact with the UI controls.
To report the progress on the UI, you should call the BackgroundWorker's ReportProgress method in the background thread, and handle the ProgressChanged event to update the UI.
You can call the CancelAsync method when the Button is clicked, and loop until the CancellationPending property is true.

Delay loading of combobox when form loads

I've got a Windows Forms (C#) project with multiple comboboxes/listboxes etc that are populated when the form loads.
The problem is that the loading of the comboboxes/listboxes is slow, and since the loading is done when the form is trying to display the entire form isn't shown until all the controls have been populated. This can in some circumstances be 20+ seconds.
Had there been a Form_finished_loaded type of event I could have put my code in there, but I can't find an event that is fired after the form is done drawing the basic controls.
I have one requirement though - the loading has to be done in the main thread (since I get the items from a non-threading friendly COM-application).
I have found one potential solution, but perhaps there is a better way?
I can create a System.Timer.Timer when creating the form, and have the first Tick be called about 1 second later, and then populate the lists from that tick. That gives the form enough time to be displayed before it starts filling the lists.
Does anyone have any other tips on how to delay the loading of the controls?
There is the Shown event that "occurs whenever the form is first displayed.". Also you may want to use the BeginUpdate and EndUpdate functions to make the populating of your combobox faster.
It has that certain smell of workaround, but this approach should fulfil your needs:
private bool _hasInitialized = false;
private void Form1_Shown(object sender, EventArgs e)
{
if (!_hasInitialized)
{
ThreadPool.QueueUserWorkItem(state =>
{
Thread.Sleep(200); // brief sleep to allow the main thread
// to paint the form nicely
this.Invoke((Action)delegate { LoadData(); });
});
}
}
private void LoadData()
{
// do the data loading
_hasInitialized = true;
}
What it does is that it reacts when the form is shown, checks if it has already been initialized before, and if not it spawns a thread that will wait for a brief moment before calling the LoadData method on the main thread. This will allow for the form to get painted properly. The samething could perhaps be achieve by simply calling this.Refresh() but I like the idea of letting the system decide how to do the work.
I would still try to push the data loading onto a worker thread, invoking back on the main thread for populating the UI (if it is at all possible with the COM component).
Can you get your data from a web service that calls the COM component?
That way, you can display empty controls on a Locked form at the start, make Asynchronous calls to get the data, and on return populate the respective combos, and once all of them are loaded, you can unlock the form for the user to use.
You could listen for the VisibleChanged event and the first time it's value is true you put your initialization code.
Isn't FormShown the event you're looking for?
When you say that you cannot use a background thread because of COM what do you mean? I am using many COM components within my apps and running them on background threads.
If you create a new thread as an STAThread you can probably load the ComboBox/ListBox on a Non-UI thread. IIRC the ThreadPool allocates worker threads as MTAThread so you'll need to actually create a thread manually instead of using ThreadPool.QueueUserWorkItem.

Categories

Resources