Cross-thread operation not valid - vb.net

I was doing the following to try out threadings but get the 'Cross-Threading' error in the line txtBox.Text = DataGridView2.Rows.Item(iloop).Cells(3).Value, anyone can point out what's wrong, thanks:
Dim lm As New Thread(AddressOf load_movie)
Dim lt As New Thread(AddressOf load_timings)
lm.Start()
lt.Start()
Private Sub load_movie()
For iloop As Integer = 0 To DataGridView1.Rows.Count - 2
Dim Cstring As String = "txt_Movie_0" & iloop.ToString
For Each cCtrl As Control In Panel1.Controls
If TypeOf cCtrl Is TextBox Then
Dim txtBox As TextBox
txtBox = cCtrl
If txtBox.Name = Cstring Then
txtBox.Text = DataGridView1.Rows.Item(iloop).Cells(1).Value
End If
End If
Next
Next
End Sub
Private Sub load_timings()
For iloop As Integer = 0 To DataGridView2.Rows.Count - 2
For Each cCtrl As Control In Panel2.Controls
If TypeOf cCtrl Is TextBox Then
Dim txtBox As TextBox
txtBox = cCtrl
If (txtBox.Name.Substring(9, 6)) = (DataGridView2.Rows.Item(iloop).Cells(0).Value.substring(0, 6)) Then
txtBox.Text = DataGridView2.Rows.Item(iloop).Cells(3).Value 'This is the part that says "Cross-thread operation not valid: Control 'txt_Time_00_000' accessed from a thread other than the thread it was created on."
End If
End If
Next
Next
End Sub

It's not legal to access a UI element from anything other than UI thread in .Net code. Hence when you try and use the DataGridView2 instance from a background thread it rightfully throws an exception.
In order to read or write to a UI component you need to use Invoke or BeginInvoke method to get back on the UI thread and make the update. For example
If TypeOf cCtrl Is TextBox Then
Dim txtBox As TextBox
txtBox = cCtrl
txtBox.Invoke(AddressOf UpdateTextBox, txtBox, iloop)
End If
Private Sub UpdateTextBox(txtBox as TextBox, iloop as Integer)
If (txtBox.Name.Substring(9, 6)) = (DataGridView2.Rows.Item(iloop).Cells(0).Value.substring(0, 6)) Then
txtBox.Text = DataGridView2.Rows.Item(iloop).Cells(3).Value 'This is the part that says "Cross-thread operation not valid: Control 'txt_Time_00_000' accessed from a thread other than the thread it was created on."
End If
End Sub

#JaredPar you have the basic idea but that code itself won't compile (unless I'm missing something). For VB9 or less you need to declare an actual delegate and invoke that:
''//The delegate is only needed for the VB 9 or less version
Private Delegate Sub UpdateTextBoxDelegate(ByVal txtBox As TextBox, ByVal value As String)
If TypeOf cCtrl Is TextBox Then
Dim txtBox As TextBox
txtBox = cCtrl
''//Perform validation logic here
If (txtBox.Name.Substring(9, 6)) = (DataGridView2.Rows.Item(iloop).Cells(0).Value.ToString().Substring(0, 6)) Then
''//Call the update method with our textbox and value
UpdateTextBox(txtBox, DataGridView2.Rows.Item(iloop).Cells(3).Value.ToString())
End If
End If
Private Sub UpdateTextBox(ByVal txtBox As TextBox, ByVal value As String)
''//Basically ask the textbox if we need to invoke
If txtBox.InvokeRequired Then
''//For VB 9 or less you need a delegate
txtBox.Invoke(New UpdateTextBoxDelegate(AddressOf UpdateTextBox), txtBox, value)
Else
txtBox.Text = value
End If
End Sub
For VB 10 we can finally use anonymous subs so we can completely get rid of the delegate:
If TypeOf cCtrl Is TextBox Then
Dim txtBox As TextBox
txtBox = cCtrl
''//Perform validation logic here
If (txtBox.Name.Substring(9, 6)) = (DataGridView2.Rows.Item(iloop).Cells(0).Value.ToString().Substring(0, 6)) Then
''//Call the update method with our textbox and value
UpdateTextBox(txtBox, DataGridView2.Rows.Item(iloop).Cells(3).Value.ToString())
End If
End If
Private Sub UpdateTextBox(ByVal txtBox As TextBox, ByVal value As String)
If txtBox.InvokeRequired Then
''//For VB 10 you can use an anonymous sub
txtBox.Invoke(Sub() UpdateTextBox(txtBox, value))
Else
txtBox.Text = value
End If
End Sub

