Loop through custom control classes - vb.net

I would like to use a function at startup to apply several properties to a custom control class.
Public Shared Function ToggleSwitchProperties()
Form1.ToggleSwitch1.OnText = "ON"
Form1.ToggleSwitch1.OnFont = New Font(Form1.Font.FontFamily, 8, FontStyle.Bold)
Form1.ToggleSwitch1.OnForeColor = Color.White
Form1.ToggleSwitch1.OffText = "OFF"
Form1.ToggleSwitch1.OffFont = New Font(Form1.Font.FontFamily, 8, FontStyle.Bold)
Form1.ToggleSwitch1.OffForeColor = Color.White
End Function
This is what I have so far. I would like to apply these settings to 5 other toggle switches (named ToggleSwitch1 to ToggleSwitch5) but, for some reason, I can't find a solution.
Some more information about the situation:
I created a Form with a TabControl on it and the switches are located on TabPage1.
The toggle switches are custom made (JCS.ToggleSwitch from CodeProject)

You can group your controls in a single array and iterate this temporary collection to set the properties af all the controls referenced. You can call these controls by name even if they're not direct child of the Parent Form (they are child of another container, a TabPage of a TabControl, in this case).
Note that you're using a Function that doesn't return a value, which makes it a Sub instead.
A static (Shared) method doesn't seem appropriate to reference instances of controls.
See also whether this method needs to be Public. Probaly not.
Let's make it internal (Friend) and see if it's ok.
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ToggleSwitchProperties()
End Sub
Friend Sub ToggleSwitchProperties()
For Each tSwitch As ToggleSwitch In {ToggleSwitch1, ToggleSwitch2, ToggleSwitch3, ToggleSwitch4, ToggleSwitch5}
tSwitch.OnText = "ON"
tSwitch.OnFont = New Font(Form1.Font.FontFamily, 8, FontStyle.Bold)
tSwitch.OnForeColor = Color.White
tSwitch.OffText = "OFF"
'(... all other settings ...)
Next
End Function
Option 2.
You could also use a recursive method that searches and sets the properties of all the controls of a specific type, which are inside a container or any of its sub-containers (a TabPage inside a TabControl, in this case).
In this case, you don't need to specify all the names of the controls affected: the method will modifiy all the controls it finds inside the specified container.
In your case, you could call this method using the TabControl that contain your ToggleSwitch control as argument:
EDIT:
The code was missing a cast: Dim tSwitch = DirectCast(ctl, ToggleSwitch), which prevents the code from working as expected. Now added back.
ToggleSwitchProperties(TabControl1)
Friend Sub ToggleSwitchProperties(ctlParent As Control)
If (ctlParent Is Nothing) OrElse (Not ctlParent.HasChildren) Then Return
For Each ctl As Control In ctlParent.Controls.OfType(Of Control)
If TypeOf ctl Is ToggleSwitch Then
Dim tSwitch = DirectCast(ctl, ToggleSwitch)
tSwitch.OnText = "ON"
tSwitch.OnFont = New Font(Form1.Font.FontFamily, 8, FontStyle.Bold)
'(... all other settings ...)
Else
If ctl.HasChildren Then
ToggleSwitchProperties(ctl)
End If
End If
Next
End Sub

Related

How to set an event for MDI child forms without adding code to each form?

I would like to set the background color for a certain type of control on all child forms that open. I have an MdiParent form that is used to open the other forms within itself. I don't want to add code to each child form as this would be very extensive. This would be used as a theme feature for the application so I would like to have it automatically change the background colors based on logic in the main form. Is there something like a global event that could trigger for all Form.Load events?
So far I have created an event in the Parent form but it doesn't work for nested controls
Private Sub frmMain_MdiChildActivate(sender As Object, e As EventArgs) Handles Me.MdiChildActivate
Dim ParentControl As frmMain = sender
Dim ChildControl = ParentControl.ActiveControl
If ChildControl IsNot Nothing Then
For Each FormControl As Control In ChildControl.Controls
If FormControl.GetType = GetType(GroupBox) Then
RemoveHandler FormControl.Paint, AddressOf PaintBorderlessGroupbox
AddHandler FormControl.Paint, AddressOf PaintBorderlessGroupbox
End If
Next
End If
End Sub
I was able to accomplish this by using Form.MdiChildActivate and adding the event to the appropriate controls based on the Event and EventHandler.
Private Sub frmMain_MdiChildActivate(sender As Object, e As EventArgs) Handles Me.MdiChildActivate
Dim ParentForm As frmMain = sender
Dim ChildForm = ParentForm.ActiveMdiChild
Dim EventName = "Paint"
Dim EventHandlerName = "PaintBorderlessGroupBox"
If ChildForm IsNot Nothing Then
AddEventToControls(ChildForm, GetType(GroupBox), EventName, EventHandlerName)
End If
End Sub
Private Sub AddEventToControls(Control As Control, ControlType As Type, ControlEventName As String, ControlEventMethod As String)
For Each ChildControl In Control.Controls
If ChildControl.GetType = ControlType Then
If ChildControl.Controls.Count > 0 Then
AddEventToControls(ChildControl, ControlType, ControlEventName, ControlEventMethod)
End If
Dim EventMethod = Me.GetType().GetMethod(ControlEventMethod, BindingFlags.NonPublic Or BindingFlags.Instance)
Dim ControlEvent As EventInfo = ControlType.GetEvent(ControlEventName)
Dim del = [Delegate].CreateDelegate(ControlEvent.EventHandlerType, Me, EventMethod)
ControlEvent.RemoveEventHandler(ChildControl, del)
ControlEvent.AddEventHandler(ChildControl, del)
End If
Next
End Sub
The call to AddEventToControls() assigns the handler to the Control and any child controls that it would also apply to. In this case I am setting the Control.Paint event to paint a GroupBox a specific way. This may not be the cleanest method to accomplish this but I was able to create a "Global Event" for all child forms without ever touching the code on each form.
In your parent form, keep a collection of all Child Forms that have been activated. You can then traverse that collection and change the relevant control property for each one.
For Each ChildForm in MyCollection
ChildForm.TextBox.BackColor = Red
Next
Of course:
The ChildForm control has to be accessible by the parent form
The ChildForm has to still exist (i.e. not been closed in the mean
time)
You can't check for ChildForm closure because you are not adding any
code to the ChildForm to signal such an event.
You have to handle the exceptions when you try to change a form that
has been closed.
Much easier to include a method in your ChildForm design for ChangeColour(), whether that method is fired by event or direct call is your design decision. And to include a method to tell the parent form when a ChildForm dies so that it stops looking for it.

