Threading with UI elements - dynamically created controls?

R

RCS

I have a UI that needs a couple of threads to do some significant processing
on a couple of different forms - and while it's at it, update the UI (set
textboxes, fill in listviews). I created a base class for the worker class,
and made up some functions/delegates to handle the invoke stuff for the UI
and that was fine for a prototype. I rewrote this chunk, broke things out
into different classes - but the threading is still the same - and one
specific problem I'm having is this.

Within the worker thread, I do this:

ListViewItem lvwItem = new ListViewItem();

I want to try to do as much work as possible, while leaving a lot of
potential functionality in the worker thread. So I'd like to pre-populate a
ListViewItem - and then do the Invoke to just add that pre-filled
ListViewItem to the listview on the UI.

On that line, I get an error of:

Illegal cross-thread operation: Control 'frmPopup' accessed from a thread
other than the thread it was created on.
Stack trace where the illegal operation occurred was:

at System.Windows.Forms.Control.get_Handle() etc, etc

And it happens right at that line above when I'm trying to create a
ListViewItem programatically. I am assuming that when you create a UI
element programatically, it needs a window handle? If so - how do I get
around this? And I know I've had this work before, the only thing different
is I have this functionality in a seperate class.

Also - I got rid of 100% of the functionality in that thread (commented it
out) - and slowly added lines back in, and this is the only, even remote
reference to a UI element in this chunk of code, and this is the specific
line that throws the exception.

Any ideas???
 
E

Elidel

I don't know how your creating your thread, but it sounds like the error is
in how you pass back the result to the main ( calling ) thread.

So, for example, if you try to add lvwItem to frmPopup in the spawned thread
you might get that error.

If you set up, say, a thread pool, that calls a method that returns
ListViewItem as a result, and then add it to frmPopup control array, that
might be different.
 
R

RCS

Windows doesn't allow a secondary thread to update any UI elements. You have
to have the original UI thread do that. So what you can do from the
secondary thread is:

if ( InvokeRequired )
{
BeginInvoke(...)
}
// Do the real work here


That part of this, is all good and works fine. There is somewhat of a
detailed class that handles all the UI elements that I'd need to update from
a background thread. But way before I get to that, and to address what
you've asked. From the UI thread I do this to start things off:


System.Threading.ThreadStart objTS = new System.Threading.ThreadStart(
LoadListView1 );
System.Threading.Thread objThread = new System.Threading.Thread( objTS );
objThread.Name = "LoadListView1";
objThread.Start();


Then, within that function, I am immediately doing this:


ListViewItem lvwItem = new ListViewItem();


That is enough to make it fail. So, I'm not actually trying to update any UI
elements on a form, at this point - I'm just programatically creating a UI
element that will be added to one of my forms later.
 
E

Elidel

RCS said:
Windows doesn't allow a secondary thread to update any UI elements.

I'm not saying it should.

I'm saying you can return a new object -- which has the values you want, and
then in the main thread, update the UI element with that value.
 
E

Elidel

RCS wrote:

System.Threading.ThreadStart objTS = new System.Threading.ThreadStart(
LoadListView1 );
System.Threading.Thread objThread = new System.Threading.Thread( objTS );
objThread.Name = "LoadListView1";
objThread.Start();


Then, within that function, I am immediately doing this:


ListViewItem lvwItem = new ListViewItem();


And you then get the error:
 
E

Elidel

RCS wrote:

System.Threading.ThreadStart objTS = new System.Threading.ThreadStart(
LoadListView1 );
System.Threading.Thread objThread = new System.Threading.Thread( objTS );
objThread.Name = "LoadListView1";
objThread.Start();


Then, within that function, I am immediately doing this:


ListViewItem lvwItem = new ListViewItem();

And you then get the error:

Control 'frmPopup' accessed from a thread ?

What does objTS look like ?
 
R

RCS

No no, that's a rule in Windows programming. You can't reference a loaded UI
object that was not created in your thread - you get a runtime error (this
is VS.NET 2K5 beta 1 by the way - I believe it was allowed in VS.NET 2K3)
 
E

Elidel

RCS wrote:

So the idea is

1. Create a ListView0 in the main thread.
2. Spawn the thread, create the ListView1 with data.
3. Take the return value of the threaded method, and set

ListView0 = ListView1

