How to return specific properties of a custom vb.net object - vb.net

I'm trying to generate a list of all of the TableName and FieldName properties for a custom object type called LxTextBox. I've gotten as far as generating a list of all of the LxTextBox names on my form, but I can't figure out a way to call the properties of the custom object... I've been looking into System.Reflection, but I haven't ever used it. Additionally, I'm returning the list to a RichTextBox while I'm testing this out, but ultimately, I need to return each objects properties as a data row. Example:
ObjectName Table Field
---------------------------------------
LxTextBox23 SomeTbl SomeFld
Here's my code to return the list - updated based on #OneFineDay...
Imports System.Collections.Generic
Imports Application.UDF.Controls
Public Class MeasurementsControl
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim textBoxList As New List(Of Control)
Dim customTbs = GetAllControls(Me)
Dim sb As New System.Text.StringBuilder
For index As Integer = 0 To customTbs.Count - 1
sb.Append(customTbs.Item(index).TableName & "." & customTbs.Item(Index).FieldName & System.Environment.NewLine)
Next
RichTextBox1.Text = sb.ToString
End Sub
Private Function GetAllControls(ByVal searchWithin As Control) As List(Of LxTextbox)
Dim returnList As List(Of LxTextbox) = Nothing
If searchWithin.HasChildren Then
returnList = searchWithin.Controls.OfType(Of LxTextbox).ToList
For Each ctrl As Control In searchWithin.Controls
returnList.AddRange(GetAllControls(ctrl))
Next
End If
Return returnList
End Function
End Class
I made the changes suggested and I'm throwing an error: OfType is not a member of System.Windows.Forms.Control.ControlCollection
FYI - Adding Imports System.Linq did not fix the error.

You are boxing it into a Control the object from where it derives, where you're custom properties cannot be found. You can cast it right from the control collection.
Dim customTbs = GetAllControls(Me)
'recursive function
Private Function GetAllControls(ByVal searchWithin As Control) As List(Of LxTextbox)
Dim returnList As List(Of LxTextbox) = Nothing
returnList = searchWithin.Controls.OfType(Of LxTextbox).ToList
If searchWithin.HasChildren Then
For Each ctrl As Control In searchWithin.Controls
Dim ctrls = GetAllControls(ctrl)
If Not ctrls Is Nothing Then returnList.AddRange(ctrls)
Next
End If
Return returnList
End Function

Related

How to check elements from an array in a CheckListBox