Can I use variables to control which PictureBox I am using?

Is there a way that I can use a variable to control which PictureBox I am using in Visual Basic?
I.e.:
CurrentNumber = 1
PictureBox(CurrentNumber).backcolour = backcolour
You can use the Me.Controls(String) indexer. It lets you specify the name (as a string) of the control you want to access, thus you can dynamically access a picture box by concatenating the string "PictureBox" with a number.
Dim TargetPictureBox As PictureBox = TryCast(Me.Controls("PictureBox" & CurrentNumber), PictureBox)
'Verifying that the control exists and that it was indeed a PictureBox.
If TargetPictureBox IsNot Nothing Then
TargetPictureBox.BackColor = Color.Red
End If
Alternatively, to save processing power by avoiding looping through the entire control collection every time you can call the OfType() extension on Me.Controls, storing the result in an array sorted by the controls' names. That way it'd only have to iterate the control collection once.
'Class level - outside any methods (subs or functions).
Dim PictureBoxes As PictureBox() = Nothing
'Doesn't necessarily have to be done in a button, it's just an example.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If PictureBoxes Is Nothing Then
PictureBoxes = Me.Controls.OfType(Of PictureBox).OrderBy(Function(p As PictureBox) p.Name).ToArray()
End If
'NOTE: CurrentNumber - 1 is necessary when using an array!
PictureBoxes(CurrentNumber - 1).BackColor = Color.Red
End Sub
NOTE: This solution will only work properly if all your picture boxes are named "PictureBox1", "PictureBox2", etc. If you suddenly skip a number ("PictureBox3", "PictureBox5", "PictureBox6") then PictureBoxes(CurrentNumber - 1) for CurrentNumber = 5 would return PictureBox6 rather than PictureBox5.
What you really should do is create a PictureBox() and use that to reference your picture boxes via an index.
The best way to build your array is to create a method that builds the array from the references created by the designer. This lets you continue to use the designer to create your controls and it makes your code check for deleted controls at design-time. Using Me.Controls(...) suffers from run-time errors if controls you are looking for have been deleted.
Here's the code you need:
Private _PictureBoxes As PictureBox() = Nothing
Sub AssignPictureBoxesArray
_PictureBoxes = {PictureBox1, PictureBox2, PictureBox3}
End Sub
Then you access them like this:
Sub SomeMethod
Dim CurrentNumber = 1
Dim PictureBox = _PictureBoxes(CurrentNumber - 1)
PictureBox.BackColor = System.Drawing.Color.Red
End Sub

VB Net access to form control by their variable names

From a FormName2, and I need to set a text in a TextboxName1 that is in a FormName1, by their string names.
Then, from FormName2, I should set the text like FormName1.TextboxName1.text = "test".
However, I need to achieve it with string names of the controls.
stringFormName.stringTextboxName.text ="test"
How to reach it?
You could retrieve all the opened instances of your forms in the Application.OpenForms collection.
Using that collection you can retrieve the form with the specified name
Dim aForm = Application.OpenForms.Item("FormName1")
At this point, you can scan the controls of this form with the same pattern for a control with a specific name
If aForm IsNot Nothing Then
Dim aControl = aForm.Controls.Item("TextBoxName1")
if aControl IsNot Nothing then
aControl.Text = "test"
End If
End If
The only problem with this search for a control is the possibility of the control to be contained in a control container different from the top level form. For example the control could be inside a GroupBox or a Panel. In this case you need to use the Find method from the Control collection with the second parameter set to true to search all the control hierarchy
Dim aControl = aForm.Controls.Find("TextBoxName1", True)
Use FindControl:
Private Sub Button1_Click(sender As Object, MyEventArgs As EventArgs)
Dim myControl1 As Control = FormName1.FindControl("stringTextboxName")
If (Not myControl1 Is Nothing)
' Get control's parent.
Dim tb as TextBox= CType(myControl1, TextBox)
tb.text="test"
Else
Console.WriteLine("Control not found.....")
End If
End Sub
If you have nested elements you may need to call find control recursively.