No no, that's a rule in Windows programming. You can't reference a loaded
UI object that was not created in your thread - you get a runtime error
(this is VS.NET 2K5 beta 1 by the way - I believe it was allowed in VS.NET
2K3)
 
R

RCS

Well, I'd rather not do that, because there are a lot of screens and lot of
different UI elements - and I'd rather do it the proper way. Ideally:


UI Thread:
Draw the interface, create a listview, spawn a new thread that does
"something", then sit idle

Background Thread:
Dynamically create an unbound programatic ListViewItem (an item that is in a
ListView) - populate that, then BeginInvoke to the UI thread to have it add
the new row.


meanwhile, the idle UI thread gets a BeginInvoke to insert a new
ListViewItem.. a couple of these listviews update over time.. so
pre-loading them, then copying them would be bulky and messy. I want to be
able to do this one row at a time..

thanks
 
E

Elidel

RCS said:
Well, I'd rather not do that, because there are a lot of screens and lot of
different UI elements

Does this help:

http://www.codeguru.com/columns/VB/print.php/c6553/
Asynchronous Programming with Thread Pools

Listing 1: Multithreading with an existing thread in ThreadPool.

1: Private Sub Form1_Load(ByVal sender As System.Object, _
2: ByVal e As System.EventArgs) Handles MyBase.Load
3:
4: ListBox1.Items.Clear()
5: ThreadPool.QueueUserWorkItem(AddressOf Initialize)
6:
7: End Sub
8:
9: Private Elem As String
10: Private Sub Add()
11: ListBox1.Items.Add(Elem)
12: Application.DoEvents()
13: End Sub
14:
15: Private Sub Initialize(ByVal State As Object)
16: Dim I As Integer
17:
18: SyncLock ListBox1.GetType
19:
20: For I = 10000000 To 1 Step -1
21: Elem = I
22: ListBox1.Invoke(CType(AddressOf Add, MethodInvoker))
23: Next
24:
25: End SyncLock
26:
27: End Sub




- and I'd rather do it the proper way. Ideally:
UI Thread:
Draw the interface, create a listview, spawn a new thread that does
"something", then sit idle

Background Thread:
Dynamically create an unbound programatic ListViewItem (an item that is in a
ListView) - populate that, then BeginInvoke to the UI thread to have it add
the new row.


meanwhile, the idle UI thread gets a BeginInvoke to insert a new
ListViewItem.. a couple of these listviews update over time.. so
pre-loading them, then copying them would be bulky and messy. I want to be
able to do this one row at a time..

thanks
 
R

RCS

Thanks for the help - this ended up being the VS.NET 2K5 beta 1 IDE messing
up - if I closed and re-opened the project, I get a different error, and it
still showed it was coming from the same line. That was not correct. I fixed
the error and all works fine.

I guess I shouldn't expect so much from a beta 1 product!!

Elidel said:
RCS said:
Well, I'd rather not do that, because there are a lot of screens and lot
of different UI elements

Does this help:

http://www.codeguru.com/columns/VB/print.php/c6553/
Asynchronous Programming with Thread Pools

Listing 1: Multithreading with an existing thread in ThreadPool.

1: Private Sub Form1_Load(ByVal sender As System.Object, _
2: ByVal e As System.EventArgs) Handles MyBase.Load
3:
4: ListBox1.Items.Clear()
5: ThreadPool.QueueUserWorkItem(AddressOf Initialize)
6:
7: End Sub
8:
9: Private Elem As String
10: Private Sub Add()
11: ListBox1.Items.Add(Elem)
12: Application.DoEvents()
13: End Sub
14:
15: Private Sub Initialize(ByVal State As Object)
16: Dim I As Integer
17:
18: SyncLock ListBox1.GetType
19:
20: For I = 10000000 To 1 Step -1
21: Elem = I
22: ListBox1.Invoke(CType(AddressOf Add, MethodInvoker))
23: Next
24:
25: End SyncLock
26:
27: End Sub




- and I'd rather do it the proper way. Ideally:
 
I

INGSOC

RCS said:
Thanks for the help - this ended up being the VS.NET 2K5 beta 1 IDE
messing up - if I closed and re-opened the project, I get a different
error, and it still showed it was coming from the same line. That was not
correct. I fixed the error and all works fine.

Rule one of support: make sure it's plugged in.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top