Auto initialize controls - vb.net

I've created a custom control that need to be initialized. Actually I have that function to initialize my custom control (called "UserControl_Grille") :
Private Sub Init_Grille()
Me.grilleA.init_traduction(lignesTraduction)
Me.grilleB.init_traduction(lignesTraduction)
Me.grilleC.init_traduction(lignesTraduction)
Me.grilleD.init_traduction(lignesTraduction)
Me.grilleE.init_traduction(lignesTraduction)
Me.grilleF.init_traduction(lignesTraduction)
Me.grilleG.init_traduction(lignesTraduction)
Me.grilleH.init_traduction(lignesTraduction)
End Sub
As you can see it's not very worth it as If a add a new control I have to add it in this function.
So I tried to initialize automatically but it seems that it don't detect any custom control in my form ... :
Private Sub Init_Grille()
For Each grille As UserControl_Grille In Me.Controls.OfType(Of UserControl_Grille)()
grille.init_traduction(lignesTraduction)
Next
End Sub
In debug mode, it direct pass throught the For Each loop. There is any other solution?

You can recursively scroll through all controls.
For example, this sample code will return a list of all Labels in your form:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' This list will hold all the labels that we find
Dim results As List(Of Control) = New List(Of Control)
' Start searching for labels at the Form level
FindControls(Me, results)
' See how many labels we have found
MessageBox.Show(results.Count)
End Sub
Private Sub FindControls(parent As Control, ByRef results As List(Of Control))
For Each control As Control In parent.Controls
If TypeOf control Is Label Then
' We found a label so we add it to the results
results.Add(control)
End If
If Not control.Controls Is Nothing Then
' We loop through all sub-controls
FindControls(control, results)
End If
Next
End Sub
End Class
Hope this helps :)

Related

Is there any way to create a global function to clear TextBoxes?