Related

quick way to disable all textboxfields unlock with edit button

Does Vb.net have a quick way to disable all editable fields (textboxes, checkboxes,...) in a form?
I'm trying to protect data from "accidentally" being overwritten. My user has to click an edit button to "unlock" the fields and make them editable and the user must press the save button to update the database and lock the fields once more.
So far i've only found a manual input to make every textbox and checkbox (about 30 of them in different tabs of the form) readonly values to true and disable them if i click on my edit button.
Any advice on how to do this? can i do a "for each" in a quicker way? how would i formulate the code?
i found a 2007 article with this snippet:
For Each control As Control In Me.Controls
If TypeOf (control) Is TextBox Then
CType(control, TextBox).ReadOnly = True
End If
Next
but i'd need all types of field in one go.
thanks for any advice on this subject. I'm a beginner.
With reference to this question
If you put this in a module called ControlExt.vb:
Imports System.Runtime.CompilerServices
Module ControlExt
<Extension()>
Public Function GetAllChildren(Of T As Control)(parentControl As Control) As IEnumerable(Of T)
Dim controls = parentControl.Controls.Cast(Of Control)
Return controls.SelectMany(Of Control)(Function(ctrl) _
GetAllChildren(Of T)(ctrl)) _
.Concat(controls) _
.Where(Function(ctrl) ctrl.GetType() = GetType(T)) _
.Cast(Of T)
End Function
End Module
Then you could do this in your class:
Class WhateverForm
Private _allTextboxes as List(Of TextBox)
Public Sub New() 'in the constructor
InitializeComponent()
_allTextboxes = Me.GetAllChildren(Of TextBox).ToList()
End Sub
Which means all the textboxes on your form, wherever they are (inside whatever subpanels of subpanels, of tabpages of groupboxes etc), are now in a List.. And you can do this in your edit button click handler:
_allTextboxes.ForEach(Sub(b) b.ReadOnly = False)
And of course, perform the corollary ReadOnly = True in the Save button. You don't have to limit it to textboxes; GetAllChildren can find anything - maybe you want to lock the checkboxes too - have another list of _allCheckboxes, GetAllChildren(Of CheckBox) and in the ForEach set Enabled = etc
I'd point out that ReadOnly and Enabled are slightly different and as a UX pointer if you're going to make stuff greyed out in a UI (or "half greyed out" in the case of ReadOnly), then you should have something explaining why the option is greyed out/how to enable it. It won't be immediately obvious to users that they have to click Enable and if you don't want tech support calls going "I'm clicking in the name box and I can see the cursor flashing but when i type nothing happens!" then give them a nudge with "View mode: To enable editing of this data, click [EDIT]" etc
This will disable every TextBox and every CheckBox that was added directly to a TabPage in TabControl1:
For each tp As TabPage In TabControl1.TabPages
For Each tb In tp.Controls.OfType(Of TextBox)()
tb.Enabled = False
Next
For Each cb In tp.Controls.OfType(Of CheckBox)()
cb.Enabled = False
Next
Next
Try this:
For Each control As Control In Me.Controls
Try
CType(control, TypeOf(control)).ReadOnly = True
Catch
' Do nothing
End Try
Next
Thanks for the GetAllChildren function. Thought I'd share a continuation of it so you can lock all common controls based on a tag.
Public Sub LockControls(ByVal frm_Form As Form,
ByVal bln_Lock As Boolean,
ByVal str_TAG As String)
Dim allTextboxes As List(Of TextBox)
Dim allComboBoxes As List(Of ComboBox)
Dim allDateTimePickers As List(Of DateTimePicker)
Dim allDataGrids As List(Of DataGridView)
Dim allCheckBoxes As List(Of CheckBox)
Dim allListBoxes As List(Of ListBox)
Dim allButtons As List(Of Button)
Dim blnEnable As Boolean
Dim blnReadonly As Boolean
If bln_Lock Then ' we need to lock controls
blnEnable = False
blnReadonly = True
Else
blnEnable = True
blnReadonly = False
End If
allTextboxes = frm_Form.GetAllChildren(Of TextBox).ToList
For Each CTL In allTextboxes
If ContainsTag(CTL.Tag, str_TAG) Then
CTL.ReadOnly = blnReadonly
End If
Next
allComboBoxes = frm_Form.GetAllChildren(Of ComboBox).ToList
For Each CTL In allComboBoxes
If ContainsTag(CTL.Tag, str_TAG) Then
CTL.Enabled = blnEnable
End If
Next
allCheckBoxes = frm_Form.GetAllChildren(Of CheckBox).ToList
For Each CTL In allCheckBoxes
If ContainsTag(CTL.Tag, str_TAG) Then
CTL.Enabled = blnEnable
End If
Next
allButtons = frm_Form.GetAllChildren(Of Button).ToList
For Each CTL In allButtons
If ContainsTag(CTL.Tag, str_TAG) Then
CTL.Enabled = blnEnable
End If
Next
allDateTimePickers = frm_Form.GetAllChildren(Of DateTimePicker).ToList
For Each CTL In allDateTimePickers
If ContainsTag(CTL.Tag, str_TAG) Then
CTL.Enabled = blnEnable
End If
Next
allDataGrids = frm_Form.GetAllChildren(Of DataGridView).ToList
For Each CTL In allDataGrids
If ContainsTag(CTL.Tag, str_TAG) Then
CTL.ReadOnly = blnReadonly
End If
Next
allListBoxes = frm_Form.GetAllChildren(Of ListBox).ToList
For Each CTL In allListBoxes
If ContainsTag(CTL.Tag, str_TAG) Then
CTL.Enabled = blnEnable
End If
Next
End Sub
Private Function ContainsTag(fulltag As String, searchTag As String) As Boolean
Dim found As Boolean
Dim tags As String() = Nz(fulltag, "").Split(New Char() {","c})
For Each tag In tags
If tag = searchTag Then
found = True
End If
Next
Return found
End Function
Public Function Nz(ByVal Value As Object, Optional ByVal DefaultVal As Object = "") As Object
If Value Is Nothing OrElse IsDBNull(Value) Then
Return DefaultVal
Else
Return Value
End If
End Function

