Jump to content
xisto Community
Sign in to follow this  
tansqrx

Multithreaded Listview Control In Different Classes Problem

Recommended Posts

I maybe crossing into the advanced questions on this one but I have been working on this problem for a little over a week now and I am just plain stuck. I have a windows application in VB.NET. The main form has a listview with approx 15 items and five subitems in each item. The list view acts as a display grid for data. The application performs many operations at once so naturally I have made it threaded to keep the main form responsive. The threads are running in their own classes. To give you a general idea see below.


Public Class frmMainâŚDim loginThread As New Thread(AddressOf modBotControler. dosomething)loginThread.startâŚEnd class Public Class modBotControlerâŚSub dosomething()âŚEnd subEnd class

Now I need to update the listview from the thread in dosomething(). At first I just passed a variable as a property into the modBotControler class. This worked fine except the program would crash for no good reason sparatically. I moved to Visual Studio 2005 and found the error of my way (at least I hope so). Apparently you can not change the value of a windows control from a different thread than it was created on. Research showed that MSDN offered a solution. ms-hel

 

ms.vsexpresscc.v80/MS.NETFramework.v20.en/dv_fxmclictl/html/138f38b6-1099-4fd5-910c-390b41cbad35.htm

 

The solution uses a delegate to move the operation to the thread that the main form is on.


' This event handler creates a thread that calls a ' Windows Forms control in a thread-safe way. Private Sub setTextSafeBtn_Click( _ ByVal sender As Object, _ ByVal e As EventArgs) Handles setTextSafeBtn.Click Me.demoThread = New Thread( _ New ThreadStart(AddressOf Me.ThreadProcSafe)) Me.demoThread.Start() End Sub' This method is executed on the worker thread and makes' a thread-safe call on the TextBox control.Private Sub ThreadProcSafe() Me.SetText("This text was set safely.") End Sub' This method demonstrates a pattern for making thread-safe' calls on a Windows Forms control. '' If the calling thread is different from the thread that' created the TextBox control, this method creates a' SetTextCallback and calls itself asynchronously using the' Invoke method.'' If the calling thread is the same as the thread that created ' the TextBox control, the Text property is set directly. Private Sub SetText(ByVal [text] As String) ' 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 Me.textBox1.InvokeRequired Then Dim d As New SetTextCallback(AddressOf SetText) Me.Invoke(d, New Object() {[text]}) Else Me.textBox1.Text = [text] End If End Sub

Now I am sure that this works wonderful if you have everything thing in one class but I do not. One requirement is that the SetText function be private. You will also notice that when SetText is called, Me.SetText("This text was set safely."), that they used the Me operator which I can not do outside the current class. I have tried passing a reference to the main form and calling it that way, didnât work. I have tried passing a reference to the SetTextDelegate, didnât work. I have also tried about 100 other different things, didnât work. If anyone has ever been in this sisutiton, please let me know what you did to fix it. BTW, the example uses a textbox and I am using a listview. Does that make a difference?


Share this post


Link to post
Share on other sites

how about use a global variable as flags. I mean first have a timer that checks this global variable every once in a while, lets say half a second then depending on the value of the global variable update or do not update the list view. I don't think using a timer will slow down other threads in the process. I use this technique once, a program loader, it works for me. It didnt slow the process or didnt even consume much of the precious CPU.

Share this post


Link to post
Share on other sites

At long last I have found a solution to my threading problems. And all I can say is that I strongly believe that I was a victim of code gremlins. I just moved to Visual Studio 2005 where I first realized that I even had a problem. After literally trying 100âs of different combinations I just started throwing stuff at the wall to see what would stick. In a last ditch effort, I simplified a sub by commenting out a try/catch and putting the call to the listview just after the sub beginning. To my absolute surprise it worked. I uncommented the try/catch and it still worked. I didnât change any meaningful code and the **** program worked. After a few hours of consideration, in the end I think I hit a Visual Studio bug. The reason that I say this is while fooling around with the dLVMain function, see code later, every now and again I would get one of those green squiggly lines out of no where. Looking at the function, nothing had changed and when I moused over the error, Visual Studio would crash. I didnât think much of it at the time but later on I have a feeling that this might have been part of the problem. I donât know about you but that is the one thing that gets me going. Working on a problem that you look at for a week knowing that it should work, only to find out that it is a **** bug. Ohh well what can you do. Saga, I did consider a similar work around earlier but I had to rule it out. The listview is generated dynamically and can have any number of elements. This can lead to a problem allocating all the static variables (can be in the 100âs). I really hate the idea of using timers in this fashion. The point of using .NET is so I do not have to resort to resource consuming timers. Everything by nature should be event driven. Implementing this solution may also hold more hidden problems. Another possibility that I considered was to raise an event and then set the listview values. I soon realized that the event is raised in the thread that called the event and not in the thread that holds the windows form thus the problem is not solved. Although untested, I am afraid using timers would pose the same problem. Enough with the *****ing, here is the solution. The result is a little testing along with examples from ms-hel 

 

ms.vsexpresscc.v80/MS.NETFramework.v20.en/dv_fxmclictl/html/138f38b6-1099-4fd5-910c-390b41cbad35.htm

 

and Programming Microsoft Visual Basic.NET 2003 by Francesco Balena [ http://www.dotnet2themax.com/BooksFrancesco.aspx ]. I have to say that Balenaâs book is one of the best programming books that I have ever had the privilege of owning. If you do VB.NET then you need this book. Balena suggested adding a separate variable to call the delegate that was setup in the MS documentation. I will use the example that I used in my first post and fill in what was needed.


Public Class frmMainâŚâthis is the delegate used in the MS documentationDelegate Sub dLVMain(ByVal row As Integer, ByVal col As Integer, ByVal text As String)âA second variable is added so that I can pass this reference to the other class Dim setLVMainMarshaler As dLVMain = AddressOf Me.setLVMainâthis is where the real work is. If you place this in the debugger you can see that whenâcalling from a second thread, the if is true and then immediately afterwards it is fasleâbecause the invoke moves the call to the correct thread. Mostly from MS documentation Public Sub setDgMain(ByVal row As Integer, ByVal col As Integer, ByVal value As String) If Me.dgMain.InvokeRequired Then Dim d As New dDgMain(AddressOf setDgMain) Me.Invoke(d, New Object() {row, col, value}) Else SyncLock listviewLock dgMain(row, col) = value 'dgMain(0, 2) = "hello" End SyncLock End If End SubâŚDim loginThread As New Thread(AddressOf modBotControler. dosomething)âpass the delegate variable to the other classloginThread.setLVMainMarshaler = setLVMainMarshalerloginThread.startâŚEnd class Public Class modBotControlerâsetup a property to get a reference to the delegatePrivate _setLVMainMarshaler As frmMain.dLVMain Public Property setLVMainMarshaler() As frmMain.dLVMain Get Return _setLVMainMarshaler End Get Set(ByVal Value As frmMain.dLVMain) _setLVMainMarshaler = Value End Set End PropertyâŚSub dosomething()âŚ_setLVMainMarshaler(_ClassID, 3, "Login Sucess")âŚEnd subEnd class

I am not completely sure that this is the best solution but this seems like the logical divertive that MS would like you to use. I hope someone else can use this information as it is a weeks worth of work, lol. Ohh yeah, **** code gremlins.


Share this post


Link to post
Share on other sites
Guest
This topic is now closed to further replies.
Sign in to follow this  

×
×
  • Create New...

Important Information

Terms of Use | Privacy Policy | Guidelines | We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.