i have a checklisbox with some value, let's say
"Apple"
"Peach"
"Lemon"
These values came from a dataset.
I have an array with Apple and Lemon: {"Apple", "Lemon"}.
How to check in the checklistbox each value read in this array?
EDIT: In my case, the checklistbox was populate using a dataset provided by a SQL query
In the following code sample, data from SQL-Server (database doesn't matter but this is what I used, what is important is the container the data is loaded into is loaded into a list.
Container to hold data
Public Class Category
Public Property Id() As Integer
Public Property Name() As String
Public Overrides Function ToString() As String
Return Name
End Function
End Class
Class to read data
Imports System.Data.SqlClient
Public Class SqlOperations
Private Shared ConnectionString As String =
"Data Source=.\SQLEXPRESS;Initial Catalog=NorthWind2020;Integrated Security=True"
Public Shared Function Categories() As List(Of Category)
Dim categoriesList = New List(Of Category)
Dim selectStatement = "SELECT CategoryID, CategoryName FROM Categories;"
Using cn As New SqlConnection With {.ConnectionString = ConnectionString}
Using cmd As New SqlCommand With {.Connection = cn}
cmd.CommandText = selectStatement
cn.Open()
Dim reader = cmd.ExecuteReader()
While reader.Read()
categoriesList.Add(New Category() With {.Id = reader.GetInt32(0), .Name = reader.GetString(1)})
End While
End Using
End Using
Return categoriesList
End Function
End Class
Extension method
Which can check or uncheck a value if found in the CheckedListBox and is case insensitive.
Public Module Extensions
<Runtime.CompilerServices.Extension>
Public Function SetCategory(sender As CheckedListBox, text As String, Optional checkedValue As Boolean = True) As Boolean
If String.IsNullOrWhiteSpace(text) Then
Return False
End If
Dim result = CType(sender.DataSource, List(Of Category)).
Select(Function(item, index) New With
{
Key .Column = item,
Key .Index = index
}).FirstOrDefault(Function(this) _
String.Equals(this.Column.Name, text, StringComparison.OrdinalIgnoreCase))
If result IsNot Nothing Then
sender.SetItemChecked(result.Index, checkedValue)
Return True
Else
Return False
End If
End Function
End Module
Form code
Public Class ExampleForm
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
CheckedListBox1.DataSource = SqlOperations.Categories
End Sub
Private Sub CheckCategoryButton_Click(sender As Object, e As EventArgs) Handles CheckCategoryButton.Click
CheckedListBox1.SetCategory(CategoryToCheckTextBox.Text, StateCheckBox.Checked)
End Sub
End Class
To check all at once
Private Sub CheckAllButton_Click(sender As Object, e As EventArgs) Handles CheckAllButton.Click
CType(CheckedListBox1.DataSource, List(Of Category)).
ForEach(Sub(cat) CheckedListBox1.SetCategory(cat.Name, True))
End Sub
You haven't mentioned exactly how the CheckedListBox gets populated by the DataSet, I've made the assumption that you add Strings directly to the Items collection.
This code will simply loop through the CheckedListBox and compare the values with the array, whatever the match result, the Checkbox is either ticked or cleared.
Dim theArray() As String = {"Apple", "Lemon"}
For counter As Integer = 0 To CheckedListBox1.Items.Count - 1
Dim currentItem As String = CheckedListBox1.Items(counter).ToString
Dim match As Boolean = theArray.Contains(currentItem.ToString)
CheckedListBox1.SetItemChecked(counter, match)
Next
Use the SetItemChecked method like this:
CheckedListBox1.SetItemChecked(CheckedListBox1.Items.IndexOf("your item goes here"), True)
Note that if the item does not exist, an exception will be thrown, so be sure to check for the item before calling the SetItemChecked() method. To do that, you can check for the return value of IndexOf(). It will be -1 if the item does not exist.

Passing list to function as parameter

I have a function that will perform work on a list, but I cannot get it to accept more than one datatype. For example:
Public Sub PopulateListBox (objectList as List(of VariantType), ListboxToPopulate as Listbox)
listboxToPopulate.Items.Clear() 'clears the items in the listbox
For Each item In objectList
listboxToPopulate.Items.Add(item.ToString)
Next
End
The problem is that I have lists of different classes, like employee, building address, etc. I cannot pass a List(Of EmployeeClass) because it says it cannot be converted to List(Of VariantType). I have also tried List(Of Object) and the same result.
I will demonstrate the use by first showing you a sample class.
Public Class Coffee
Public Property ID As Integer
Public Property Name As String
Public Property Type As String
Public Sub New(iid As Integer, sname As String, stype As String)
ID = iid
Name = sname
Type = stype
End Sub
Public Overrides Function ToString() As String
Return Name
End Function
End Class
I added a parameterized constructor just to make it easy to get a fully populate Coffee. You need to add the .ToString override so the list box will know what to display.
Here is where my List(Of Coffee) comes from.
Private Function FillCoffeeList() As List(Of Coffee)
Dim CoffeeList As New List(Of Coffee)
Using cn As New SqlConnection(My.Settings.CoffeeConnection),
cmd As New SqlCommand("Select Top 10 ID, Name, Type From Coffees;", cn)
cn.Open()
Using reader = cmd.ExecuteReader
Do While reader.Read
Dim c As New Coffee(reader.GetInt32(0), reader.GetString(1), reader.GetString(2))
CoffeeList.Add(c)
Loop
End Using
End Using
Return CoffeeList
End Function
As commented by Hans Passant, change the datatype of objectList to IEnumerable(Of Object).
Public Sub PopulateListBox(objectList As IEnumerable(Of Object), ListboxToPopulate As ListBox)
ListboxToPopulate.Items.Clear() 'clears the items in the listbox
For Each item In objectList
ListboxToPopulate.Items.Add(item)
Next
End Sub
Now I can pass a List(Of Coffee) to the PopulateListBox method.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim CList = FillCoffeeList()
PopulateListBox(CList, ListBox1)
End Sub
I can access the properties of the underlying type be casting.
Private Sub ListBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ListBox1.SelectedIndexChanged
Dim t = ListBox1.SelectedItem.GetType
Select Case t.Name
Case "Coffee"
Dim c = DirectCast(ListBox1.SelectedItem, Coffee)
TextBox1.Text = c.ID.ToString
TextBox2.Text = c.Type
End Select
End Sub
You can add additionsl cases depending on what types you are expecting. There is probably a better way to do this.

String form name to form reference

Basically, I am rewriting some code working for years. Over the time I have many (60+) references to forms - there's a menuitem with OnClick event for each form, where a form reference was created:
Private Sub SomeForm_Click(sender As Object, e As EventArgs) Handles MenuItemForSomeForm.Click
NewTab("Some Form", New SomeForm, 0)
End Sub
...where first parameter is a name to put in a tabPage.Text where the form is opened, second is a new instance of the (particular) form SomeForm and 0 is a default record to display (0 means no default record).
Now, I created a dynamic menu and stored the form names in a database (due to better access control over the access rights, etc). Now, because the menu is generated at runtime, I can't have the OnClick event with separate instance definition of the form and have to create it at runtime, after the MenuItems are created. The side-effect idea was to cut the code short by using only 1 OnClick event or such with MenuItem.Tag paremeter as FormName. Something like:
Private Sub clickeventhandler(sender As Object, e As EventArgs)
Dim tsmi As ToolStripMenuItem = CType(sender, ToolStripMenuItem)
Dim newForm As New >>>FormFrom(tsmi.Tag.ToString)<<< ' only explanation, this won't work
MainW.OpenModuleInTab(new newForm, tsmi.Tag.ToString, 0)
However I am failing to find a way to create form (instances) from this string reference. Reference through collection (i.e. List(of) or Dictionary) would be fine too, I believe.
The structure is obviously:
Object → Form → Form1 (class) → MyForm1 (instance)
I know I can create an object like this:
' Note that you are getting a NEW instance of MyClassA
Dim MyInstance As Object = Activator.CreateInstance(Type.GetType(NameOfMyClass))
I can re-type it to a Form type:
Dim NewForm as Form = CType(MyInstance,Form)
... to acccess some of the form properties like Width, TopLevel, etc., but that's about it. I can't do:
Dim NewForm1 as Form1 = CType(NewForm,Form1)
...because obviously, Form1 comes as a string "Form1".
I don't know how to create a Form1 reference from a "Form1" text (then it would be easy to create an instance) or how to create an instance directly (MyForm1).
SOLUTION
As sugested, I used reflection to get the form. The only way working for me I found was this:
Dim T As Type = System.Type.GetType(FormName, False)
If T Is Nothing Then 'if not found prepend default namespace
Dim Fullname As String = Application.ProductName & "." & FormName
T = System.Type.GetType(Fullname, True, True)
End If
Dim f2 As New Form ' here I am creating a form and working with it
f2 = CType(Activator.CreateInstance(T), Form)
f2.TopLevel = False
f2.Name = FormName.Replace(" ", "") & Now.ToString("yyyyMMddmmhh")
f2.FormBorderStyle = FormBorderStyle.None
f2.Dock = DockStyle.Fill
I am using VB.net CallByName to set public variable and same function to run a sub method (every form contains RecordID variable and LoadRecords sub):
CallByName(f2, "RecordID", CallType.Set, 111)
CallByName(f2, "LoadRecords", CallType.Method, Nothing)
For testing purposes, I put following into the testing form:
Public RecordID As Int32
Public Sub LoadRecords()
MsgBox("Load records!!!!" & vbCrLf & "RecordID = " & RecordID)
End Sub
Activator.CreateInstance(TypeFromName("Form1"))
TypeFromName Function:
Dim list As Lazy(Of Type()) = New Lazy(Of Type())(Function() Assembly.GetExecutingAssembly().GetTypes())
Function TypeFromName(name As String) As Type
Return list.Value.Where(Function(t) t.Name = name).FirstOrDefault()
End Function
So, let's go with the idea that I have an assembly called "WindowsApp2" and in that assembly I've defined Form1 and Form2. I've also created this module in the same assembly:
Public Module Module1
Public Function GetDoStuffWiths() As Dictionary(Of Type, System.Delegate)
Dim DoStuffWiths As New Dictionary(Of Type, System.Delegate)()
DoStuffWiths.Add(GetType(WindowsApp2.Form1), CType(Sub(f) WindowsApp2.Module1.DoStuffWithForm1(f), Action(Of WindowsApp2.Form1)))
DoStuffWiths.Add(GetType(WindowsApp2.Form2), CType(Sub(f) WindowsApp2.Module1.DoStuffWithForm2(f), Action(Of WindowsApp2.Form2)))
Return DoStuffWiths
End Function
Public Sub DoStuffWithForm1(form1 As Form1)
form1.Text = "This is Form 1"
End Sub
Public Sub DoStuffWithForm2(form2 As Form2)
form2.Text = "This is Form 2"
End Sub
End Module
Now, in another assembly "ConsoleApp1" I write this:
Sub Main()
Dim DoStuffWiths As Dictionary(Of Type, System.Delegate) = WindowsApp2.Module1.GetDoStuffWiths()
Dim formAssembly = System.Reflection.Assembly.Load("WindowsApp2")
Dim typeOfForm = formAssembly.GetType("WindowsApp2.Form1")
Dim form As Form = CType(Activator.CreateInstance(typeOfForm), Form)
DoStuffWiths(typeOfForm).DynamicInvoke(form)
Application.Run(form)
End Sub
When I run my console app I get a form popping up with the message "This is Form 1".
If I change the line formAssembly.GetType("WindowsApp2.Form1") to formAssembly.GetType("WindowsApp2.Form2") then I get the message "Wow this is cool".
That's how you can work with strongly typed objects that you dynamically instantiate.
Dim AssemblyProduct As String = System.Reflection.Assembly.GetExecutingAssembly().GetName.Name
Dim FormName As String = "Form1"
Dim NewForm As Object = Reflection.Assembly.GetExecutingAssembly.CreateInstance(AssemblyProduct & "." & FormName)
If TypeOf (NewForm) Is Form1 Then
Dim NewForm1 As Form1 = CType(NewForm, Form1)
NewForm1.BackColor = Color.AliceBlue
NewForm1.Show()
End If

Create and loop through collection subset of controls

Im making a small vb.net windows form application in which I have 4 ComboBoxes. I would like to add the ComboBoxes to a collection and be able to loop through that collection to refer to each one.
There are other ComboBoxes on the form so I cannot just use the collection for the entire form (the form layout cannot be changed, e.g. to add a container, etc).
I was thinking something like the following:
Public Class Form1
Dim IoTypeCombos As New ControlCollection(Me) From {Me.IO1_ComboBox, Me.IO2_ComboBox, Me.IO3_ComboBox, Me.IO4_ComboBox}
Dim IoTypes As New Collection() From {"Out 0", "Out 1", "Input", "Analog"}
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
For Each cb As combobox In Me.IoTypeCombos
FillComboBox(cb, Types)
Next
End Sub
Function FillComboBox(cb As Control, cc As Collection) As Boolean
Dim cbc As ComboBox = CType(cb, ComboBox)
If cc.Count = 0 Then
Return False
End If
For Each cn In cc
cbc.Items.Add(cn)
Next
Return True
End Function
This doesn't raise any exception, BUT it doesn't populate the ComboBoxes either :(
The FillComboBox() works perfectly if I pass a single control to it.
What am I doing wrong? Thanks
This line is illegal:
Public Class Form1
Dim IoTypeCombos As New ControlCollection(Me) From {Me.IO1_ComboBox,
Me.IO2_ComboBox, Me.IO3_ComboBox, Me.IO4_ComboBox }
That code will run before the constructor, before Me or ION_ComboBox exist. In this case, the resulting collection contains nothing since there is nothing to put in it yet.
In other cases, referencing controls before they exist can result in a NullReference being thrown, but due to an odd bug it may not be reported. When that happens, the rest of the code is skipped and the form simply shown.
In either case, the solution is to declare your collection at the form level, but populate it in the form load event once the controls do exist. I would also use a Collection(Of T) instead (an array or List(Of T) will also work, the OP uses/asks about a collection though):
Imports System.Collections.ObjectModel
Public Class Form1
Dim IoTypeCombos As Collection(Of ComboBox) ' form and controls Do No Exist yet
Public Sub New
'...
InitializeComponent()
' NOW they exist
End Sub
Sub Form_Load
IoTypeCombos = New Collection(Of ComboBox)
IoTypeCombos.Add(IO1_ComboBox)
IoTypeCombos.Add(IO2_ComboBox)
...
If you use a List(Of ComboBox), you can populate it different ways:
' in the ctor:
IoTypeCombos = New List(Of ComboBox)({IO1_ComboBox, IO2_ComboBox...})
' using AddRange:
IoTypeCombos.AddRange({IO1_ComboBox, IO2_ComboBox...})
Not sure if you need the where clause, but if you have other comboboxes that do not have names like this and do not want them in the collection then you do need it.
Dim IoTypeComboboxes =
Me.Controls.OfType(Of Combobox)().Where(Function(cb) cb.Name.StartsWith("IO")).ToList()
'on yourFormName
'added :
'45 PictureBox:("PicBarNum1_NotLastOdig" to "PicBarNum45_NotLastOdig")
'added:
'45 PictureBox:("PicBarNum1_UkOdig" to "PicBarNum45_UkOdig")
Public Class yourFormName
Private picbarlistNum1to45_UkOdig As New List(Of PictureBox)
Private picbarlistNum1to45_UkLastNotOdig As New List(Of PictureBox)
Private sub yourFormName_Load
Call AddPicBoxesInList_OdigNoOdig()
End sub
Private Sub AddPicBoxesInList_OdigNoOdig()
picbarlistNum1to45_UkOdig.Clear()
picbarlistNum1to45_UkLastNotOdig.Clear()
picbarlistNum1to45_UkOdig = Me.Controls(0).Controls.OfType(Of PictureBox)()
.Where(Function(pb) pb.Name.StartsWith("PicBarNum") And
pb.Name.EndsWith("_UkOdig")).ToList()
picbarlistNum1to45_UkLastNotOdig = Me.Controls(0).Controls.OfType(Of
PictureBox)().Where(Function(pb) pb.Name.StartsWith("PicBarNum") And
pb.Name.EndsWith("_NotLastOdig")).ToList()
End Sub
End Class

Using string as object name

I'm trying to use string as object name. Example I have an object and has a name = Label1. Can I do this?
Dim i As String = "Label1"
someVariable = i.Text
I'm using string as object name, is it possible?
You could iterate over all of the controls as #Christian Sauer said but you might run into problems if any controls are containers of controls. You'd need to do a recursive search to solve that. However, the ControlCollection actually has a Find() method that you can use. It returns an array of controls that match the name and optionally performs a recursive search.
''//Our final control
Dim someVariable As Control = Nothing
''//Search recursively for our control
Dim SearchedControls = Me.Controls.Find(key:="Label1", searchAllChildren:=True)
''//If we found one and only one control
If SearchedControls.Count = 1 Then
''//Set it to our variable
someVariable = SearchedControls(0)
Else
''//Whatever your logic dictates here
End If
This is not possible - but what you can do:
Dim i As String = "Label1"
Dim Obj as Label
for each elem in me.controls
if elem.Name = i then
Obj = elem
exit for
end if
next
someVariable = obj.Text
I am iterating over all WinForms control to find the label with the Name "Label1" - when found, i assign the label to a Variable.
This works, but can be quite dangerous, especially if you add controls
I know it's been answered, but this is from my library, and I use it all the time. It will iterate over all controls, and containers' controls recursively as #ChrisHaas suggested.
Public Function GetControlByName(ByRef parent As Control, ByVal name As String) As Control
For Each c As Control In parent.ChildControls
If c.Name = name Then
Return c
End If
Next
Return Nothing
End Function
<Extension()> _
Public Function ChildControls(ByVal parent As Control) As ArrayList
Return ChildControls(Of Control)(parent)
End Function
<Extension()> _
Public Function ChildControls(Of T)(ByVal parent As Control) As ArrayList
Dim result As New ArrayList()
For Each ctrl As Control In parent.Controls
If TypeOf ctrl Is T Then result.Add(ctrl)
result.AddRange(ChildControls(Of T)(ctrl))
Next
Return result
End Function
(It's been asked and answered before)
Loop Through Controls on Web User Control
I'm sure it's answered but some points to be clear it's control array and result so as to be sure it's corrected.
Private Sub btn1_click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn1.Click
Dim Ds As New DataSet
Dim str As String = ""
str = " SELECT TOP (10) t_Suppliers.STNo from t_Suppliers "
Ds = SqlHelper.ExecuteDataset(ConnectionString, CommandType.Text, str)
For i As Integer = 0 To Ds.Tables(0).Rows.Count - 1
Dim str1 As String = "lblInv" & i + 1
Dim OBj As New Label
Try
Dim SearchedControls() As Control = Me.Controls.Find(key:=str1, searchAllChildren:=True)
If SearchedControls.Length > 0 Then
SearchedControls(0).Text = Ds.Tables(0).Rows(i).Item("STNo").ToString
End If
Catch
End Try
Next
End Sub
I found the following solution on another site.
It works.
--Quote -
Dim TextBox As TextBox
Dim I As Integer = 2
Dim name As String = "TextBox" & I.ToString
TextBox = Me.Controls.Item(name)
TextBox.Text = "Something special"