Trying to make my windows form lighter - c#

I have a form that contains so many controls and does lots and lots of interactive actions. This is causing my form to have some delays during usage.
One of these controls is a plotting tool that plots lots of data recieved from my server. I thought of moving the ploting tool to another form trying to make my form lighter, reducing the delay problem. I have been told by a friend that this won't help much since it is the same thread handling both of the forms, is that true ?

What your friend says is true but it is unlikely to apply here. A form appears sluggish when it has a lot of controls. When it needs to redraw itself then you'll start noticing the time taken by every control to paint itself. Typically that happens when the form has around 50 controls, but greatly depends on the type of control. Buttons are very expensive, labels are not, for example. Your plot is likely to be expensive so anything that's drawn after it (higher in the Z-order) will be delayed. Try right-clicking the control and click "Bring to Front" so it is drawn last.
Whatever you do, never just make drastic changes like you are contemplating without knowing that you'll improve your program. Which requires measuring first. You'll need a profiler to know where the cpu cycles are being consumed. Profiling painting code is not so easy, given that it doesn't execute very often. That can be fixed, modify your form constructor so it looks like this:
public Form1() {
InitializeComponent();
Application.Idle += new EventHandler((s, ea) => this.Invalidate());
}
Your form now burns 100% core, repainting itself over and over again. But is otherwise still completely functional. Just what you need to effectively profile the painting code.

If you create the form (plotting tool container) on application start, then your start speed well be down...
Then you have 2 way:
1) Move plotting tool container to a new form, but create it when it is require (after application started)
2) Move plotting tool to a new thread. in this case you can move it in an another form and create it by a new thread. so if you use this
way your start speed will be go up

Starting from the answer given here
I've just tried the code and slightly modified it to make it clearer:
static void Main()
{
Thread t1 = new Thread(Main1);
Thread t2 = new Thread(Main2);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
}
static void Main1()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
static void Main2()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form2());
}
I can say it works because I tried using a Thread.Sleep() in one of them and the second form gui didn't lock.

Related

BackgroundWorker to fill a DataGridView

