VB.NET - Thread + invoke won't add controls to form - c#

i´ve got a problem. i have multiple threads. each thread shall create a new tabpage. this tabpage shall contain a webbrowser element. the elements are not there yet. they shall be generated after the single threads (up to 20) have been started.
so, i´ve got everything right, i think. i´ve got threads, which call a delegate (as STA Thread) and after working the thread and adding the controls, everything is right, i dont get errors. But every time, the tabe page (including the browser) is "added", there will be no changes in the form. there is no error, but also nothing appears on the form.
what am i doing wrong?
this is the call
frmMain.navigateBrowser("http://example.com")
and then, in the "navigateBrowser" function i will generate some elements and then finally add the elements to the form
tcMain.TabPages.Add(tmpTab)
I´ve had it in a timer before and was working it over a class, which saves the "command" (you! navigate now!) in a global var. but this method was too slow and now i want to try it directly over invoke.
That´s it, hope you can help me :)

Related

Application hangs when hosting managed control as CWnd

My application has ATL-based GUI (CWnd, CDialog,...) and it consists of multiple pages (CDialog). One of these pages is otherwise empty but it has a placeholder frame (CWnd) that resizes with the dialog. Everything is built as x64.
When the page loads, it asks for a control handle from managed (C#) side of the application using COM-interop, and adds the control to the dialog as CWnd that is created from that handle:
Managed implementation simplified:
// Class "ManagedControlProvider"
private Control myUserControl;
public long CreateControl()
{
myUserControl = /*Create some new inheritant of UserControl */
myUserControl.Dock = DockStyle.Fill;
return myUserControl.Handle.ToInt64();
}
Native side simplified:
// Call the managed class. Lifetime of m_pManagedControlProvider
// is ensured elsewhere.
LONGLONG lHandle = m_pManagedControlProvider->CreateControl();
// m_pUserCtrlAsCWnd is CWnd*
m_pUserCtrlAsCWnd = CWnd::FromHandle((HWND)lHandle);
m_pUserCtrlAsCWnd->SetParent(this);
// m_ControlFrame is just a native helper-CWnd the dialog that
// resizes with it a so gives us the size we want to set for the
// managed control. This code is also call in every resize -event.
RECT winRect;
m_ControlFrame.GetWindowRect(&winRect);
ScreenToClient(&winRect);
m_pUserCtrlAsCWnd->SetWindowPos(NULL,
winRect.left, winRect.top, winRect.right - winRect.left,
winRect.bottom - winRect.top, 0);
I have done this multiple times and it usually works exactly as is should. But sometimes, like now, I'm experiencing application hangs without any clear reason. With my current control this seems to happen roughly 5s after the focus is set to some other desktop application.
I have verified that the issue is not in the managed control's lifetime or GC. Also it's reproducible in debug build so optimizations are not to blame. When the hang occurs, I can attach debugger and see that some ATL loop keeps on going but that's the only piece of code I'm able to see in stack (imo this indicates that the message loop is somehow caught in infinite loop without interacting with my code).
Now for the dirties fix ever: I added a separate thread to my managed control that invokes this.Focus() every second on the UI thread. Obviously this is a ridiculous hack but it works as long as I pause the focusing everytime user opens combos etc (otherwise they get closed every second).
What am I doing wrong or what could cause this somewhat unpredictable behavior?
I don't know why or what it has to do with anything, but the application hang somehow originated from WM_ACTIVATE. So the solution was to override WINPROC at the main CDialog and block forwarding of that message. Everything has been working without any issues since then.
I'll not mark this as answer because I don't know why this solution works.

Application hanging on Control.Invoke except when debugging

I have my main form kicking off some background work using Delegate.BeginInvoke and within those delegates I am adding some rows to be displayed on a DataGridView on my main form. I have a backing dataset and a BindingSource attached to that, which I use as the source for my DataGridView.
Whenever I add a row, I do this:
ResultsDataTable.AddResultsRow(row);
RefreshDataGridView();
Where RefreshDataGridView() looks like this:
private void RefreshDataGridView()
{
if(InvokeRequired)
{
//I have tried dgvResults.Invoke() as well
dgvResults.BeginInvoke(new Action(() => RefreshDataGridView()));
}
else
{
dgvResults.Refresh(); //this is where it hangs
dgvResults.FirstDisplayedScrollingRowIndex = dgvResults.Rows.Count - 1;
}
}
It works well, when I add a new row it displays instantly and scrolls (despite my scrollbar not being drawn correctly but I can live with that) as expected, but only when I run the app through the debugger. When I start it without debugging, the application hangs whenever a row is added and it actually needs to scroll.
I've built the application in debug mode and run it without debugging, then let it get to the point where it hangs and attached the debugger to the process to see where it is happening (see comment in code above).
I know this is happening because my main thread is waiting for something but I have no clue what it is waiting for or how to find out.
Does anyone have any ideas?
Update: I started it without debugging then attached the debugger again, and found that the main thread is getting stuck updating a control, but I can't figure out which one.
Update 2: I got rid of the refresh and now it doesn't hang when adding the new row, but I can't resize my form at all without it hanging.
Update 3: It seemed to be hanging while trying to update the scrollbars of the data grid, so I encapsulated it in a panel and gave that scrollbars instead. With a bit of hacking to get the data grid to dynamically size itself based on the data it contains, it's a bit glitchy but no more deadlocks.
I had the same issue. You mentioned that your DataGridView has a bindingsource attached to it. If you are doing something to your source, then you are affecting the DataGridView. You will need to place that line of code that is modifying the source inside the BeginInvoke statement. Once I did that, the issue is gone.

gui not updating

I ran into the following problem with my kiosk application:
I have a window with some thumbnails. When the user clicks on a thumbnail, another window (docview) with info is displayed and in the center a document is shown..
I would like to have the docview visible instantly and then start loading the document (the document loading takes 1-3 secs)and add it to the interface afterwards.
At the moment when I click a thumb, the interfaces freezes for a second or two, and then the docview is visible with the document already..
This is what I do:
viewgrid.Children.Add(docView); // the main window uses this grid to display the windows
viewgrid.InvalidateVisual();
viewgrid.UpdateLayout(); // at this point I would like to have the docView visible
docView.showDocument(); //and afterwards, the loading of the document should start and be also visible eventually..
I tried using the LayoutUpdated event with no success..Also putting the showDocument in a separate thread didn't help either..I also read about Application.DoEvents(), but that's deprecated and seemed bad practice anyway..
How should I resolve this?
Thanks in advance!
You need to use a BackgroundWorker object to load the document. You said you tried threading, and it didn't work, but you need to get threading to work and I recommend you use a background worker.
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker(v=vs.110).aspx
I am not sure what type of document you are loading, but I loaded a RichText file using Binding as described here:
http://www.rhyous.com/2011/08/01/loading-a-richtextbox-from-an-rtf-file-using-binding-or-a-richtextfile-control/
You would do the same thing only you load the document in a BackgroundWorker. Once the document is loaded, you update the bound property and the UI will update.
You can try to use async bindings. Or do it manually with another thread and Dispatcher. But it depends on that your ShowDocument is doing and that is slow.

C# - Cross-thread operation - Create Control in thread, add to main form

I have an older form that I really don't want to rewrite at this point, so what I'm doing is loading the form and then adding it to a panel in the new UI form. This is working fine, but it's slow. The old form does a lot of loading and gathering of data and it's not very efficient. So as a result larger records take up to 30 seconds to load. As you know, creating the form then "locks up" the main UI for about 30 seconds while it loads the old form. This is the action I'm trying to prevent. I want to load the new form, display a "Loading" gif in the blank panel, and then once the old form is loaded remove the "Loading" image and add the form as a control.
And here starts the problem.
I've tried creating a Background Worker but this causes a STA error (old form has a few threaded data loadings of it's own), and since I can't change the worker to STA I stopped trying.
I've tried to create an Invoke (and BeginInvoke) and while this works, it doesn't really load the old form in the thread. It simply sends it back to the UI thread and does the work there. Again this hangs the UI. I.E.: Not what I want.
I've tried to create a delegate and trigger it as an event in the thread, but I get the same results as below...
I've created a thread, set STA on it, started it and then did a while loop with a DoEvents waiting on it to finish. Of course this all seems to work up to the point of accually adding the form to the panel, and then I get the "Control 'ChartForm' accesses from a thread other than the thread it was created on". In this error 'ChartForm' is the old chart that was loaded in the thread.
I've tried the above method, but I instead used a private static field to hold the creating of the old form, and then adding it to the panel once the thread is completed. This is in the method that created the thread, just after the while loop. Same error.
So, I've used the above method in other places with DataTables and didn't have any issue getting the data back to the main thread for use with DataBinding. I know that this is a little different but I didn't think that it would be this hard to do.
Below is the code that I have tried to use that seems to be the closest to what I want.
private static _ChartForm;
private void LoadPatientChart()
{
ClearMainPanel(); // Removes any loaded ChartForms from Panel
if (_Patient == null) // Test to make sure a patient is loaded
return;
loadingPanel.Visible = true; // Displays the "Loading" gif
Thread thread = new Thread(new ThreadStart(this.GetChartForm));
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
while (thread.ThreadState != ThreadState.Stopped)
Application.DoEvents(); // Keeps the UI active and waits for the form to load
this.ChartPanel.Controls.Add(_ChartForm); // This is where the error is
loadingPanel.Visible = false; // Hide the "Loading" gif
}
private void GetChartForm()
{
ChartForm chartForm = new ChartForm(_Patient.AcctNum.ToString(), false);
chartForm.TopLevel = false;
chartForm.FormBorderStyle = FormBorderStyle.None;
chartForm.Dock = DockStyle.Fill;
chartForm.Visible = true;
_ChartForm = chartForm;
}
It's really not a good idea to create UI controls on any other thread than the UI thread. It is technically possible, but it's difficult to manage, especially if the new thread is a "temporary" one.
What you really need to do is refactor out the work that the ChartForm is doing (on construction it appears?) and do that work on a background thread, and then return it to your UI thread and then create your ChartForm passing in the results of that work. IMHO this is a better design anyways; although it may be a lot of work for you.
I don't think what you want is possible without refactoring this "old form". There is only one UI thread, and all UI elements must be created on that thread to be displayed to the user.
I would suggest refactoring the form to display initially without any data (or maybe with a loading image), and then have the form start a background task using BackgroundWorker to perform the long running tasks that are not UI related (going to a database, etc.) Once the worker is complete, then you can run the code that initializes the Form's data elements. This will keep the UI responsive for as long as possible while the blocking tasks are performed.
I've tried to create an Invoke (and BeginInvoke) and while this works,
it doesn't really load the old form in the thread. It simply sends it
back to the UI thread and does the work there. Again this hangs the
UI. I.E.: Not what I want.
You must update the user interface on the main thread, you do not have any choice, if its still hanging then your doing the calculations in the wrong thread.