I was wondering if there is any way to create a class with a global function/method/sub that upon
calling it will clear some of the textboxes of the form. How can i handle the different number of textboxes
each forms has?
The current code clears only the pre-defined 2 boxes. Thank you.
Public Class ClearElements
Public Sub CLEAR_TEXT(ByVal text1 As TextBox, ByVal text2 As TextBox)
text1.Clear()
text2.Clear()
End Sub
End Class
There are many ways to do it.
You can add the TextBoxes to a List, and clear each item in the list
Private ReadOnly someOfTheTextBoxes As New List(Of TextBox)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
someOfTheTextBoxes.Add(TextBox1)
someOfTheTextBoxes.Add(TextBox2)
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
For Each t In someOfTheTextBoxes
t.Clear()
Next
End Sub
Or make this method
Public Sub CLEAR_TEXT(textboxes As IEnumerable(Of TextBox))
For Each t In textboxes
t.Clear()
Next
End Sub
and call it with your list of TextBoxes
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
CLEAR_TEXT(someOfTheTextBoxes)
End Sub
or make an array on the spot and pass it in
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
CLEAR_TEXT({TextBox1, TextBox2})
End Sub
If you are interested in recursion at all, here are some extensions I have which could help
Module Extensions
<Runtime.CompilerServices.Extension>
Public Function ChildControls(parent As Control) As IEnumerable(Of Control)
Return ChildControls(Of Control)(parent)
End Function
<Runtime.CompilerServices.Extension>
Public Function ChildControls(Of TControl As Control)(parent As Control) As IEnumerable(Of TControl)
Dim result As New List(Of TControl)
For Each ctrl As Control In parent.Controls
If TypeOf ctrl Is TControl Then result.Add(CType(ctrl, TControl))
result.AddRange(ctrl.ChildControls(Of TControl)())
Next
Return result
End Function
<Runtime.CompilerServices.Extension>
Public Function ForEach(Of TSource)(source As IEnumerable(Of TSource), action As Action(Of TSource)) As IEnumerable(Of TSource)
For Each item As TSource In source
action(item)
Next item
Return source
End Function
<Runtime.CompilerServices.Extension>
Public Function ForEach(Of TSource)(source As IEnumerable(Of TSource), action As Action(Of TSource, Integer)) As IEnumerable(Of TSource)
For i As Integer = 0 To source.Count() - 1
action(source.ElementAt(i), i)
Next
Return source
End Function
End Module
Clear all textboxes recursively
Me.ChildControls(Of TextBox).ForEach(Sub(t) t.Clear())
Or ForEach on your list
someOfTheTextBoxes.ForEach(Sub(t) t.Clear())
We have this:
You could recursively go through all the controls in the form and in case of type = Textbox clear it.
But then the plot thickens:
I've done that before. It clears all the boxes of the form. I am talking about the case where some boxes have to be untouched and some to cleared.
The solution here is two parts. First, create the recursive method as suggested like this:
Public Sub ClearText(root As Control)
For Each ctrl As Control In Root.Controls
If TypeOf ctrl Is TextBox Then ctrl.Text = String.Empty
ClearText(ctrl)
Next ctrl
End Sub
or this:
Public Sub ClearText(root As IEnumerable(Of Control))
For Each ctrl As Control In root
If TypeOf ctrl Is TextBox Then ctrl.Text = String.Empty
ClearText(ctrl.Controls)
Next ctrl
End Sub
Second, on your form, use a container like Panel, GroupBox, FlowLayoutPanel, etc for the TextBox controls you need to clear. The key is all of the TextBox controls you need to clear — and none of the ones you want to keep — should be in same common container. Once that is done, you can pass the container to one of the above methods. If this messes with your layout, you can have a small number of containers for sets of controls on different areas of the form and call the function just a few times.
Remember, Panel controls can be styled to leave no visible artifacts on the parent form at all, and used entirely for logical groupings. The second version of the method above will also allow you to create arrays or lists of the controls (or control containers) you care about.
Another way to control this is to inherit a custom control from TextBox. You don't even need to change anything. All that matters is the control is now a different type from a regular textbox, and so the recursive method can target your new control type instead of textbox.
I'm using For in some cases.
First is to know for what do you need Textboxes or any component.
Second is to know if Textboxes (or any other component) will be inside Form (root) or inside others components like panels, groupoxes, tabPages… and if them will be inside of others.
Example1: Form – GroupBox(x) – TabControl(y) – TabPage(z) – TextBox(n)
Example2: Form --- TextBox(x)
Example3: Form – GroupBoox(x) – Panel(y) – TextBox(n)
Etcetera.
You may to create some anidated subs/functions to complete something more elaborated. There are two important things:
Path of the component (see previous examples)
Number of the component. If you follow Example3, maybe could be this:
Form1 – GroupBox2 – Panel1 – TextBox3
Important: These are names of the components, and you need must be enumerated all of them.
The easy way to do what you are asking is:
Public Sub CountTextBoxesAndClear(ByVal FormName As String, Optional ByVal myObject As Object = Nothing)
Dim ArrayTextBoxName() As String
Dim myTextBox As New TextBox
Dim nTBOX As Integer
'Path of component
If myObject = Nothing Then myObject = My.Application.OpenForms.Item(FormName)
'Bucle
For i As Integer = 0 To myObject.Controls.Count - 1
If myObject.Controls(i).GetType Is GetType(TextBox) Then
'Counting
nTBOX += 1
'Redim array
ReDim Preserve ArrayTextBoxName(nTBOX)
'Get Component
ArrayTextBoxName(nTBOX) = "TextBox" & nTBOX
'Get Path
myTextBox = myObject.Controls.Item(ArrayTextBoxName(nTBOX))
'myTextBox = myObject.Controls.Item("TextBox" & nTBOX) '<< the same of above line
Try
'Clear TextBoxes
myTextBox.Clear()
Catch ex As NullReferenceException
'A TextBox is Null, no error message
End Try
End If
Next
End Sub
FormName is the name of Form, with quotes, for example “Form1”.
myObject is the object that contains textboxes, if Textboxes are inside of a Panel named Panel1, you must write Panel1 (without quotes).
Try/Catch: Maybe you need to have Textbox1, TextBox2, Textbox4, Textbox5, AnotherTextBox1, AnotherTextBox2.
And you call your sub:
CountTextBoxesAndClear("Form1")
If TextBoxes are into a Panel named Panel1:
CountTextBoxesAndClear("Form1", Panel1)
You must to have the total of textboxes but only clear (or do any action) only for TextBoxes named TextBox[x].
Try/Catch manage the error because TextBox3 does not exist. However, the correct way is Textbox1, TextBox2, Textbox3, Textbox4, AnotherTextBox1, AnotherTextBox2 and put limits in your sub/function.
For example:
Public Sub CountTextBoxesAndClear(ByVal FormName As String, Optional ByVal myObject As Object = Nothing, Optional byval start as integer = 0, Optional byval finish as integer = 0)
[…tracatra…]
For i As Integer = start To finish
[…tratra…]
Next
End Sub
And this is how to call:
CountTextBoxesAndClear("Form1", Nothing, 1, 4)
And now, you can investigate a little bit about how create subs/functions to know correct paths of components, and get contents and properties of TextBoxes, Labels, Comboboxes, checkboxes…
Additional info:
If you are working in VisualStudio, you know that if you change a name of component, all of code is changed automatically. This is a big problem if you are using start/finish vars as numbers because, you must to change manually all start/finish values in functions when you need to add/remove or move positions, for example:
CountTextBoxesAndClear("Form1", Nothing, 8, 12)
Now you need to add a new TextBox just in the eight position and move one. Your sub looks like this:
CountTextBoxesAndClear("Form1", Nothing, 9, 13)
You can create a simply function that convert the name of the component to integer (this function is only for two digits (0 to 99):
Public Function ObjToInt(ByVal IntObject As Object) As Integer
If IntObject IsNot Nothing Then
Dim ref As Integer = Val(IntObject.Name.Substring(IntObject.Name.Length - 2))
If ref = 0 Then
ref = Val(IntObject.Name.Substring(IntObject.Name.Length - 1))
End If
Return ref
Else
Return 0
End If
End Function
And your sub may be written like this:
CountTextBoxesAndClear("Form1", Nothing, ObjToInt(TextBox9), ObjToInt(TextBox13))
Thanks for your awesome solutions.
I finally figured it out using ParamArray
Public Sub CLEAR_TEXTBOXES(ParamArray arr_textboxes() As TextBox)
For Each textbox As TextBox In arr_textboxes
textbox.Clear()
Next
End Sub
Then i call class using whatever textbox i want,
CLS_CLEAR_TEXTBOX.CLEAR_TEXTBOXES(TextBox1, TextBox2, Textbox7)
It's more shorter method.
Use ParamArray and Linq.
Public Sub CLEAR_TEXT(ParamArray text As TextBox())
text.ToList().ForEach(Sub(s) s.Clear())
End Sub

Adding an event handler when a subcontrol is instantiated

I've got a custom FlowLayoutPanel: an "AlbumFlowLayout" which inherits from a FlowLayoutPanel and is used to hold a collection of UserControls ("AlbumItems"). Typically, this would reside on a form ("FrmMain")so that the hierarchy of items is:
Form ("FrmMain")
AlbumFlowLayout ("AlbumFlowLayout1")
AlbumItems (1 or more)
[Is there a way/what is the protocol] for adding a "WasClicked" handler to a created AlbumItem whenever it's created/added to the AlbumFlowLayout?
Ideally I'd like to encapsulate the handler construction code inside the AlbumFlowLayout so that it happens automatically whenever the code in FrmMain does an AlbumFlowLayout.Controls.Add of a new AlbumItem, rather than having a second line in the FrmMain add the handler before adding the control, e.g.:
Dim myItem As New AlbumItem
AddHandler myItem.WasClicked, AddressOf AlbumFlowLayout1.AlbumItem_WasClicked
AlbumFlowLayout1.Controls.Add(myItem)
Thanks!
-Pete
Plutonix had the solution. Here's what the final code looks like to use it:
Partial Public Class AlbumFlowLayout
Inherits FlowLayoutPanel
' A FlowLayoutPanel for handling collections of AlbumItems
Public SelectedItems As New List(Of String)
' Holds IDs of currently selected items
Private Sub AlbumFlowLayout_ControlAdded(sender As Object, e As ControlEventArgs) Handles Me.ControlAdded
' Wire up each user item as it's added so that it will pass its
' Wasclicked up to here
Dim myAlbumItem As AlbumItem = e.Control
AddHandler myAlbumItem.WasClicked, AddressOf Me.AlbumItem_WasClicked
End Sub
' Other methods...
' ...
Public Sub AlbumItem_WasClicked(ByVal sender As Object, e As EventArgs)
' Deselects all previously selected items. Doing this via a List to
' Allow for expansion item where we permit multi-selection via
' Control-key or the like; currently is single-select
Dim myItem As AlbumItem = sender
For Each itm As String In SelectedItems
If itm <> myItem.Name Then
DirectCast(Me.Controls(itm), AlbumItem).IsSelected = False
End If
Next
SelectedItems.Clear()
SelectedItems.Add(myItem.Name)
End Sub
End Class

How to call a Sub without knowing which form is loaded into panel?

On every DataGridView1_SelectionChanged event I need to run a Private Sub OnSelectionChanged() of the form that is loaded into Panel1 (see the image http://tinypic.com/r/2nu2wx/8).
Every form that can be loaded into Panel1 has the same Private Sub OnSelectionChanged() that initiates all the necessary calculations. For instance, I can load a form that calculates temperatures or I can load a form that calculates voltages. If different element is selected in the main form’s DataGridView1, either temperatures or voltages should be recalculated.
The problem is - there are many forms that can be loaded into Panel1, and I’m struggling to raise an event that would fire only once and would run the necessary Sub only in the loaded form.
Currently I’m using Shared Event:
'Main form (Form1).
Shared Event event_UpdateLoadedForm(ByVal frm_name As String)
'This is how I load forms into a panel (in this case frm_SCT).
Private Sub mnu_SCT_Click(sender As Object, e As EventArgs) Handles mnu_SCT.Click
frm_SCT.TopLevel = False
frm_SCT.Dock = DockStyle.Fill
Panel1.Controls.Add(frm_SCT)
frm_SCT.Show()
Var._loadedForm = frm_SCT.Name
RaiseEvent event_UpdateLoadedForm(Var._loadedForm)
End Sub
‘Form that is loaded into panel (Form2 or Form3 or Form4...).
Private WithEvents myEvent As New Form1
Private Sub OnEvent(ByVal frm_name As String) Handles myEvent.event_UpdateLoadedForm
‘Avoid executing code for the form that is not loaded.
If frm_name <> Me.Name Then Exit Sub
End Sub
This approach is working but I’m sure it can be done way better (I'd be thankful for any suggestions). I have tried to raise an event in the main form like this:
Public Event MyEvent As EventHandler
Protected Overridable Sub OnChange(e As EventArgs)
RaiseEvent MyEvent(Me, e)
End Sub
Private Sub DataGridView1_SelectionChanged(sender As Object, e As EventArgs) _
Handles DataGridView1.SelectionChanged
OnChange(EventArgs.Empty)
End Sub
but I don't know to subscribe to it in the loaded form.
Thank you.
Taking into account Hans Passant’s comments as well as code he posted in related thread I achieved what I wanted (see the code below).
Public Interface IOnEvent
Sub OnSelectionChange()
End Interface
Public Class Form1
' ???
Private myInterface As IOnEvent = Nothing
' Create and load form.
Private Sub DisplayForm(frm_Name As String)
' Exit if the form is already displayed.
If Panel1.Controls.Count > 0 AndAlso _
Panel1.Controls(0).GetType().Name = frm_Name Then Exit Sub
' Dispose previous form.
Do While Panel1.Controls.Count > 0
Panel1.Controls(0).Dispose()
Loop
' Create form by its full name.
Dim T As Type = Type.GetType("Namespace." & frm_Name)
Dim frm As Form = CType(Activator.CreateInstance(T), Form)
' Load form into the panel.
frm.TopLevel = False
frm.Visible = True
frm.Dock = DockStyle.Fill
Panel1.Controls.Add(frm)
' ???
myInterface = DirectCast(frm, IOnEvent)
End Sub
Private Sub DataGridView1_SelectionChanged(sender As Object, e As EventArgs) _
Handles DataGridView1.SelectionChanged
' Avoid error if the panel is empty.
If myInterface Is Nothing Then Return
' Run subroutine in the loaded form.
myInterface.OnSelectionChange()
End Sub
End Class
One last thing – it would be great if someone could take a quick look at the code (it works) and confirm that it is ok, especially the lines marked with “???” (I don’t understand them yet).

Raising events from a List(Of T) in VB.NET

I've ported a large VB6 to VB.NET project and while it will compile correctly, I've had to comment out most of the event handlers as to get around there being no array collection for winform objects and so putting the various objects that were in at the collection array into a List object.
For example, in VB6 you could have an array of Buttons. In my code I've got
Dim WithEvents cmdButtons As New List(Of Button)
(and in the Load event, the List is propagated)
Obviously, you can't fire an event on a container. Is there though a way to fire the events from the contents of the container (which will have different names)?
In the Button creation code, the event name is there, but from what I understand the handler won't intercept as the Handles part of the code is not there (commented out).
I'm not exactly sure what you're after, but if you want to be able to add event handlers to some buttons in a container and also have those buttons referenced in a List, you can do something like
Public Class Form1
Dim myButtons As List(Of Button)
Private Sub AddButtonsToList(targetContainer As Control)
myButtons = New List(Of Button)
For Each c In targetContainer.Controls
If TypeOf c Is Button Then
Dim bn = DirectCast(c, Button)
AddHandler bn.Click, AddressOf SomeButton_Click
myButtons.Add(bn)
End If
Next
End Sub
Private Sub SomeButton_Click(sender As Object, e As EventArgs)
Dim bn = DirectCast(sender, Button)
MsgBox("You clicked " & bn.Name)
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' GroupBox1 has some Buttons in it
AddButtonsToList(GroupBox1)
End Sub
End Class

How to create Control Arrays in VB .NET

In VB6 there is a feature called Control Arrays, where you name controls the same name and provide them an index value. This allows you to set a value by looping through the controls and setting each value. In VB .NET I can't create a control array could someone provide me with a similar solution.
Here is a sample I wrote for something else that shows how to do something similar and shows how to do the handler as well. This makes a 10x10 grid of buttons that turn red when you click them.
Dim IsCreated(99) As Boolean
Dim Buttons As New Dictionary(Of String, Button)
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
For i As Integer = 0 To 99
Dim B As New Button
Me.Controls.Add(B)
B.Height = 30
B.Width = 40
B.Left = (i Mod 10) * 41
B.Top = (i \ 10) * 31
B.Text = Chr((i \ 10) + Asc("A")) & i Mod 10 + 1
Buttons.Add(B.Text, B)
B.Tag = i
AddHandler B.Click, AddressOf Button_Click
Next
End Sub
Private Sub Button_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim B As Button = sender
IsCreated(B.Tag) = True
B.BackColor = Color.Red
End Sub
Avoid using the proposed iteration approaches, you'll get a fairly random collection of controls unless your form is very simple. Simply declare the control array in your code and initialize it in the form constructor. Like this:
Public Class Form1
Private OrderNumbers() As TextBox
Public Sub New()
InitializeComponent()
OrderNumbers = New TextBox() {TextBox1, TextBox2}
End Sub
End Class
You can now treat OrderNumbers just like you could in VB6.
Maybe this is simpler. To create a control array, I put the control array declaration in a module. For example, if I have a Form with three TextBoxes and I want the TextBoxes to be part of a control array called 'mytext', I declare my control array in a module as follows:
Module Module1
Public mytext() As TextBox = {Form1.TextBox1, Form1.TextBox2, Form1.TextBox3}
End Module
And, I use the TextBoxes from the control array as follows:
Public Class Form1
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
mytext(0).Text = "Hello"
mytext(1).Text = "Hi"
mytext(2).Text = "There"
End Sub
End Class
You can even loop through the control array, like you could in VB6:
Public Class Form1
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
For i As Integer = 0 To 2
mytext(i).Text = i + 1
Next
End Sub
End Class
The beauty of using a module is that the TextBoxes do not even need to be in the same form.
With Winforms, you could do this:
myForm.Controls _
.OfType(Of TextBox) _
.OrderBy(Function(c) c.Name) _
.Where(Function(c) c.Name.StartsWith("somePrefix")) _
.ToArray()
On your form you would name your textboxes somePrefix1, somePrefix2, etc.
Here is an old article but it could give you more information. The top method is super easy.
Your Form, or PanelControl, or anything else that can contain child controls will have a Property called Controls.
You can loop through all of the text boxes in a control by using
'Create a List of TextBoxes, like an Array but better
Dim myTextBoxControls As New List
For Each uxControl As UserControl in MyFormName.Controls
If TypeOf(uControl) is TextBox
myTextBoxControls.Add(uControl)
End IF
Next
Now you have your iterate-able collection you can work with.
You can access a TextBoxes value with the EditValue property.
After looking at what you're trying to do a little further.
You probably want to name all of your controls with a Prefix, let's say abc for now.
For Each uxControl As UserControl in MyFormName.Controls
If TypeOf(uControl) is TextBox Then
Dim tbControl As TextBox = DirectCast(uControl, TextBox)
If tbControl.Name.StartsWith("abc") Then
tbControl.EditValue = "the Value you want to initialize"
End If
End If
Next
So this is one of the features that did not make the transition to VB.NET -- exactly :-( However, you can accomplish much of what you would have done in VB6 with two different mechanisms in .NET: Looping through the controls collection and handling control events.
Looping Through the Controls Collection
In VB.NET every form and control container has a controls collection. This is a collection that you can loop through and then do an operation on the control like set the value.
Dim myTxt As TextBox
For Each ctl As Control In Me.Controls
If TypeOf ctl Is TextBox Then
myTxt = CType(ctl, TextBox)
myTxt.Text = "something"
End If
Next
In this code sample you iterate over the controls collection testing the type of the returned object. If you find a textbox, cast it to a textbox and then do something with it.
Handling Control Events
You can also handle events over multiple controls with one event handler like you would have using the control array in VB6. To do this you will use the Handles keyword.
Private Sub TextBox1_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged, TextBox2.TextChanged, TextBox3.TextChanged
Dim myTxt As TextBox = CType(sender, TextBox)
MessageBox.Show(myTxt.Text)
End Sub
The key here is the Handles keyword on the end of the event handler. You separate out the various controls that you want to handle and the event by using a comma. Make sure that you are handling controls that have the same event declaration. If you ever wondered what sender was for on every event well here's one of the uses for it. Cast the sender argument to the type of control that you are working with and assign it to a local variable. You will then be able to access and manipulate the control that fired the event just like you would have in VB6 if you specified and index to the array.
Using these two techniques you can replicate the functionality of control arrays in VB6. Good luck.
Private Sub Button3_Click(sender As System.Object, e As System.EventArgs) Handles Button3.Click
Dim a() As Control = GetControls("textbox")
For Each c As TextBox In a
c.Text = c.Name
Next
End Sub
Private Function GetControls(typeOfControl As String) As Control()
Dim allControls As New List(Of Control)
'this loop will get all the controls on the form
'no matter what the level of container nesting
'thanks to jmcilhinney at vbforums
Dim ctl As Control = Me.GetNextControl(Me, True)
Do Until ctl Is Nothing
allControls.Add(ctl)
ctl = Me.GetNextControl(ctl, True)
Loop
'now return the controls you want
Return allControls.OrderBy(Function(c) c.Name). _
Where( _
Function(c) (c.GetType.ToString.ToLower.Contains(typeOfControl.ToLower) AndAlso _
c.Name.Contains("Box")) _
).ToArray()
End Function