Modify a structure field containing combobox when SelectedIndex event fires - vb.net

I am trying to have a generic widget composed of a label and a value. The value is set by a combobox. Here is the structure:
Structure tParam
Dim label As Label
Dim comboBox As ComboBox
Dim valueX As String
End Structure
Dim parameter1 As tParam
I'd like to modify the valueX as the SelectedIndexChanged event is fired.
For now I have set
parameter1.label.text = "Id"
parameter1.comboBox.Tag = parameter1 ' the struct itself
AddHandler parameter1.comboBox.SelectedIndexChanged, AddressOf updateParam
and in the handler
Private Sub updateParam(sender As Object, e As System.EventArgs)
Dim parameterX As tParam = sender.Tag
With parameterX
Select Case .label.Text
Case "Id"
parameter1.valueX = .comboBox.SelectedIndex
End Select
End Sub
The problem is that I have a lot (>50) parameters of type tParam and I like not to check every parameter name with the select case.
Note that I am calling directly parameter1 in the handler, because parameterX (=sender.Tag) is read-only, as any update to parameterX is local.

I cant quite tell what you are trying to do, but tStruct.ComboBox.Tag = Me seems a convoluted way to track your widgets. Using a class, you could internalize and simplify some of what it seems you are trying to do:
Public Class CBOWidgetItem
Private WithEvents myCBO As ComboBox
Private myLbl As Label
Public Property Name As String
Public Property Value As String
Public Sub New(n As String, cbo As ComboBox, lbl As Label)
Name = n
myCBO = cbo
myLbl = lbl
End Sub
Private Sub myCBO_SelectedIndexChanged(sender As Object,
e As EventArgs) Handles myCBO.SelectedIndexChanged
Value = myCBO.SelectedIndex.ToString
End Sub
Public Overrides Function ToString() As String
Return Name
End Function
End Class
The widget is able to handle the Value change itself (again, I dont quite know what you are up to). You might have other wrapper props to expose certain info the widget is managing:
Public ReadOnly Property LabelText As String
Get
If myLbl IsNot Nothing Then
Return myLbl.Text
Else
Return ""
End If
End Get
End Property
To use it:
' something to store them in:
Private widgets As List(Of CBOWidgetItem)
...
widgets = New List(Of CBOWidgetItem)
' long form
Dim temp As New CBOWidgetItem("ID", ComboBox1, Label1)
widgets.Add(temp)
' short form:
widgets.Add(New CBOWidgetItem("foo", ComboBox2, Label2))
Elsewhere if you need to find one of these guys:
Dim find = "ID"
Dim specificItem = widgets.Where(Function(s) s.Name = find).FirstOrDefault
If specificItem IsNot Nothing Then
Console.WriteLine(specificItem.Name)
End If
Alternatively, you could use a Dictionary(Of String, CBOWidgetItem) and get them back by name.

Related

VB.NET Search ListBox for string and return specific data

I have a populated listbox. Each item has a string of data with id's and values. How would i search for the id and receive the vale?
If i search for 'itemColor' i would like it to return each boot color in a new msgbox.
itemName="boots" itemCost="$39" itemColor="red"
itemName="boots" itemCost="$39" itemColor="green"
itemName="boots" itemCost="$39" itemColor="blue"
itemName="boots" itemCost="$39" itemColor="yellow"
I understand there are different and easier ways to do this but i need to do it this way.
Thanks!
Here's one way to do it involving parsing the text as XML:
' Here's Some Sample Text
Dim listboxText As String = "itemName=""boots"" itemCost=""$39"" itemColor=""red"""
' Load XML and Convert to an Object
Dim xmlDocument As New System.Xml.XmlDocument
xmlDocument.LoadXml("<item " & listboxText & "></item>")
Dim item = New With {.ItemName = xmlDocument.DocumentElement.Attributes("itemName").Value,
.ItemCost = xmlDocument.DocumentElement.Attributes("itemCost").Value,
.ItemColor = xmlDocument.DocumentElement.Attributes("itemColor").Value}
' Write It Out as a Test
Console.WriteLine(item.ItemName & " " & item.ItemCost & item.ItemColor)
Console.Read()
Create a class (or structure), the appropriate properties, a default constructor, a parametized constructor and an Override of .ToString.
Public Class Item
Public Property Name As String
Public Property Cost As String
Public Property Color As String
Public Sub New()
End Sub
Public Sub New(sName As String, sCost As String, sColor As String)
Name = sName
Cost = sCost
Color = sColor
End Sub
Public Overrides Function ToString() As String
Return $"{Name} - {Cost} - {Color}"
End Function
End Class
Item objects are added to the list box calling the parameterized constructor. The list box calls .ToString on the objects to determine what to display.
Private Sub FillList()
ListBox3.Items.Add(New Item("boots", "$39", "red"))
ListBox3.Items.Add(New Item("boots", "$39", "green"))
ListBox3.Items.Add(New Item("boots", "$39", "blue"))
ListBox3.Items.Add(New Item("boots", "$39", "yellow"))
End Sub
Since we added Item objects to the list box, we can cast each list item back to the Item type and access its properties.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim sb As New StringBuilder
For Each i In ListBox3.Items
sb.AppendLine(DirectCast(i, Item).Color)
Next
MessageBox.Show(sb.ToString)
End Sub

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