Showing Multiple Instances Of Same Form?

I'm having some serious issues with a WinForm application that I'm working on.
Currently, I'm using Form1.ShowDialog(); to display a form. This code is contained in a background worker that looks for changes in a database. Using Form1.ShowDialog(); only allows 1 form to open at a time, even if there are multiple changes to the database. What I want to have happen is for multiple forms to open at once if there is more than one change in my database.
When I use Form1.Show();, the application blows up. For some reason, the Show() method makes the forms not display properly (all the elements in the form are missing).
Is there anything I can do to make my code work the way I want it to?
Edit: here's a code snippet
//result is a linq result
foreach (var row in result)
{
Form1 Form = new Form1();
Form.ShowDialog();
}
After a first look, I can tell you this:
Showdialog can't work the way you intend: this very method makes the owner inactive until the dialog is closed. In your case, the loop will pause at the first showdialog, then resume when you close the form, opening a new one and so on until the loop is finished.
As for the "show" problem, creating empty forms, I need more information. The rest of the code and the exception(s) you're getting.
Two points from the top of my head:
1) To open more then one form , use non modal (modeless) method (i think
the show() method). see for example http://msdn.microsoft.com/en-us/library/39wcs2dh.aspx
2) I am not sure you can call UI related method from a non UI thread. You might want to send an event to your UI thread from the worker thread and the UI thread will call the show method

Categories