accesing controls from a thread (safely)
Hi,
In short this is what I have 2 forms formA and formB. Button on formA opens formB. FormB has a progress bar and a thread. The thread runs some code that creates a report. That code must access controls on formA e.g.
Code:
if formA.checkbox1.checked then
..
end if
In VB6 this was simply but I can't seem to figure out how to use invoke to do this.
I would greatly appreciate a detailed answer as I'm under pressure to get this work done.
Jim
Re: accesing controls from a thread (safely)
You didn't state the version of .NET you use, so its hard to give you a solid answer.
You could read over this
http://msdn.microsoft.com/en-us/libr...28(VS.80).aspx
If you are using .NET 2.0 or above, then you could also look at using the BackgroundWorker, which automatically handles marshalling between threads.
Also, just a side note, but using your email address as your user name is probably just asking to get tons of spam as bots crawl these pages looking for email addresses.
Re: accesing controls from a thread (safely)
Hi,
First yep probably wasn't a good idea to use my email but its incomplete anyways.
I use .NET 2. I have tried the back ground worker but as soon as I call the my class that refers to my main form I get the thread error at formA.checkbox1.checked
Jim
Re: accesing controls from a thread (safely)
when using the BGW, where are you trying to access your control from? Which routine? You can't access controls in the DoWork routine, that routine is ONLY for the background thread. However from that routine you can call reportprogress on the BGW, which will fire an event in the main UI thread, and there you CAN access UI controls like checkboxes.
Re: accesing controls from a thread (safely)
Hi,
This is my do work
Code:
Private Sub bgwProcessingReport_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgwProcessingReport.DoWork
' Run report function
ProcessReport()
End Sub
In side the Process Report I have
Code:
With Me
Select Case True
Case .rbReportJourneySummary.Checked
' I get the error here.
frmActiveReportViewer.FillTreeView(mySQLDataSet)
JourneySummarySetup(mySQLDataSet)
Also in journey summary setup, I have
Code:
With frmDiscoveryII ' I get the error here as well
Select Case True
Case .rbDateToday.Checked
With CType(ShowReport, rptJourneySummary)
.txtStartRange.Text = Format(Now(), "dddd, dd MMMM yyyy").ToString
.txtEndRange.Text = Format(Now(), "dddd, dd MMMM yyyy").ToString
End With
Case .rbDateYesterday.Checked
With CType(ShowReport, rptJourneySummary)
.txtStartRange.Text = Format(DateAdd(DateInterval.Day, -1, Now()), "dddd, dd MMMM yyyy").ToString
.txtEndRange.Text = Format(DateAdd(DateInterval.Day, -1, Now()), "dddd, dd MMMM yyyy").ToString
End With
This is the error.
An error occurred creating the form. See Exception.InnerException for details. The error is: ActiveX control '35f36212-43a6-48fa-8a6a-af558abe7030' cannot be instantiated because the current thread is not in a single-threaded apartment.
Thanks,
Jim
Re: accesing controls from a thread (safely)
Try creating a delegate and call 'Invoke'...
Code:
'This goes outside at the top of your form class code
Public Delegate Sub InvokeFillTreeViewDelgate(ByVal o As Object)
...
With Me
Select Case True
Case .rbReportJourneySummary.Checked
''replace frmActiveReportViewer.FillTreeView(mySQLDataSet) with
Invoke(New InvokeFillTreeViewDelgate(AddressOf FillTreeView), mySQLDataSet)
Re: accesing controls from a thread (safely)
Hi Michael,
That didn't work for me I still got the following error.
An error occurred creating the form. See Exception.InnerException for details. The error is: ActiveX control '35f36212-43a6-48fa-8a6a-af558abe7030' cannot be instantiated because the current thread is not in a single-threaded apartment.
I had to make a couple of changes to your code as the compiler were reporting an error.
Code:
Public Delegate Sub InvokeFillTreeViewDelgate(ByVal objD As DataSet)
Code:
Select Case True
Case .rbReportJourneySummary.Checked
' Fill Tree View
Invoke(New InvokeFillTreeViewDelgate(AddressOf frmActiveReportViewer.FillTreeView), mySQLDataSet)
'frmActiveReportViewer.FillTreeView(mySQLDataSet)
JourneySummarySetup(mySQLDataSet)
This is the code for Fill Tree View.
Code:
Friend Sub FillTreeView(ByVal MySQLDataReader As DataSet)
Dim tvAsset As TreeNode = Nothing, tvDate As TreeNode = Nothing
Dim intX As Integer
Try
If tcReport.TabPages.Count = 1 Then Exit Try
With MySQLDataReader.Tables(0)
tvReports.Nodes.Clear()
For intX = 0 To .Rows.Count - 1
' If Journey is Private and either Private or Limited Private is ticked then do not show
If MySQLDataReader.Tables(0).Rows(intX).Item("Type") = "P" And (LoginUsername.JourneyType(1) = "0" Or LoginUsername.JourneyType(2) = "1") Then
' do not show
Else
If tvReports.Nodes.Count = 0 Then
tvAsset = tvReports.Nodes.Add(.Rows(intX).Item("Primary Asset"))
tvDate = tvAsset.Nodes.Add(Format(GMTtoUserLocalTime(.Rows(intX).Item("Start")), "dddd, dd MMM, HH:mm") & " - " & Format(GMTtoUserLocalTime(.Rows(intX).Item("End")), "dddd, dd MMM, HH:mm") & " (" & ConvertSeconds(DateDiff(DateInterval.Second, GMTtoUserLocalTime(.Rows(intX).Item("Start")), GMTtoUserLocalTime(.Rows(intX).Item("End")))) & ")")
tvDate.Tag = .Rows(intX).Item("JourneyID")
Else
If tvAsset.Text.ToString = MySQLDataReader.Tables(0).Rows(intX).Item("Primary Asset").ToString Then
tvDate = tvAsset.Nodes.Add(Format(GMTtoUserLocalTime(.Rows(intX).Item("Start")), "dddd, dd MMM, HH:mm") & " - " & Format(GMTtoUserLocalTime(.Rows(intX).Item("End")), "dddd, dd MMM, HH:mm") & " (" & ConvertSeconds(DateDiff(DateInterval.Second, GMTtoUserLocalTime(.Rows(intX).Item("Start")), GMTtoUserLocalTime(.Rows(intX).Item("End")))) & ")")
tvDate.Tag = .Rows(intX).Item("JourneyID")
Else
tvAsset = tvReports.Nodes.Add(.Rows(intX).Item("Primary Asset").ToString)
tvDate = tvAsset.Nodes.Add(Format(GMTtoUserLocalTime(.Rows(intX).Item("Start")), "dddd, dd MMM, HH:mm") & " - " & Format(GMTtoUserLocalTime(.Rows(intX).Item("End")), "dddd, dd MMM, HH:mm") & " (" & ConvertSeconds(DateDiff(DateInterval.Second, GMTtoUserLocalTime(.Rows(intX).Item("Start")), GMTtoUserLocalTime(.Rows(intX).Item("End")))) & ")")
tvDate.Tag = .Rows(intX).Item("JourneyID")
End If
End If
End If
Next
tvReports.ExpandAll()
End With
Catch ex As Exception
MsgBox("Error generating Map Replay list", MsgBoxStyle.Exclamation, "Error")
End Try
End Sub
I'm not to worried about this part, I can make the mySQLDataSet a public variable and access it once the form opens up later on.
My main problem comes from this code which is called via ProcessReport
Code:
With frmDiscoveryII
Select Case True
Case .rbDateToday.Checked
With CType(ShowReport, rptJourneySummary)
.txtStartRange.Text = Format(Now(), "dddd, dd MMMM yyyy").ToString
.txtEndRange.Text = Format(Now(), "dddd, dd MMMM yyyy").ToString
End With
I know accessing forms from other forms in VB6 was not "safe" but it was a lot easier then this!
Thanks for all the help.
Jim
Re: accesing controls from a thread (safely)
Hi Michael,
That didn't work for me I still got the following error.
An error occurred creating the form. See Exception.InnerException for details. The error is: ActiveX control '35f36212-43a6-48fa-8a6a-af558abe7030' cannot be instantiated because the current thread is not in a single-threaded apartment.
I had to make a couple of changes to your code as the compiler were reporting an error.
Code:
Public Delegate Sub InvokeFillTreeViewDelgate(ByVal objD As DataSet)
Code:
Select Case True
Case .rbReportJourneySummary.Checked
' Fill Tree View
Invoke(New InvokeFillTreeViewDelgate(AddressOf frmActiveReportViewer.FillTreeView), mySQLDataSet)
'frmActiveReportViewer.FillTreeView(mySQLDataSet)
JourneySummarySetup(mySQLDataSet)
This is the code for Fill Tree View.
Code:
Friend Sub FillTreeView(ByVal MySQLDataReader As DataSet)
Dim tvAsset As TreeNode = Nothing, tvDate As TreeNode = Nothing
Dim intX As Integer
Try
If tcReport.TabPages.Count = 1 Then Exit Try
With MySQLDataReader.Tables(0)
tvReports.Nodes.Clear()
For intX = 0 To .Rows.Count - 1
' If Journey is Private and either Private or Limited Private is ticked then do not show
If MySQLDataReader.Tables(0).Rows(intX).Item("Type") = "P" And (LoginUsername.JourneyType(1) = "0" Or LoginUsername.JourneyType(2) = "1") Then
' do not show
Else
If tvReports.Nodes.Count = 0 Then
tvAsset = tvReports.Nodes.Add(.Rows(intX).Item("Primary Asset"))
tvDate = tvAsset.Nodes.Add(Format(GMTtoUserLocalTime(.Rows(intX).Item("Start")), "dddd, dd MMM, HH:mm") & " - " & Format(GMTtoUserLocalTime(.Rows(intX).Item("End")), "dddd, dd MMM, HH:mm") & " (" & ConvertSeconds(DateDiff(DateInterval.Second, GMTtoUserLocalTime(.Rows(intX).Item("Start")), GMTtoUserLocalTime(.Rows(intX).Item("End")))) & ")")
tvDate.Tag = .Rows(intX).Item("JourneyID")
Else
If tvAsset.Text.ToString = MySQLDataReader.Tables(0).Rows(intX).Item("Primary Asset").ToString Then
tvDate = tvAsset.Nodes.Add(Format(GMTtoUserLocalTime(.Rows(intX).Item("Start")), "dddd, dd MMM, HH:mm") & " - " & Format(GMTtoUserLocalTime(.Rows(intX).Item("End")), "dddd, dd MMM, HH:mm") & " (" & ConvertSeconds(DateDiff(DateInterval.Second, GMTtoUserLocalTime(.Rows(intX).Item("Start")), GMTtoUserLocalTime(.Rows(intX).Item("End")))) & ")")
tvDate.Tag = .Rows(intX).Item("JourneyID")
Else
tvAsset = tvReports.Nodes.Add(.Rows(intX).Item("Primary Asset").ToString)
tvDate = tvAsset.Nodes.Add(Format(GMTtoUserLocalTime(.Rows(intX).Item("Start")), "dddd, dd MMM, HH:mm") & " - " & Format(GMTtoUserLocalTime(.Rows(intX).Item("End")), "dddd, dd MMM, HH:mm") & " (" & ConvertSeconds(DateDiff(DateInterval.Second, GMTtoUserLocalTime(.Rows(intX).Item("Start")), GMTtoUserLocalTime(.Rows(intX).Item("End")))) & ")")
tvDate.Tag = .Rows(intX).Item("JourneyID")
End If
End If
End If
Next
tvReports.ExpandAll()
End With
Catch ex As Exception
MsgBox("Error generating Map Replay list", MsgBoxStyle.Exclamation, "Error")
End Try
End Sub
I'm not to worried about this part, I can make the mySQLDataSet a public variable and access it once the form opens up later on.
My main problem comes from this code which is called via ProcessReport
Code:
With frmDiscoveryII
Select Case True
Case .rbDateToday.Checked
With CType(ShowReport, rptJourneySummary)
.txtStartRange.Text = Format(Now(), "dddd, dd MMMM yyyy").ToString
.txtEndRange.Text = Format(Now(), "dddd, dd MMMM yyyy").ToString
End With
I know accessing forms from other forms in VB6 was not "safe" but it was a lot easier then this!
Thanks for all the help.
Jim
Re: accesing controls from a thread (safely)
Have you tried calling the backgroundworders ReportProgress method, which raises the ProgressChanged event, and execute the code that accesses the controls in its eventhandler? Thats how it should be done using BackgroundWorkers, which kleinma pointed out.
Re: accesing controls from a thread (safely)
Hi,
I have tried doing this.
Code:
Private Sub bgwProcessingReport_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgwProcessingReport.DoWork
bgwProcessingReport.ReportProgress(1)
End Sub
Private Sub bgwProcessingReport_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles bgwProcessingReport.ProgressChanged
' Run report function
ProcessReport()
End Sub
The reason I want to run Process Report in a back ground thread is that it can cause the form to go "WHITE". If I do Process Report in Progress Changed event then it results in the same issue as it seems that event is ran in the main thread of the form which is basically the same as running it without any threads.
I should add that I've tried
Code:
Dim f As Form1 = My.Application.OpenForms("Form1")
But my tree view selected node throws up the same error - single threading etc.
Thanks,
Jim