Get Label through Function

all! I'm developing a BlackJack game but I've run into a little bit of a problem. When calculating score, I have to type YourCard1.Text, YourCard2.Text, YourCard3.Text, etc.
Can I make a function that gets the right label each time it's called? I want to do this so I don't have to type so much...
For example, instead of typing out "YourCard1.Text", I want to be able to type "card(1)" Is this possible? I've tried multiple ways of doing this, but to no avail. I'm having trouble figuring out how to make it work.
Assuming you have those labels on your form, YourCard1.Text, YourCard2.Text, YourCard3.Text, etc., This function should work for you. It returns the Label itself, not the Text property.
Private Function card(index As Integer) As Label
Try
Return Me.Controls.
OfType(Of Label).
Where(Function(l) l.Name = "YourCard" & index.ToString()).
Single()
Catch
Return Nothing
End Try
End Function
Note: Me.Controls returns the controls directly inside the form, but doesn't return controls inside containers in the form. If your cards are inside a panel, Panel1 for example, you would do Return Panel1.Controls.OfType(Of Label)...
Usage:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
card(1).Text = "Hello"
card(2).Text = "World"
End Sub
Edit to address comment.
You are pidgeonholed into only those semantics. So there is another way I could think of. But I wouldn't personally do this.
Public Class Form1
Private Class cardClass
Private myContainer As Control
Sub New(container As Control)
myContainer = container
End Sub
Default Public WriteOnly Property Item(ByVal index As Long) As String
Set(value As String)
card(index).Text = value
End Set
End Property
Private Function card(index As Integer) As Label
Try
Return myContainer.Controls.
OfType(Of Label).
Where(Function(l) l.Name = "YourCard" & index.ToString()).
Single()
Catch
Return Nothing
End Try
End Function
End Class
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim card As New cardClass(Me)
card(1) = "Hello"
card(2) = "World"
End Sub
End Class
The reason it's so complex is that though String is a reference type, it uses value type semantics. So when returning a string from a function, it can't refer back to the original memory location: it actually creates a copy of the string. So using function semantics won't work. Same would go for an array. It would be difficult (impossible?) to modify a string from either a function or array and have it modify the Label's Text property.

DataGridView Auto-calculate cells In Vb