Dynamically create and remove a control from a form, many times

The below subroutine, when called using a mouse click, successfully creates and then removes a control. but it doesn't create it a second time. I'm assuming it is because the label is not longer dimensioned as public. ie Dim lblDebug1 As New Label is at the top variable section of the form.
However when I put Dim lblDebug1 As New Label in the subroutine the dispose request doesn't work. Is there someway that I can keep creating and disposing a control?
In the below sub, booleanDebug is used to switch back and forth between creating it and disposing it. Thanks in advance.
Dim lblDebug1 As New Label
booleanDebug = Not booleanDebug
If booleanDebug Then
Me.Controls.Add(lblDebug1)
lblDebug1.BackColor = Color.BlueViolet
Else
lblDebug1.Dispose()
End If
Ensure the label has a global context. Within the form that owns it and that you have all the appropriate size and coordinates information and visibility set.
Here is some sample code that worked for me. First just create a new windows form then add a button control in the middle of the form then use the following code.
Public Class Main
Private labelDemo As Windows.Forms.Label
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Me.SuspendLayout()
If labelDemo Is Nothing Then
labelDemo = New Windows.Forms.Label
labelDemo.Name = "label"
labelDemo.Text = "You Click the Button"
labelDemo.AutoSize = True
labelDemo.Left = 0
labelDemo.Top = 0
labelDemo.BackColor = Drawing.Color.Violet
Me.Controls.Add(labelDemo)
Else
Me.Controls.Remove(labelDemo)
labelDemo = Nothing
End If
Me.ResumeLayout()
End Sub
End Class
Once you've Disposed a control, you can't use it any more. You have two choices here:
Choice 1: Just Remove the control from the form rather than disposing it:
'Top of the file
Dim lblDebug1 As New Label
'Button click
booleanDebug = Not booleanDebug
If booleanDebug Then
lblDebug1.BackColor = Color.BlueViolet
Me.Controls.Add(lblDebug1)
Else
Me.Controls.Remove(lblDebug1)
End If
Choice 2: Create a new control object each time
'Top of the file
Dim lblDebug1 As Label
' ^ No "New".
'We just want an object reference we can share at this point, no need for an instance yet
'Button click
booleanDebug = Not booleanDebug
If booleanDebug Then
lblDebug1 = New Label()
lblDebug1.BackColor = Color.BlueViolet
Me.Controls.Add(lblDebug1)
Else
lblDebug1.Dispose()
End If

Retrieving data on dynamic controls

I am using dynamically created controls and need to retrieve information about the control at runtime.
If IsLoaded <> "free" Then
flow_display.Controls.Clear()
For x As Integer = 0 To populate.Count - 1
If populate(x).parentID = 2 Then
Dim NewPicBox As PictureBox = New PictureBox
NewPicBox.Size = New System.Drawing.Size(697, 50)
NewPicBox.ImageLocation = pw_imgLink & populate(x).imageID
AddHandler NewPicBox.Click, AddressOf catWindow
flow_display.Controls.Add(NewPicBox)
End If
Next
IsLoaded = "free"
End If
End Sub
Here I create the control when the user clicks on the appropriate label. Right now the catWindow sub is empty. I need to figure out which button is clicked and figure out its location on the populate list. I have tried a few things and from what I've read from other questions can't seem to find anything the helps. Thanks :)
For finding out which PictureBox is pressed, your catWindow Sub should look like this:
Public Sub catWindow(ByVal sender As Object, ByVal e As EventArgs)
Dim box As PictureBox = TryCast(sender, PictureBox)
If box Is Nothing Then Exit Sub
'Now "box" refers to the PictureBox that was pressed
'...
End Sub
If you want to find it's location in the populate list, you will need to iterate through the list until you find the matching box. You could also pre-empt a property on your PictureBox that isn't doing anything else and use it to store the index. Older forms tools used to have a .Tag property especially for this kind of thing. But really, the need to do this smells like a design flaw to me.
FWIW, I'd rewrite your original sample like this:
If IsLoaded <> "free" Then
flow_display.SuspendLayout()
flow_display.Controls.Clear()
For Each box As PictureBox In populate
.Where(Function(p) p.parentID = 2)
.Select(Function(p) New PictureBox() With {
.Size = New System.Drawing.Size(697, 50),
.ImageLocation pw_imgLink & p.imageID })
AddHandler box.Click, AddressOf catWindow
flow_display.Controls.Add(box)
Next box
flow_display.ResumeLayout()
IsLoaded = "free"
End If