Is it possible to have things defined and controlled inside a class, without being assigned in the "form" (outside the class) in VB?

My problem:
I have a checkbox I use to control if certain textboxes are enabled or not, and I need to do this around 30+ times. I've named my textboxes numerically/sequentially (TB_name_1, TB_name_2, etc) so if I know the Checkbox name I know which textboxes are affected.
My question:
Can I make a class for my checkboxes that says "if this box is checked/unchecked, then enable/disable these 3 textboxes" without the class also having to be told which textboxes (finds them itself)?
Here's the copy/paste code I'm currently using (not a class, obviously). I change the first 2 values and the rest of the code solves itself. (PS - I see you laughing)
Private Sub T1_cb_c_1_CheckedChanged(sender As Object, e As EventArgs) Handles T1_cb_c_1.CheckedChanged
'change here for current checkbox
Dim b As CheckBox = T1_cb_c_1
'change here for start value of first textbox (of 3), the next 2 will be in sequence
Dim a As Integer = 1
'How much of the below code can be moved to, and controlled from, a class?
Dim a1 As Integer = a + 1
Dim a2 As Integer = a + 2
Dim TB_PtNum As TextBox = Me.Controls.Find("T1_tb_c_" & a, True).FirstOrDefault
Dim TB_Qty As TextBox = Me.Controls.Find("T1_tb_c_" & a1, True).FirstOrDefault
Dim TB_Seq As TextBox = Me.Controls.Find("T1_tb_c_" & a2, True).FirstOrDefault
If b.Checked = True Then
TB_PtNum.Enabled = True
TB_Qty.Enabled = True
TB_Seq.Enabled = True
Else
TB_PtNum.Enabled = False
TB_Qty.Enabled = False
TB_Seq.Enabled = False
End If
End Sub
Here's a design time only class that will do this. You only have to the AssociatedCheckbox property in the designer:
Public Class TextBoxWithCheckboxProperty
Inherits TextBox
Private m_CheckBox As CheckBox
Public Property AssociatedCheckBox As CheckBox
Get
Return m_CheckBox
End Get
Set(value As CheckBox)
If Not m_CheckBox Is Nothing Then
RemoveHandler m_CheckBox.CheckedChanged, AddressOf OnCheckBoxChanged
End If
m_CheckBox = value
If Not value Is Nothing Then
AddHandler m_CheckBox.CheckedChanged, AddressOf OnCheckBoxChanged
End If
OnCheckBoxChanged(m_CheckBox, Nothing)
End Set
End Property
Private Sub OnCheckBoxChanged(ByVal sender As Object, ByVal e As System.EventArgs)
If Not sender Is Nothing Then
Me.Enabled = CType(sender, CheckBox).Checked
Else
Me.Enabled = False
End If
End Sub
End Class
Here's a sample Form1 that uses it:
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Class Form1
Inherits System.Windows.Forms.Form
'Form overrides dispose to clean up the component list.
<System.Diagnostics.DebuggerNonUserCode()> _
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
Finally
MyBase.Dispose(disposing)
End Try
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Me.CheckBox1 = New System.Windows.Forms.CheckBox()
Me.TextBoxWithCheckboxProperty1 = New WindowsApp4.TextBoxWithCheckboxProperty()
Me.SuspendLayout()
'
'CheckBox1
'
Me.CheckBox1.AutoSize = True
Me.CheckBox1.Location = New System.Drawing.Point(293, 131)
Me.CheckBox1.Name = "CheckBox1"
Me.CheckBox1.Size = New System.Drawing.Size(81, 17)
Me.CheckBox1.TabIndex = 0
Me.CheckBox1.Text = "CheckBox1"
Me.CheckBox1.UseVisualStyleBackColor = True
'
'TextBoxWithCheckboxProperty1
'
Me.TextBoxWithCheckboxProperty1.AssociatedCheckBox = Me.CheckBox1
Me.TextBoxWithCheckboxProperty1.Location = New System.Drawing.Point(428, 131)
Me.TextBoxWithCheckboxProperty1.Name = "TextBoxWithCheckboxProperty1"
Me.TextBoxWithCheckboxProperty1.Size = New System.Drawing.Size(100, 20)
Me.TextBoxWithCheckboxProperty1.TabIndex = 1
'
'Form1
'
Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New System.Drawing.Size(800, 450)
Me.Controls.Add(Me.TextBoxWithCheckboxProperty1)
Me.Controls.Add(Me.CheckBox1)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)
Me.PerformLayout()
End Sub
Friend WithEvents CheckBox1 As CheckBox
Friend WithEvents TextBoxWithCheckboxProperty1 As TextBoxWithCheckboxProperty
End Class
I would use the property Tag for the related controls.
Suppose to set this property to the value "line1" for the first set of textboxes and on the checkbox that controls them.
Next row of controls (checkbox+textboxes) will have the property set to "line2" and so on until the last row. (You can do this through the Winforms Designer or through code)
At this point you could have a single event handler for all your checkboxes
Private Sub onCheckedChanged(sender As Object, e As EventArgs) _
Handles T1_cb_c_1.CheckedChanged, T2_cb_c_2.CheckedChanged, _
..... add other checkbox events here .......
' Get whatever checkbox has been clicked and extract its tag
Dim b As CheckBox = DirectCast(sender, CheckBox)
Dim tag = b.Tag.ToString()
' Find the textbox controls in this form with the same Tag
Dim ctrls = Me.Controls.OfType(Of TextBox).Where(Function(x) x.Tag.ToString() = tag)
' Enabled status matches the status of the Checked property
For Each c as TextBox in ctrls
c.Enabled = b.Checked
Next
End Sub