EDIT: Solved using this: http://reedcopsey.com/2011/11/28/launching-a-wpf-window-in-a-separate-thread-part-1/
In my project (.net/windows forms) I'm filling a DataGridView with a large DataTable. Filling can take up to 20 seconds, so I'd like an animated loading window. This animation freezes if the thread is busy, so I'll have to use a new thread either for the window or for filling the DataGridView.
I've tried using the BackgroundWorker to show the form, but it'll be a blank white window in the correct shape.
I've also tried using the BackgroundWorker to fill the DataGridView, but it'll throw an error saying the DataGridView is being accessed by a different thread than the one it has been created for. Since the DataGridView is being created in the designer class, I can't just create it in the new thread - plus that solution doesn't sound very elegant.
What's the best way for me to show a working animated form while filling the DataGridView?
Edit: The answer didn't solve the issue for me, so I've tried to break the code down to something that can be presented here. I didn't do it before because it didn't really seem relevant enough to work through some 1k lines of code. Might be something missing or some remnants from previous experiments in the code presented here. Please ignore bad naming of function, that's a legacy thing I'll fix once I got it working. Parts of the code are ancient.
I've made it run without errors, but the frmLoading still isn't animated (it is if I keep it alive while the working thread isn't busy, though).
namespace a
{
public partial class frmMain : DockContent, IPlugin
{
//...
private delegate void SafeCallDelegate(DataTable dt);
private Thread thread1 = null;
private frmLoading frmLoading = new frmLoading();
public frmMain()
{
//...
}
//...
private void FillDataGrid(DataTable dt)
{
if(this.InvokeRequired)
{
var d = new SafeCallDelegate(FillDataGrid);
Invoke(d, new object[] { dt });
}
else
{
//...
DataGridFiller(dt);
}
}
private void DataGridFiller(DataTable dt)
{
BindingSource dataSource = new BindingSource(dt, null);
//...
dgvData.DataSource = dataSource;
//...
frmLoading.Hide();
}
private void btnGetData_Click(object sender, EventArgs e)
{
DataTable dt = [...];
// Wenn Daten vorhanden sind, dann anzeigen
if (dt != null)
{
//...
frmLoading.Show();
thread1 = new Thread(() => FillDataGrid(dt));
thread1.Start();
}
}
}
}
The second approach is the correct one: use a BackgroundWorker to do any work that will freeze the UI if done in the main thread. About the exception you're getting, that's because the call you're making isn't thread-safe (Because the control was created on a different thread that the one whos calling its methods). Plase take a look at this link to MSDN to understand how to make this kind of call between threads, using backgroundWorker's event-driven model.
From the link:
There are two ways to safely call a Windows Forms control from a
thread that didn't create that control. You can use the
System.Windows.Forms.Control.Invoke method to call a delegate created
in the main thread, which in turn calls the control. Or, you can
implement a System.ComponentModel.BackgroundWorker, which uses an
event-driven model to separate work done in the background thread from
reporting on the results.
Take a look at the second example which demonstrates this technique using the backgroundWorker
EDIT
From your comments I get that the real problem here is size. The problem is that the thread that owns the DataGridView control, is the thread that is displaying it, so no matter how, if you load all the data at once it will freeze for the time it takes this thread to draw all that data on the screen. Fortunately you're not the first that had this problem, and microsoft got you covered this time. This is, I think, a great example of what the XY problem is, the real problem is that you want to load a huge dataset (X) and the answer on how to do that is here, but instead you asked how to display a loading icon while filling your datagrid whithout the UI freezing (Y), which was your attempted solution.
This was from a technical perspective but, from an UI/UX perspective I would like you to think about the actuall use of the form. Do you really need all that data loaded every time you visit this section? If the answer is no, maybe you could think about implementing some pagination (example here). Also 200 colums means horizontal scroll even for an ultrawide monitor. I can't really figure out the user case for you to need all that information on a list/table view at once. I think that maybe implemeting a Master-Detail kind of interface could be more useful.
EDIT 2.0
I think that you can try to create a whole new Form window in another thread, place it over your datagrid and paint the loading animation there. Meanwhile in the main window you can draw the grid without its painting making the loading animation freeze (the other thread is taking care of the drawing). You may want to hold a reference to the thread and kill it, or maybe better try to hold a reference to the From and close it more gracefully.
I've never done this but, I think I recall from some legacy application doing something like that, and it was ugly.
Use an asynchronous task that displays the pop-up loading window until the DataGridView is filled up.
Here's a link to a write-up Microsoft made for async programming: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/

Splash stays on screen if a race condition occurs

Recently I modified an application adding a splash screen.
I decided to use WindowsFormsApplicationBase so I put all the heavy initialization logic inside
protected override void OnCreateMainForm()
On some computers all successive executions of the application after the first one cause the splash screen to be visible forever and main form is not displayed.
I searched on internet to find a solution but it seems that after some bug fixes WindowsFormsApplicationBase is currently well trusted.
So I did the educated guess that the race condition which causes my splash to remain on screen is related to something specific of my application.
During initialization I write some progress signs on the splash with the following method:
public void showCurrentLoadStep(string message)
{
if (this.mySplash.IsHandleCreated)
{
this.mySplash.Invoke(new EventHandler(delegate
{
mySplash.label1.Text = message;
}));
}
}
but I think this is ok.
Besides the last operation done in the MainForm constructor is
timer1.Enabled = true;
I verified that the timer callback is executed also when the splash remains and MainForm is not displayed.
What I'm going to try is to move timer1.Enabled = true; after the creation of MainForm but I would like to understand what goes wrong because, as I have already said, this race condition happens only on some computers so if I don't see it any more I cannot say I have solved it.
Update: I'm using .net framework 4.0 extended
Why are you so sure that this is a race condition?
You need to supply much more code in order for someone not familiar with your application to be able to help you. When are the procedures called, when is the splash dialog initialized, is it from the main form etc.

C# / Winforms: Always keep a form focusable

I want to implement a help-form into my app, which can get the focus, even if a dialog is shown. At the moment i dispose the actual instance of my help if it can't get focused, but i dont think thats the right way. So i want to ask, if theres a option to show a form, seperated from the logic of my main-application.
Things i tried:
calling as a AppDomain (MSDN)
putting the help into seperate app and call it as a process
In both ways, the help(-form) can't get the focus back, when a dialog was called.
I dont want to use the help provided with C#, because i need to show the help(-pages) inside the application.
Thanks
PS: I'm using .Net 2.0.
You can do this by creating an STA thread and using Application.Run() to display the form from that separate thread. Application.Run() will create a separate Message Pump for the other form; this is what keeps it separate.
If you do that, you have to be VERY CAREFUL when communicating between the forms. You will need to use Control.Invoke() or some other inter-thread mechanism to call UI-changing methods on the second form from the first form (and vice-versa).
But if you do this, then the first form can be showing a modal dialog, and the second form will still be focusable.
Note that the second window may be behind the first window because there will be no way to specify the relative Z-order between them.
Showing the second form can be done like this:
private static void ShowIndependentForm()
{
Thread thread = new Thread(ShowIndependentFormImpl);
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
}
private static void ShowIndependentFormImpl()
{
Application.Run(new Form2());
}
You can just call ShowIndependentForm() where appropriate; probably from the main form after you have created it, but my test code in Main() looks like this:
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
ShowIndependentForm();
Application.Run(new Form1());
}
Important
Because the second form has its own message pump, closing the first form will NOT close the program unless you set Thread.IsBackground to true. If you don't, you will have to explicitly close the second form (via calling a method in the second form using Control.Invoke() or some other way) when the first form closes if you want the program to close automatically.