I want to multiply two columns of a datagridview and show the product in Column 3 of the same datagridview.
Example
Column1 - Column2 - Column3
12 2 24
15 2 30
Here is my Code
Private Sub Table1DataGridView1_CellContentClick(sender As Object, e As DataGridViewCellEventArgs) Handles Table1DataGridView1.CellValidated
Try
Dim iCell1 As Integer
Dim icell2 As Integer
Dim icellResult As Integer
iCell1 = Table1DataGridView1.CurrentRow.Cells(1).Value
icell2 = Table1DataGridView1.CurrentRow.Cells(2).Value
If String.IsNullOrEmpty(iCell1) OrElse String.IsNullOrEmpty(icell2) Then Exit Sub
If Not IsNumeric(iCell1) OrElse Not IsNumeric(icell2) Then Exit Sub
icellResult = CDbl(iCell1) * CDbl(icell2)
Table1DataGridView1.CurrentRow.Cells(3).Value = icellResult
Catch ex As Exception
MessageBox.Show(ex.ToString)
End Try
End Sub
It works but a new row is added afterwards. So please help.
An elegant way is to use Databinding (the bread and butter method with datagridviews):
We create a new class, I call MultiplyPair. This class basically has to changable properties, Value1 and Value2.
It also has a third, readonly property called Product.
Whenever Value1 or Value2 changes, we notify any bindingsource that also the product has changed (by implementing INotifyPropertyChanged).
This has several advantages:
You avoid many quirks of the DGV that may arise during manual edits
It's a great way to learn to use databinding (I'm just beginning myself to use the concept thoroughly and it's amazing)
It's easily adaptable: Want also the product, sum and quotient? Just add properties and simple notifications.
You have a clear structure, so other people can understand your code.
You can reuse the whole logic, just bind another datagridview to the same bindinglist and you can display and change the information at multiple locations, with hardly any code.
To use the class we create a Bindinglist(Of MultiplyPair) and bind this list to the datagridview. Then we just ass values to the list and the datagridview is populated automatically. You can even change the values in the Datagridview and the product is automatically updated.
Public Class Form1
Private WithEvents Values As New System.ComponentModel.BindingList(Of MultiplyPair) 'This holds one object for every line in the DGV
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Values = New System.ComponentModel.BindingList(Of MultiplyPair) 'Initialize the list
DataGridView1.AllowUserToAddRows = False 'Disallow new rows, as per your question
DataGridView1.DataSource = Values 'Bind the list to the datagridview
'Add two example lines
Values.Add(New MultiplyPair With {.Value1 = 2, .Value2 = 12})
Values.Add(New MultiplyPair With {.Value1 = 15, .Value2 = 2})
End Sub
End Class
Public Class MultiplyPair
Implements System.ComponentModel.INotifyPropertyChanged
'The first value
Private _Value1 As Double
Public Property Value1
Get
Return _Value1
End Get
Set(value)
_Value1 = value
'Notify not only that Value1 has changed, but also that the Product has changed
Notify("Value1")
Notify("Product")
End Set
End Property
'Same as above
Private _Value2 As Double
Public Property Value2
Get
Return _Value2
End Get
Set(value)
_Value2 = value
Notify("Value2")
Notify("Product")
End Set
End Property
'This will show the product of Value1 and Value2 whenever asked
Public ReadOnly Property Product
Get
Return Value1 * Value2
End Get
End Property
'Helper sub to raise the PropertyChanged event with the given Propertyname
Private Sub Notify(name As String)
RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs(name))
End Sub
'INotifyPropertyChanged implementation
Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class
Edit: To avoid additional rows, set the AllowUsersToAddRows property of the Datagridview to false.

Binding data to a combobox and add a select option

I'm using VB.NET and Visual Studio 2010
I've got a windows form with a combobox.
I fill the combobox using the following
Dim objSizes As List(Of ASME_Hub_Sizes) = ASME_Hub_Sizes.GetAllHubSizes()
If Not objSizes Is Nothing Then
With Me.cboSize
.DisplayMember = "Size"
.ValueMember = "ID"
.DataSource = objSizes
End With
End If
This works fine, but i would like to add a "Select Size..." option but i'm unsure how to do this.
It seems so much easier to do this in asp.net, but this has me baffled
Thanks
Mick
You could try adding a custom objSize object to the objSizes collection that has ID = 0 and value = "Select size..." as it's ID 0 it should be at the top (I think) and won't clash with any values in your database, upon saving the record you can validate the combobox to avoid writing the "Select size..." object to your database. I'll have a bit of a code and see if this will work...
Ok, I've had another look. You can do as I suggested but you must sort the list before passing it to the combobox. Here is my example:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
With Me.ComboBox1
.DisplayMember = "Description"
.ValueMember = "ID"
.DataSource = GetListOfObjects.returnListOFObjects
End With
End Sub
End Class
Public Class dummyObjectForCombo
Public Property ID As Integer
Public Property Description As String
Public Sub New(ByVal id As Integer,
ByVal description As String)
_ID = id
_Description = description
End Sub
End Class
Public Class GetListOfObjects
Public Shared Function returnListOFObjects() As List(Of dummyObjectForCombo)
Dim col As New List(Of dummyObjectForCombo)
Dim obj0 As New dummyObjectForCombo(-1, "Herp")
Dim obj1 As New dummyObjectForCombo(1, "Jamie")
Dim obj2 As New dummyObjectForCombo(2, "Bob")
col.Add(obj1)
col.Add(obj2)
col.Add(obj0)
'using a lambda to sort by ID as per http://stackoverflow.com/questions/3309188/c-net-how-to-sort-a-list-t-by-a-property-in-the-object
Return col.OrderBy(Function(x) x.ID).ToList
End Function
End Class
I've used -1 as the topmost record instead of 0.
So you'd get your list as usual, add in the extra dummy record, then sort it as per the above code before assigning it as your combo boxes datasource.
simply add the item before setting the datasource property
C# : cboSize.items.add(...);