How to multithread using serial connection vb.net

I have a serial connection to a remote machine and i'm developing a windows form using vb.net in order to gather some infos.
So as u can see in the code below i'm waiting until i receive the full string ( length 4, # as separator ) to change some textboxes text.
Dim ReceivedTextSeries As String = vbNullString
Private Sub ReceivedText(ByVal [text] As String)
If TextBoxConsola.InvokeRequired Then
Dim x As New SetTextCallBlack(AddressOf ReceivedText)
Me.Invoke(x, New Object() {(text)})
ReceivedTextSeries &= [text]
JustTesting()
Else
TextBoxConsolaReceived.Text &= [text]
ReceivedTextSeries &= [text]
JustTesting()
End If
End Sub
Sub JustTesting()
Dim Series() As String = ReceivedTextSeries.Split("#")
If Series.Length = 4 Then
TextBox1.Text = Series(0)
TextBox2.Text = Series(2)
End If
End Sub
But i'm getting an error saying that multithreading isn't allowed..
The operation between threads is not valid: Control 'TextBox1' accessed from a thread other than the thread where it was created.
How can i manage this now? I've tried to add event handlers to avoid this but without success..
So you could create a quick sub that invokes your textboxes. You're already doing that in your previous method. This makes it reusable.
Private Sub UpdateTextBox(Text As String, TextBox As TextBox)
If TextBox.InvokeRequired Then
TextBox.Invoke(DirectCast(Sub() UpdateTextBox(Text, TextBox), Action))
Exit Sub
End If
TextBox.Text = Text
End Sub
Then all your calls to write to a textbox can be called with
UpdateTextBox("My Text", TextBox1)
So with your code you could do
Sub JustTesting()
Dim Series() As String = ReceivedTextSeries.Split("#")
If Series.Length = 4 Then
UpdateTextBox(Series(0), TextBox1)
UpdateTextBox(Series(2), TextBox2)
End If
End Sub