Exception when using secondary UI message pump for a splash screen

I have encountered an odd issue with the way I am showing a splash form, that causes an InvalidAsynchronousStateException to be thrown.
First of all, here is the code for Main{} where I start the splash form:
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Thread splash = new Thread(new ThreadStart(ShowSplash));
splash.Start();
Application.Run(new MainForm());
}
static void ShowSplash()
{
using (SplashForm splash = new SplashForm())
{
Application.Run(splash);
}
}
I am using .NET2.0, with Win XP.
During some testing where the app was left running for may hours, I noticed that the number of exceptions would occasionally increase by one or two.
(Numbers obtained by PerfMon, viewing '# of Exceps Thrown' counter.) These exceptions seem to be caught and swallowed by the runtime, because they do
not ripple up and cause anything to go wrong in the app itself. At least nothing that I can determine anyway.
I have discovered that the exception is thrown when the UserPreferenceChanged event is fired by the system. Since finding this out, I can generate the exception
at will by changing the background picture or screen saver, etc.
I am not explicitly subscribing to this event myself anywhere in code, but I understand (via the power of Google) that all top level controls and forms subscribe
to this event automatically.
I still have not determined why this event is being fired in the first place, as it appears to happen while the app is running over night, but I guess that is another mystery to be solved.
Now, if I stop the splash form thread from running, the exception disappears. Run the thread, it comes back. So, it appears that something is not unsubscribing from the event, and this is causing the subsequent exception perhaps?
Interestingly, if I substitute my splash form with a default, out of the box Form, the problem still remains:
static void ShowSplash()
{
using (Form splash = new Form())
{
Application.Run(splash);
}
}
While this form is being displayed, any UserPreferenceChanged events do not cause any exceptions. As soon as the form is closed, and the thread exits, exceptions will be thrown.
Further research has lead me to this Microsoft article, that contains the following comment:
Common causes are a splash screens
created on a secondary UI thread or
any controls created on worker
threads.
Hmm, guilty as charged by the looks of it. Note that my app is not freezing or doing anything untoward though.
At the moment, this is more of a curiosity than anything else, but I am conecerned that there may be some hidden nasties here waiting to bite in the future.
To me, it looks like the form or the message pump started by Application.Run is not cleaning up properly when it terminates.
Any thoughts?
Yes, you are running afoul with the SystemEvents class. That class creates a hidden window that listens to system events. Particularly the UserPreferenceChanged event, a lot of controls use that event to know when they need to repaint themselves because the system colors were changed.
Problem is, the initialization code that creates the window is very sensitive to the apartment state of the thread that calls it. Which in your case is wrong, you didn't call Thread.SetApartmentState() to switch to STA. That's very important for threads that display a UI.
Beware that your workaround isn't actually a fix, the system events will be raised on the wrong thread. Your splash thread instead of the UI thread of your program. You'll still get random and extremely hard to diagnose failure when an actual system event gets fired. Most infamously when the user locks the workstation, the program deadlocks when it is unlocked again.
I think calling Thread.SetApartmentState() should fix your problem. Not 100% sure, these UI thread interactions are very difficult to analyze and I haven't gotten this wrong yet. Note that .NET already has very solid support for splash screens. It definitely gets details like this right.
I was able to simulate your problem and I can offer a work around, but there might be a better option out there since this was the first time I had come across this.
One option to avoid the exception is to not actuall close the splash screen, but rather just hide it. Something like this
public partial class SplashForm : Form
{
public SplashForm()
{
InitializeComponent();
}
// Not shown here, this is wired to the FormClosing event!!!
private void SplashForm_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = true;
this.Hide();
}
}
Then it will be important that you make the thread that you run the splash screen on a background thread to ensure that the application is not kept alive by the splash screen thread. So your code would look something like this
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Thread splash = new Thread(new ThreadStart(ShowSplash));
splash.IsBackground = true;
splash.Start();
Application.Run(new MainForm());
}
static void ShowSplash()
{
using (SplashForm splash = new SplashForm())
{
Application.Run(splash);
}
}

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