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
Related
I am trying to implement a clear all button on a form that clears the textbox contents and unchecks all checkboxes. The issue is that the controls that need to be accessed are contained within Groupboxes and thus cannot be acessed via Me.Controls collection. I saw a similar post here: VB Uncheck all checked checkboxes in forms, but the answer seems to be more complex than I would expect it should be. Is there any easier way other than in that post.
I tried this code, which logically to me should work but it does not:
'Get textboes and clears them
For Each ctrGroupBoxes As Control In Me.Controls.OfType(Of GroupBox)
For Each ctrControls As Control In ctrGroupBoxes.Controls.OfType(Of TextBox)
ctrControls.Text = ""
Next
Next
'Get checkboxes and unchecks them
For Each ctrGroupBoxes As Control In Me.Controls.OfType(Of GroupBox)
For Each ctrControls As Control In ctrGroupBoxes.Controls.OfType(Of CheckBox)
DirectCast(ctrControls, CheckBox).Checked = False
Next
Next
I know the inner for loops work as I used it to clear each GroupBox individually for a different button on the form.
Any assistance would be appreciated.
Here's one option that should work regardless of the UI hierarchy:
Dim cntrl = GetNextControl(Me, True)
Do Until cntrl Is Nothing
Dim tb = TryCast(cntrl, TextBox)
If tb IsNot Nothing Then
tb.Clear()
Else
Dim cb = TryCast(cntrl, CheckBox)
If cb IsNot Nothing Then
cb.Checked = False
End If
End If
cntrl = GetNextControl(cntrl, True)
Loop
That will follow the Tab order on the form
A recursive function really isn't that difficult:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
ClearForm(Me)
End Sub
Private Sub ClearForm(ByVal C As Control)
For Each ctl As Control In C.Controls
If TypeOf ctl Is TextBox Then
ctl.Text = ""
ElseIf TypeOf ctl Is CheckBox Then
DirectCast(ctl, CheckBox).Checked = False
ElseIf ctl.HasChildren Then
ClearForm(ctl)
End If
Next
End Sub
End Class
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
On a VB.NET project with Metroframework Modern UI 1.4.0.0 installed, I use this part of code into a Module to loop over all MetroTextBoxes which placed into a MetroTabControl and give Cursor.Hand to their embedded Clear button. How can I do the same thing including MetroTextBoxes which could be out of MetroTabControl, into my form or into an other container for example? In other words, I would like to loop over every MetroTextBox into a form, even if it is nested.
Public Sub TxtBoxes_Cursors(sender, e)
Dim _FormSender = DirectCast(sender, MetroFramework.Forms.MetroForm)
For Each _MetroTabControl As Control In _FormSender.Controls.OfType(Of MetroFramework.Controls.MetroTabControl)()
For Each _TabPage As Control In _MetroTabControl.Controls.OfType(Of MetroFramework.Controls.MetroTabPage)()
For Each _Textbox As Control In _TabPage.Controls.OfType(Of MetroFramework.Controls.MetroTextBox)()
If _Textbox.HasChildren Then
For Each _ClearButton As Control In _Textbox.Controls
If _ClearButton.Name = "lnkClear" Then
_ClearButton.Cursor = System.Windows.Forms.Cursors.Hand
End If
Next
End If
Next
Next
Next
End Sub
This is the exact answer to my question according to the accepted answer of this relevant question which user A Friend pointed on comments. First I have to place this Function into my Module:
Public Function FindControlRecursive(ByVal list As List(Of Control), ByVal parent As Control, ByVal ctrlType As System.Type) As List(Of Control)
If parent Is Nothing Then Return list
If parent.GetType Is ctrlType Then
list.Add(parent)
End If
For Each child As Control In parent.Controls
FindControlRecursive(list, child, ctrlType)
Next
Return list
End Function
And then edit my Public Sub like this:
Public Sub TxtBoxes_Cursors(sender, e)
Dim _FormSender = DirectCast(sender, MetroFramework.Forms.MetroForm)
Dim _MetroTextBoxes As New List(Of Control)
For Each _Textbox As MetroFramework.Controls.MetroTextBox In FindControlRecursive(_MetroTextBoxes, _FormSender, GetType(MetroFramework.Controls.MetroTextBox))
If _Textbox.HasChildren Then
For Each _ClearButton As Control In _Textbox.Controls
If _ClearButton.Name = "lnkClear" Then
_ClearButton.Cursor = System.Windows.Forms.Cursors.Hand
End If
Next
End If
Next
End Sub
I want to list all names of my buttons that begins with "btn" but these buttons are place in different panels. I have this in my mind
dim obj() as object in frmForm.Controls.Find(True,"btn*")
but I think it might be wrong..
First, the first parameter is the name and the second a bool which indicates whether you want to search recursively or not.
Second, there is no builtin way for this. I would use your own method, one like this:
Public Function FindControlStartsWith(root As Control, name As String, recursive As Boolean, comparison As StringComparison) As Control()
If root Is Nothing Then
Throw New ArgumentNullException("root")
End If
Dim controls = New List(Of Control)
Dim stack = New Stack(Of Control)()
stack.Push(root)
While stack.Count > 0
Dim c As Control = stack.Pop()
If c.Name.StartsWith(name, comparison) Then
controls.Add(c)
End If
If recursive Then
For Each child As Control In root.Controls
stack.Push(child)
Next
End If
End While
Return controls.ToArray()
End Function
Use it in this way:
Dim obj() As Control = FindControlStartsWith(Me, "BUT", True, StringComparison.OrdinalIgnoreCase)
I do something similar with the type of control, but it can easily be modified for name. Try the code below:
Private Sub findcontrols(ByVal root As Control)
For Each cntrl As Control In root.Controls
findcontrols(cntrl)
If cntrl.name.startswith("btn") Then
msgbox(cntrl.name)
End If
End Sub
You can make this even more complex by adding parameters for stuff like controlling recursion and such. Keep in mind that if you want to do any control type-specific stuff with it (ie. anything that is in the control that is not inherited from the Control class), you need to CType that object as the appropriate control. So, if .name was only in the Button class, and did not exist in the Control class, I would have to do the following for this to work:
msgbox(ctype(cntrl, Button).name)
My own personal version of it looks more like this:
Private Sub clearcontrols(ByVal root As Control, ByVal ClearLists As Boolean, Optional ByVal ClearTabPages As Boolean = False)
For Each cntrl As Control In root.Controls
clearcontrols(cntrl, ClearLists, ClearTabPages)
If TypeOf cntrl Is TextBox Then
CType(cntrl, TextBox).Clear()
End If
If TypeOf cntrl Is DataGridView Then
CType(cntrl, DataGridView).Rows.Clear()
End If
If TypeOf cntrl Is ListBox And ClearLists = True Then
CType(cntrl, ListBox).Items.Clear()
End If
If TypeOf cntrl Is TabControl And ClearTabPages = True Then
For Each tp As TabPage In CType(cntrl, TabControl).TabPages
If DynTPList.Contains(tp.Name) Then
tp.Dispose()
End If
Next
End If
Next
End Sub
I am having a problem with this scenario (See Title). I have 6 Sub Panels within one big Panel. I have made a TextBox class that inherits from the main Textbox. I am trying to use the KeyPressed Event Handler for handling the Enter key. When a User presses the Enter Key, it moves from one TextBox inside a sub panel into the next sub panel. So far, I have gotten the Enter Key Event handler to work for the panel where the focus is without jumping to the next panel.
Below is subroutine that I am using to control the movements.
The problem is that I am unable to jump from one sub panel to another. Any help would be appreciated!
Protected Shared Sub NextControl(ByVal tControl As Control, ByVal Direction As Boolean)
Dim pControl As Control = tControl.TopLevelControl
tControl = pControl.GetNextControl(tControl, Direction)
If Direction = False Then
Dim tParent As Control
While TypeOf tControl Is UserControl
tParent = tControl.Parent
tControl = pControl.GetNextControl(tControl, Direction)
If tControl.Parent Is tParent Then
Exit While
End If
End While
End If
If tBox_P00.ControlNesting > 0 Then
'Dim i As Integer
pControl = tControl.Parent
For i As Integer = 0 To tBox_P00.ControlNesting - 2
pControl = pControl.Parent
Next
End If
If Not tControl Is Nothing Then
Do Until (tControl.TabStop = True) AndAlso (tControl.Enabled = True) AndAlso (tControl.Visible = True) AndAlso (TypeOf tControl Is Tbx00)
tControl = pControl.GetNextControl(tControl, Direction)
'Last in the Panel
If tControl Is Nothing Then
tBox_P00.Select(0, tBox_P00.TextLength)
Beep()
Exit Sub
End If
Loop
tControl.Focus()
Else
tBox_P00.Select(0, tBox_P00.TextLength)
Beep()
End If
Exit Sub
End Sub
It sounds as though you are complicating things. As HansPassant mentioned you can use GetNextControl to do the work for you:
This code will move the focus to the next textbox on the form (based on tab index order) when enter is pressed:
Private Sub TextBox1_KeyDown(sender As Object, e As System.Windows.Forms.KeyEventArgs) Handles TextBox1.KeyDown
If e.KeyCode = Keys.Enter Then
Dim ctl As Control = CType(sender, Control)
Do
ctl = Me.GetNextControl(ctl, True)
Loop Until TypeOf ctl Is TextBox
ctl.Focus()
End If
End Sub
You could then expand this to handle all textbox KeyDown events.