Loop Combobox and variable inside

I have this code:
Dim val1,val2,val3,val4,val5, ... ,val20 As String
If Combobox1.SelectedIndex = 0 Then
val1=">"
else
val1="<"
end if
If Combobox2.SelectedIndex = 0 Then
val2=">"
else
val2="<"
end if
How can I loop this? There are 20 Combo boxes. Please help! Thanks
To loop through the controls on the form, use Me.Controls. And to check the type of the control, use Control.GetType.Name.
The problem is that separate variables like val1,val2,val3...etc cannot be easily used in a loop, so I recommend changing them to a list.
Dim val As New List(Of String)
For Each MyControl In Me.Controls
If MyControl.GetType.Name = "ComboBox" Then
Dim MyComboBox As ComboBox = CType(MyControl, ComboBox)
If MyComboBox.SelectedIndex = 0 Then
val.Add(">")
Else
val.Add("<")
end if
End If
Next
So, here are some looping sequences for you. disclaimer - code is not tested, could be bugs but logical layout should be correct
Private _numOfControls As Integer = 20
Private _variables As New Dictionary(Of String, String)(_numOfControls)
Private Sub Setup()
For i as integer = 1 To _numOfControls
Dim cboName As String = "cbo" & i
Dim cbo As New ComboBox()
cbo.Name = cboName
cbo.SelectedIndex = 0
' Add cbo location according to your logic here
container.Controls.Add(cbo) ' container - any control or form that hosts cbo
_variables.Add(cboName, ">")
AddHandler cbo.SelectedIndexChanged, AddressOf OnCboSelectedIndexChanged
Next
End Sub
' If you do this way, your control-related variable will have appropriate equality sign without need for iteration
Private Sub OnCboSelectedIndexChanged (sender As Object, e As EventArgs)
Dim cbo As ComboBox = CType(sender, ComboBox)
myDict(cbo.Name) = If(cbo.SelectedIndex = 0, ">", "<")
End Sub
' But if you do not want to do in the above^^ way, you can iterate dictionary
Private Sub FromDictionaryToCbo()
' Here you can't use for-loop on dictionary because your dictionary will be mutating. So lets iterate keys
For Each k As String In _variables.Keys.ToArray())
_variables(k) = If(CType(container.Find(k, True), ComboBox).SelectedIndex = 0, ">", "<")
Next
End Sub
' And iterating combo boxes would be something similar as another answer here
' But we use our naming convention to bypass other controls
Private Sub FromCboToDictionary()
Dim c As Control
For i As Integer = 1 To < _numOfControls
Dim key As String = "cbo" & i.ToString()
'We don't know, may be there some other combo boxes are there, so we use our naming convention instead of control type
c = container.Find(key, True)
If c IsNot Nothing Then
_variables(key) = If(CType(c, ComboBox).SelectedIndex = 0, ">", "<")
end If
Next
End Sub

ContextMenuStrip event handler at runtime

I create Context menu at runtime depends of text in selected cell of datagridview.
Like this:
With ContextMenuStrip1
.Items.Clear()
Dim Str As String = DataGridView1.Item(1, DataGridView1.CurrentRow.Index).Value
Dim strArr() As String = Str.Split(" ")
For count As Integer = 0 To strArr.Length - 1
If strArr(count).Length > 1 Then
.Items.Add(strArr(count))
End If
Next
.Items.Add("-")
.Items.Add("Common operation ...")
.Items.Add("Second common operation ...")
AddHandler .Click, AddressOf cMenu_Click
.Show(New Point(Cursor.Position.X, Cursor.Position.Y))
End With
etc...
Then I add event handler like this:
Private Sub cMenu_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim mytext As String
mytext = (CType(sender, ContextMenuStrip).Text)
Debug.Print(mytext)
'after all...
RemoveHandler ContextMenuStrip1.Click, AddressOf cMenu_Click
End Sub
And as vbnet beginner with this code I can't get text of fired menu item in event handler.
So please help to get it.
Each menu item needs the handler.
Try it this way (updated with adding a shortcut key):
For count As Integer = 0 To strArr.Length - 1
If strArr(count).Length > 1 Then
Dim newMenu As New ToolStripMenuItem(strArr(count), _
Nothing, AddressOf cMenu_Click)
newMenu.ShortcutKeys = Keys.Control Or Keys.C
.Items.Add(newMenu)
End If
Next
Your click method should be changed to handle a ToolStripMenuItem instead:
Private Sub cMenu_Click(ByVal sender As Object, ByVal e As EventArgs)
Dim mytext As String
mytext = DirectCast(sender, ToolStripMenuItem).Text
Debug.Print(mytext)
End Sub
Add a handler (pointing to the same method) for the Click Event of all of the child Items of your ContextMenuStrip. Then in your method cast it as a ToolStripMenuItem or MenuItem class (whatever you're using) to find the text of the clicked item.