how to keep checkedlistbox and a structure in sync - vb.net

I have declared a structure
Public Structure cList
Public Name As String
Public Path As String
Public isChecked As Boolean
End Structure
And variables of it -
Public sourceList As New List(Of cList)
Public source As cList
And I have a CheckedListBox
What I want to achive is when any of element of checkedlistbox is selected or deselected it must reflact on
sourceList.isChecked = False 'When Deselected
or
sourceList.isChecked = True 'When Selected
Well this won't work here to achive this and I used a technique here in this ex.
For index = 0 To sourceList.Count - 1
source = sourceList(index)
'by this way I can access every source(item) of sourceList
source.Name = "test"
any changes
sourceList.Add(source)
'changes are reflecting to sourceList
Next
To achive sync of checkedListBox.CheckedItems and sourceList.isChecked I written this code
'First making isChecked value to false for every property in sourceList
For index = 0 To sourceList.Count - 1
source = sourceList(index)
source.isChecked = False
sourceList.Add(source)
Next
'Now assigning isChecked=true for Checked items of listbox
For Each item As String In CheckedListBox1.CheckedItems
For index = 0 To sourceList.Count - 1
source = sourceList(index)
If item = source.Name Then
source.isChecked = True
sourceList.Add(source)
End If
Next
Next
but it gives runtime errors
Every item selected or deselected didn't reflact on it's corresponding isChecked
Anyone please help??

This seems to work just fine.. The form load event handler is just there to populate the sourceList and to bind it to the CheckedListbox'. This of course populates theCheckedListBox` with the names of the Items in sourceList
The event handler is the important bit. If the Checkbox is already checked, it will uncheck it and mark the item with the same index value in sourceList's property 'isChecked` as false. If the checkbox is already unchecked, it will do the reverse.
Public Class cList
Property Name As String
Property Path As String
Property IsChecked As Boolean
End Class
Public sourceList As New List(Of cList)
Public source As cList
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
sourceList.Add(New cList With {.Name = "first", .Path = "firstpath", .IsChecked = False})
sourceList.Add(New cList With {.Name = "second", .Path = "secondpath", .IsChecked = False})
sourceList.Add(New cList With {.Name = "third", .Path = "thirdpath", .IsChecked = False})
CheckedListBox1.DataSource = sourceList
CheckedListBox1.DisplayMember = "Name"
End Sub
Private Sub CheckedListBox1_ItemCheck(sender As Object, e As ItemCheckEventArgs) Handles CheckedListBox1.ItemCheck
If e.CurrentValue = CheckState.Checked Then
e.NewValue = CheckState.Unchecked
sourceList.Item(e.Index).IsChecked = False
Else
e.NewValue = CheckState.Checked
sourceList(e.Index).IsChecked = True
End If
MessageBox.Show(sourceList(e.Index).Name & " " & sourceList(e.Index).IsChecked)
End Sub

Related

DataGridView column of arbitrary controls per row (or replicate that appearance)

I have a DataGridView which contains, among other things, a "Feature" column and a "Value" column. The "Feature" column is a DataGridViewComboBoxColumn. What I would like to do, is manipulate the "Value" column, such that it can be one of a number of controls, depending on the item selected in the "Feature" column on any given row. So, for example :
Feature
Value Cell Behaviour
A
Combobox with a pre-determined set of options, specific to Feature A
B
Combobox with a different set of pre-determined options, specific to Feature B
C
Checkbox
D
Textbox (free-format)
X
etc. etc.
My initial naive approach to this (which I never really expected to work but figured I'd try anyway...) was to programmatically manipulate the specific value cell in the grid whenever the Feature combobox on the same row was changed :
Private Sub dgv_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) Handles dgv.CellValueChanged
Dim changedCell As DataGridViewCell = CType(dgv.Rows(e.RowIndex).Cells(e.ColumnIndex), DataGridViewCell)
If changedCell.ColumnIndex = cboFeatureColumn.Index Then
Dim cboChangedFeature = CType(changedCell, DataGridViewComboBoxCell)
If cboChangedFeature.Value IsNot Nothing Then ConfigureValueField(cboChangedFeature)
End If
End Sub
Private Sub ConfigureValueField(cboFeature As DataGridViewComboBoxCell)
Dim cllValueField As DataGridViewCell = dgv.Rows(cboFeature.RowIndex).Cells(valueColumn.Index)
Dim featureID As Integer = dgv.Rows(cboFeature.RowIndex).Cells(featureIDColumn.Index).Value
Dim matches = From row In dtbFeatureList Let lookupID = row.Field(Of Integer)("ID") Where lookupID = featureID
Dim strFieldControl As String = ""
If matches.Any Then strFeatureControl = matches.First().row.Field(Of String)("FieldControl")
Select Case strFieldControl
Case "Checkbox"
' Do something
Case "Textbox"
' Do something
Case "Combobox"
Dim cboConfigured As New DataGridViewComboBoxCell
Dim dvOptions As New DataView(dtbFeatureValueList)
dvOptions.RowFilter = "[FeatureID] = " & featureID
Dim dtbOptions As DataTable
dtbOptions = dvOptions.ToTable
With cboConfigured
.DataSource = dtbOptions
.ValueMember = "Value"
.DisplayMember = "ValueText"
.DisplayStyle = DataGridViewComboBoxDisplayStyle.ComboBox
.ReadOnly = False
End With
cllValueField = cboConfigured
End Select
End Sub
But this (probably, obviously to many) doesn't work; for starters it throws a DataError Default Error Dialog :
The following exception occurred in the DataGridView:
System.FormatException: DataGridViewComboBoxCell value is not valid To
replace this default dialog please handle the DataError event.
...which I can trap and handle (i.e. ignore!) easily enough :
Private Sub dgv_DataError(sender As Object, e As DataGridViewDataErrorEventArgs) Handles dgv.DataError
e.Cancel = True
End Sub
...and the resulting cell does have the appearance of a combobox but nothing happens when I click the dropdown (no list options appear) I suspect there are a whole host of DataErrors being thrown; to be quite honest, I'm not entirely comfortable with ignoring exceptions like this without handling them properly...
The only alternative option I can think of is to add separate columns for each possible value type (so add a DataGridViewComboBoxColumn for combos, a DataGridViewCheckBoxColumn for checkboxes, a DataGridViewTextBoxColumn for textboxes etc.) but then all of my values are scattered across multiple columns instead of all under a single heading which will be really confusing to look at.
And I really want to retain the appearance of comboboxes (set list of options) versus checkboxes (boolean values) versus textboxes (free-format text), as it makes it a lot easier for users to differentiate the values.
I read somewhere that it may be possible to derive my own custom column class, inheriting the native DataGridViewColumn class, but I couldn't find any examples of how this was done. I've done something similar with a customised DataGridViewButtonColumn but literally just to change the appearance of the buttons across the entire column, not the functionality of the individual cells within it.
Would anybody have any suggestions as to how it might be possible to have a mix of controls in the same column, configured specifically to the row in which they reside?
EDIT
So, I followed the walkthrough at : https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-host-controls-in-windows-forms-datagridview-cells?view=netframeworkdesktop-4.8&redirectedfrom=MSDN
And I've added four new classes to my project as follows :
UserControl class : a basic control which contains a textbox, a combobox and a checkbox, and basic methods for showing/hiding each one as appropriate, configuring the combobox if necessary etc. By default, the UserControl should display as an empty textbox.
CustomConfigurableCellEditingControl : derived from the UserControl in #1
DataGridViewCustomConfigurableCell : container for the EditingControl in #2 and derived from DataGridViewTextBoxCell
DataGridViewCustomConfigurableColumn : container for the Cell in #3 and derived from DataGridViewColumn
Public Class UserControl
Private displayControl As String
Private displayValue As String
Private myValue As String
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
displayControl = "Textbox"
displayValue = ""
refreshDisplay()
End Sub
Public Property ControlToDisplay As String
Get
Return displayControl
End Get
Set(value As String)
displayControl = value
End Set
End Property
Public Property ValueToDisplay As String
Get
Return displayValue
End Get
Set(value As String)
displayValue = value
End Set
End Property
Public Property Value As String
Get
Return myValue
End Get
Set(value As String)
myValue = value
End Set
End Property
Public Sub refreshDisplay()
Select Case displayControl
Case "Textbox"
With ucTextBox
.Text = displayValue
.Visible = True
End With
ucComboBox.Visible = False
ucCheckbox.Visible = False
Case "Combobox"
With ucComboBox
.Visible = True
End With
ucTextBox.Visible = False
ucCheckbox.Visible = False
Case "Checkbox"
With ucCheckbox
.Checked = myValue
.Visible = True
End With
ucTextBox.Visible = False
ucComboBox.Visible = False
End Select
End Sub
Public Sub configureCombobox(dtb As DataTable, valueMember As String, displayMember As String, style As ComboBoxStyle)
With ucComboBox
.DataSource = dtb
.ValueMember = "ID"
.DisplayMember = "FriendlyName"
.DropDownStyle = style
End With
End Sub
End Class
Class CustomConfigurableCellEditingControl
Inherits UserControl
Implements IDataGridViewEditingControl
Private dataGridViewControl As DataGridView
Private valueIsChanged As Boolean = False
Private rowIndexNum As Integer
Public Sub New()
End Sub
Public Property EditingControlFormattedValue() As Object _
Implements IDataGridViewEditingControl.EditingControlFormattedValue
Get
'Return Me.Value.ToShortDateString()
Return Me.valueIsChanged.ToString
End Get
Set(ByVal value As Object)
Me.Value = value
End Set
End Property
Public Function GetEditingControlFormattedValue(ByVal context As DataGridViewDataErrorContexts) As Object _
Implements IDataGridViewEditingControl.GetEditingControlFormattedValue
Return Me.valueIsChanged.ToString
End Function
Public Sub ApplyCellStyleToEditingControl(ByVal dataGridViewCellStyle As DataGridViewCellStyle) _
Implements IDataGridViewEditingControl.ApplyCellStyleToEditingControl
Me.Font = dataGridViewCellStyle.Font
End Sub
Public Property EditingControlRowIndex() As Integer _
Implements IDataGridViewEditingControl.EditingControlRowIndex
Get
Return rowIndexNum
End Get
Set(ByVal value As Integer)
rowIndexNum = value
End Set
End Property
Public Function EditingControlWantsInputKey(ByVal key As Keys,
ByVal dataGridViewWantsInputKey As Boolean) As Boolean _
Implements IDataGridViewEditingControl.EditingControlWantsInputKey
End Function
Public Sub PrepareEditingControlForEdit(ByVal selectAll As Boolean) _
Implements IDataGridViewEditingControl.PrepareEditingControlForEdit
' No preparation needs to be done.
End Sub
Public ReadOnly Property RepositionEditingControlOnValueChange() As Boolean _
Implements IDataGridViewEditingControl.RepositionEditingControlOnValueChange
Get
Return False
End Get
End Property
Public Property EditingControlDataGridView() As DataGridView _
Implements IDataGridViewEditingControl.EditingControlDataGridView
Get
Return dataGridViewControl
End Get
Set(ByVal value As DataGridView)
dataGridViewControl = value
End Set
End Property
Public Property EditingControlValueChanged() As Boolean _
Implements IDataGridViewEditingControl.EditingControlValueChanged
Get
Return valueIsChanged
End Get
Set(ByVal value As Boolean)
valueIsChanged = value
End Set
End Property
Public ReadOnly Property EditingControlCursor() As Cursor _
Implements IDataGridViewEditingControl.EditingPanelCursor
Get
Return MyBase.Cursor
End Get
End Property
Protected Overrides Sub OnValueChanged(ByVal eventargs As EventArgs)
' Notify the DataGridView that the contents of the cell have changed.
valueIsChanged = True
Me.EditingControlDataGridView.NotifyCurrentCellDirty(True)
MyBase.OnValueChanged(eventargs)
End Sub
End Class
Public Class DataGridViewCustomConfigurableCell
Inherits DataGridViewTextBoxCell
Public Sub New()
End Sub
Public Overrides Sub InitializeEditingControl(ByVal rowIndex As Integer,
ByVal initialFormattedValue As Object,
ByVal dataGridViewCellStyle As DataGridViewCellStyle)
' Set the value of the editing control to the current cell value.
MyBase.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle)
Dim ctl As CustomConfigurableCellEditingControl = CType(DataGridView.EditingControl, CustomConfigurableCellEditingControl)
' Use the default row value when Value property is null.
If (Me.Value Is Nothing) Then
ctl.Value = CType(Me.DefaultNewRowValue, String)
Else
ctl.Value = CType(Me.Value, String)
End If
End Sub
Public Overrides ReadOnly Property EditType() As Type
Get
' Return the type of the editing control that Cell uses.
Return GetType(CustomConfigurableCellEditingControl)
End Get
End Property
Public Overrides ReadOnly Property ValueType() As Type
Get
' Return the type of the value that Cell contains.
Return GetType(String)
End Get
End Property
Public Overrides ReadOnly Property DefaultNewRowValue() As Object
Get
Return ""
End Get
End Property
End Class
Imports System.Windows.Forms
Public Class DataGridViewCustomConfigurableColumn
Inherits DataGridViewColumn
Public Sub New()
MyBase.New(New DataGridViewCustomConfigurableCell())
End Sub
Public Overrides Property CellTemplate() As DataGridViewCell
Get
Return MyBase.CellTemplate
End Get
Set(ByVal value As DataGridViewCell)
' Ensure that the cell used for the template is a Custom Configurable Cell.
If (value IsNot Nothing) AndAlso Not value.GetType().IsAssignableFrom(GetType(DataGridViewCustomConfigurableCell)) Then
Throw New InvalidCastException("Must be a Custom Configurable Cell")
End If
MyBase.CellTemplate = value
End Set
End Property
End Class
But... I'm still none the wiser as to how I populate, display, manipulate etc. The code compiles fine, but I just get a blank column. I can't see any controls, I can't see any values and I can't seem to "trap" any of the events that should manipulate them?
With dgv
cfgValueColumn = New DataGridViewCustomConfigurableColumn With {.DisplayIndex = valueColumn.Index + 1}
With cfgValueColumn
.HeaderText = "Custom Value"
.Width = 300
End With
.Columns.Add(cfgValueColumn)
Dim cfgCell As DataGridViewCustomConfigurableCell
For Each row As DataGridViewRow In .Rows
cfgCell = CType(row.Cells(cfgValueColumn.Index), DataGridViewCustomConfigurableCell)
With cfgCell
.Value = row.Cells(valueColumn.Index).Value
End With
Next
End With
Your main approach is correct if you just need to change the type of the given cell based on the ComboBox selection.
When the value changes of the ComboBox cell:
Dispose of the current cell of the target column.
Create a new cell whose type is defined by the ComboBox selection.
Assign the new cell to the grid place of the old one.
Here's a working example.
Private Sub dgv_CellValueChanged(
sender As Object,
e As DataGridViewCellEventArgs) Handles dgv.CellValueChanged
If e.RowIndex < 0 Then Return
If e.ColumnIndex = dgvcTypeSelector.Index Then
Dim cmb = DirectCast(dgv(e.ColumnIndex, e.RowIndex), DataGridViewComboBoxCell)
Dim selItem = cmb.FormattedValue.ToString()
Dim valSelCell = dgv(dgvcValueSelector.Index, e.RowIndex)
valSelCell.Dispose()
Select Case selItem
Case "Text"
valSelCell = New DataGridViewTextBoxCell With {
.Value = "Initial value If any."
}
Case "Combo"
valSelCell = New DataGridViewComboBoxCell With {
.ValueMember = "Value",
.DisplayMember = "ValueText",
.DataSource = dt,
.Value = 2 ' Optional...
}
Case "Check"
valSelCell = New DataGridViewCheckBoxCell With {
.ValueType = GetType(String),
.Value = True
}
valSelCell.Style.Alignment = DataGridViewContentAlignment.MiddleCenter
Case "Button"
valSelCell = New DataGridViewButtonCell With {
.Value = "Click Me!"
}
valSelCell.Style.Alignment = DataGridViewContentAlignment.MiddleCenter
End Select
dgv(dgvcValueSelector.Index, e.RowIndex) = valSelCell
ElseIf e.ColumnIndex = dgvcValueSelector.Index Then
Dim cell = dgv(e.ColumnIndex, e.RowIndex)
Console.WriteLine($"{cell.Value} - {cell.FormattedValue}")
End If
End Sub
Private Sub dgv_CurrentCellDirtyStateChanged(
sender As Object,
e As EventArgs) Handles dgv.CurrentCellDirtyStateChanged
If dgv.IsCurrentCellDirty Then
dgv.CommitEdit(DataGridViewDataErrorContexts.Commit)
End If
End Sub
' If you need to handle the Button cells.
Private Sub dgv_CellContentClick(
sender As Object,
e As DataGridViewCellEventArgs) Handles dgv.CellContentClick
If e.RowIndex >= 0 AndAlso
e.ColumnIndex = dgvcValueSelector.Index Then
Dim btn = TryCast(dgv(e.ColumnIndex, e.RowIndex), DataGridViewButtonCell)
If btn IsNot Nothing Then
Console.WriteLine(btn.FormattedValue)
End If
End If
End Sub
Note, I've changed the value type of the check box cell by ValueType = GetType(String) to avoid throwing exceptions caused by the different value types of the check box and main columns. I'd assume the main column here is of type DataGridViewTextBoxColumn. So you have String vs. Boolean types. If you face problems in the data binding scenarios, then just swallow the exception.
Private Sub dgv_DataError(
sender As Object,
e As DataGridViewDataErrorEventArgs) Handles dgv.DataError
If e.RowIndex >= 0 AndAlso e.ColumnIndex = dgvcValueSelector.Index Then
e.ThrowException = False
End If
End Sub

Multiple Checkboxes = Text in Text Box

I have a form I'm trying to build. I would like multiple checkboxs to ADD text to a textbox. I was unable to do to do that, without define alot! I used 3 Checkboxes in the original scenario. Now I have to do that using 12 checkboxes. I would like to know if there is an easier way to build this.
The fallowing shows what I used to with the original 3. I would really appreciate help so I don't have to do this for the next 12, it'd be A LOT! Thank you in advance!
Private Sub SUE_Click()
If SUE.value = True And SUD.value = False And SUG.value = False Then
StartUp.Value = "E DEGD"
Else
If SUD.value = True And SUE.value = False And SUG.value = False Then
StartUp.Value = "D DEGD"
Else
If SUG.value = True And SUD.value = False And SUE.value = False Then
StartUp.Value = "G DEGD"
Else
If SUD.value And SUE.value And SUG.value = True Then
StartUp.Value = "D DEGD, E DEGD, G ALT DEGD"
Else
If SUD.value And SUE.value = True Then
StartUp.Value = "D DEGD, E DEGD"
Else
If SUE.value And SUG.value = True Then
StartUp.Value = "E DEGD, G ALT DEGD"
Else
If SUD.value And SUG.value = True Then
StartUp.Value = "D DEGD, G ALT DEGD"
Else
If SUE.value = False Then
StartUp.Value = ""
Else
If SUD.value = False Then
StartUp.Value = ""
Else
If SUG.value = False Then
StartUp.Value = ""
End If
End If
End If
End If
End If
End If
End If
End If
End If
End If
End Sub
I really don't think this is the way to go. You're going to end up with lots of lines of code each time you need to add a checkbox.
With the following, the only thing you need to do is :
Create your userform with all the checkboxes you need (keep track of the captions).
In the main, create as many stringprovider objects as you need (probably as many as there are checkboxes), supply them with the string they need to produce and the caption of the checkbox to which they are linked.
Register the stringproviders with the stringbuilder.
supply the stringbuilder to the form.
What you need to do is:
Create a StringProvider class (you need to create a class module) as
follows
This is a very simple class that contains 3 pieces of information:
The string you want to produce
The Enabled/Disabled state
The caption (case sensitive) of the checkbox to which it is linked.
This object is designed to store the string value you want as an output, e.g. "E DEGD", as well as the name (in fact the Caption) of the checkbox (e.g. "SUE", "SUD" in your code) that is linked to it.
Option Explicit
Private Type TValueProvider
value As String
IsEnabled As Boolean
LinkledTB As String
End Type
Private this As TValueProvider
Public Property Get value() As String
value = this.value
End Property
Public Property Let value(v As String)
this.value = v
End Property
Public Property Get IsEnabled() As Boolean
IsEnabled = this.IsEnabled
End Property
Public Property Let IsEnabled(b As Boolean)
this.IsEnabled = b
End Property
Public Property Get LinkedTB() As String
LinkedTB = this.LinkledTB
End Property
Public Property Let LinkedTB(textboxname As String)
this.LinkledTB = textboxname
End Property
A stringbuilder class, with the following code:
This class will store all the stringprovider objects. When called, the UpdateString() sub will a) go through the registered stringproviders (in order of addition) and fire StringUpdated event that is used to inform the UserForm that the string was updated and that it needs to display it.
Option Explicit
Private Type TStringBuilder
stringproviders As Collection
End Type
Public Event StringUpdated(newstring As String)
Private this As TStringBuilder
Private Sub class_initialize()
Set this.stringproviders = New Collection
End Sub
Public Sub UpdateString()
Dim k As Variant
Dim sp As StringProvider
Dim arr() As String
Dim i As Long
Dim EnabledStringProviders As Collection
Set EnabledStringProviders = New Collection
For Each k In this.stringproviders
Set sp = k
If sp.IsEnabled Then
EnabledStringProviders.Add sp
End If
Next k
If EnabledStringProviders.Count = 0 Then
RaiseEvent StringUpdated(vbNullString)
Else
ReDim arr(EnabledStringProviders.Count - 1)
For Each k In EnabledStringProviders
Set sp = k
arr(i) = sp.value
i = i + 1
Next k
RaiseEvent StringUpdated(Join(arr, ", "))
End If
End Sub
Public Sub RegisterStringProvider(sp As StringProvider)
this.stringproviders.Add sp
End Sub
Public Function getSringProviderFromTBName(name As String)
Dim sp As StringProvider
Dim k As Variant
For Each k In this.stringproviders
Set sp = k
If sp.LinkedTB = name Then
Set getSringProviderFromTBName = sp
Exit Function
End If
Next k
Err.Raise vbObjectError + 1, , "Could not find stringprovider with name: " & name
End Function
This will hold a collection of stringproviders. When UpdateString() is called, it will go through the stringproviders and retrieve their value if they are updated.
You also need wrapper for the checkbox object, which would look, like this:
Option Explicit
Private WithEvents WrappedTB As MSForms.CheckBox
Private pstringbuilder As stringbuilder
Public Sub Initialize(ByVal tb As MSForms.CheckBox, sb As stringbuilder)
Set WrappedTB = tb
Set pstringbuilder = sb
End Sub
Private Sub wrappedTB_Change()
Dim sp As StringProvider
Set sp = pstringbuilder.getSringProviderFromTBName(WrappedTB.Caption)
sp.IsEnabled = WrappedTB.value
pstringbuilder.UpdateString
End Sub
The role of this wrapper is to capture the Checkbox_change event. Whenever a checkbox changes state, the wrappedTB_Change() sub is called. It requests the stringprovider linked to the checkbox from the stringbuilder and then calls the UpdateString method of the stringbuilder object. Note that stringbuilder holds references to the stringproviders so that an update to the stringprovider is automatically reflected inside the stringbuilder.
Your form should look like this:
Option Explicit
Private WithEvents pstringbuilder As stringbuilder
Private wrappers As Collection
Private Sub UserForm_initialize()
Set wrappers = New Collection
End Sub
Public Sub ShowDialog(sb As stringbuilder)
Dim wrapper As ChkBoxWrapper
Dim c As Control
Set pstringbuilder = sb
' This code discovers how many checkboxes you have in the userform, and creates one
' wrapper per checkbox. It supplies each wrapper with the same stringbuilder object
' the wrappers are then stored in the wrappers collection.
' That way you don't need to know how many checkboxes there are at compile time.
For Each c In Me.Controls
Set wrapper = New ChkBoxWrapper
If TypeName(c) = "CheckBox" Then
wrapper.Initialize c, sb
wrappers.Add wrapper
End If
Next c
Me.Show
End Sub
Private Sub pstringbuilder_StringUpdated(s As String)
' This handles the event from the stringbuilder object.
TextBox1.value = s
End Sub
As an example usage, this is module1:
Public Sub main()
Dim frm As UserForm1
Set frm = New UserForm1
Dim sp1 As StringProvider
Dim sp2 As StringProvider
Set sp1 = StringProviderFactory("Toto", "CheckBox1") ' This is where you associate the string you want to output with the checkbox Caption
Set sp2 = StringProviderFactory("Titi", "CheckBox2")
Dim sb As stringbuilder
Set sb = New stringbuilder
' Then you register each stringprovider with the stringbuilder.
sb.RegisterStringProvider sp1
sb.RegisterStringProvider sp2
frm.ShowDialog sb
End Sub
Private Function StringProviderFactory(value As String, linked_TB_name As String) As StringProvider
' Just a helper function to create stringproviders
Dim sp As StringProvider
Set sp = New StringProvider
sp.value = value
sp.LinkedTB = linked_TB_name
Set StringProviderFactory = sp
End Function
I hope this helps. The code can be very much improved on, but hopefully this will get you started.

User control items in FlowLayoutPanel cannot be incremented using ExecuteReader database for Visual Basic

my question about how to make a list of cart shopping item using a FlowLayoutPanel in which there are items using User Control (Windows Forms)
I create a shopping cart app. Here is how this application works:
1.) Double click DaftarKeranjang FlowLayoutPanel
step 1
2 - 3.) After the Pilih form appears, click the OBH cell. Then, click the Pilih button
step 2 - 3
4.) After that, Double click DaftarKeranjang FlowLayoutPanel
step 4 .
5 - 6.) After the Pilih form appears, click the ID : 11 cell. Then, click the Pilih button
step 5 - 6
7.) Results like this appear : Result 2a .
The correct result should be like this : Result 2b
How to fix the problem of list items not growing after clicking the Pilih button so that the result of image Result 2b can be achieved?
The following is the program code that has been written using the Visual Basic programming language :
PilihObat.vb
Public Sub btnpilih_click(sender As Object, e As EventArgs) Handles btnPilih.Click
Dim sqlcari, sqlnama As String
Dim id_barang As Integer
If dgvPilihDaftarObat.Rows.Item(dgvPilihDaftarObat.CurrentRow.Index).Cells(0).Value.ToString = "" Then
MsgBox("data kosong!", vbInformation, "pilih data")
Else
id_barang = dgvPilihDaftarObat.Rows.Item(dgvPilihDaftarObat.CurrentRow.Index).Cells(0).Value
sqlcari = "SELECT id,namaobat,jumlahobat,hargaobat FROM tb_obat WHERE id = '" & id_barang & "'"
cmd = New Odbc.OdbcCommand
cmd.CommandType = CommandType.Text
cmd.Connection = conn
cmd.CommandText = sqlcari
dr = cmd.ExecuteReader()
If dr.Read Then
ApotekerDashboard.ItemKeranjang1.NamaItemObat.Text = dr.Item(1)
Me.Close()
End If
End If
End Sub
ItemKeranjang.vb
Imports System.ComponentModel
Public Class ItemKeranjang
#Region "Properties"
Private _title As String
Private _itemcount As Integer
<Category("Custom Props")>
Public Property Title As String
Get
Return _title
End Get
Set(ByVal value As String)
_title = value
NamaItemObat.Text = value
End Set
End Property
<Category("Custom Props")>
Public Property ItemCount As Integer
Get
Return _itemcount
End Get
Set(ByVal value As Integer)
_itemcount = value
ItemObat.Text = value
End Set
End Property
#End Region
End Class
ApotekerDashboard.vb
Private Sub DaftarKeranjang_MouseDoubleClick(sender As Object, e As MouseEventArgs) Handles DaftarKeranjang.MouseDoubleClick
PilihObat.Show()
End Sub
and the UI Designer ApotekerDashboard.vb structure:
UI Designer scructure
Instead of If dr.Read Then, you want to do something like this:
Do While dr.Read
Dim text = CType(dr.Item(1), String)
Dim ik = New ItemKeranjang() With { .Title = Text }
Dim label = New Label() With { .Text = ik.Title }
Me.Controls.Add(label)
Loop
How to raise events:
Sub Main
Dim ik As New ItemKeranjang
AddHandler ik.TitleChanged, AddressOf Hello
ik.Title = "Foo"
End Sub
Public Sub Hello(sender As Object, ea As String)
Console.WriteLine("!" & ea & "!")
End Sub
Public Class ItemKeranjang
Private _title As String
Private _itemcount As Integer
Public Event TitleChanged As EventHandler(Of String)
Public Event ItemCountChanged As EventHandler(Of Integer)
Public Property Title As String
Get
Return _title
End Get
Set(ByVal value As String)
If _title <> value Then
_title = value
RaiseEvent TitleChanged(Me, _title)
End If
End Set
End Property
Public Property ItemCount As Integer
Get
Return _itemcount
End Get
Set(ByVal value As Integer)
If _itemcount <> value Then
_itemcount = value
RaiseEvent ItemCountChanged(Me, _itemcount)
End If
End Set
End Property
End Class

Check the item in ToolStripMenuItem dynamically through Sub Function

I am new to .Net Visual Basic, I am currently self learning and trying to make some small application.
I need a help on Checking a sub menu item of ToolStripMenuItem
Complete concept is like:
I have a datagridview in which user will be able to rearrange the column or make a column visible or Hidded for this I have Sub / Function like below:
Public Sub Fun_Grid_Colomn_Visibility(ByVal GridName As DataGridView, ByRef ColName As String, ByVal MS_col As ToolStripMenuItem, ChkVal As Boolean)
If ChkVal = True Then
With GridName
.Columns("" & ColName & "").Visible = False
End With
MS_col.Checked = False
Exit Sub
End If
If ChkVal = False Then
GridName.Columns("" & ColName & "").Visible = True
MS_col.Checked = True
Exit Sub
End If
End Sub
On the form close I will be saving the user grid format as below (Got code from another Q/A Post) :
Public Sub WriteGrideViewSetting(ByVal dgv As DataGridView, ByVal FileName As String)
Dim settingwriter As XmlTextWriter = New XmlTextWriter("C:\Users\<username>\Desktop\temp\" & FileName & ".xml", Nothing)
settingwriter.WriteStartDocument()
settingwriter.WriteStartElement(dgv.Name)
Dim count As Integer = dgv.Columns.Count
For i As Integer = 0 To count - 1
settingwriter.WriteStartElement("column")
settingwriter.WriteStartElement("Name")
settingwriter.WriteString(dgv.Columns(i).Name)
settingwriter.WriteEndElement()
settingwriter.WriteStartElement("width")
settingwriter.WriteString(dgv.Columns(i).Width.ToString())
settingwriter.WriteEndElement()
settingwriter.WriteStartElement("headertext")
settingwriter.WriteString(dgv.Columns(i).HeaderText)
settingwriter.WriteEndElement()
settingwriter.WriteStartElement("displayindex")
settingwriter.WriteString(dgv.Columns(i).DisplayIndex.ToString())
settingwriter.WriteEndElement()
settingwriter.WriteStartElement("visible")
settingwriter.WriteString(dgv.Columns(i).Visible.ToString())
settingwriter.WriteEndElement()
settingwriter.WriteEndElement()
Next
settingwriter.WriteEndElement()
settingwriter.WriteEndDocument()
settingwriter.Close()
End Sub
End Module
If the user is reopening the form I used the below (Q/A code) to rearrange Datagridview column as pervious :
Public Sub ReadDataGridViewSetting(ByVal dgv As DataGridView, ByVal FileName As String, ByRef Frm_name As Form)
Dim xmldoc As XmlDocument = New XmlDocument()
Dim xmlnode As XmlNodeList
Dim CMSN_ToolName As String
Dim Var_file_Chk As String = "C:\Users\<user>\Desktop\temp\" & FileName & ".xml"
If System.IO.File.Exists(Var_file_Chk) = True Then
Dim fs As FileStream = New FileStream(Var_file_Chk, FileMode.Open, FileAccess.Read)
xmldoc.Load(fs)
xmlnode = xmldoc.GetElementsByTagName("column")
For i As Integer = 0 To xmlnode.Count - 1
Dim columnName As String = xmlnode(i).ChildNodes.Item(0).InnerText.Trim()
Dim width As Integer = Integer.Parse(xmlnode(i).ChildNodes.Item(1).InnerText.Trim())
Dim headertext As String = xmlnode(i).ChildNodes.Item(2).InnerText.Trim()
Dim displayindex As Integer = Integer.Parse(xmlnode(i).ChildNodes.Item(3).InnerText.Trim())
Dim visible As Boolean = Convert.ToBoolean(xmlnode(i).ChildNodes.Item(4).InnerText.Trim())
dgv.Columns(columnName).Width = width
dgv.Columns(columnName).HeaderText = headertext
dgv.Columns(columnName).DisplayIndex = displayindex
dgv.Columns(columnName).Visible = visible
Next
fs.Close()
End If
End Sub
Now what I need is that a Function or Sub for the Itemmenu. If a Particular column is Visible in the datagridview then the particular Itemmenu should be checked else it would be unchecked. I need this function when Itemmenu is being displayed / opened.
what I tried just (for sample) in Itemmenu opening is like
Private Sub ColumnsToolStripMenuItem_DropDownOpening(sender As Object, e As EventArgs) Handles ColumnsToolStripMenuItem.DropDownOpening
If DGV_CompList.Columns("DGC_Est").Visible = True Then
Dim CMSN_ToolName = MS_CV_Est.Name
Dim unused As ToolStripMenuItem = New ToolStripMenuItem(CMSN_ToolName) With {
.Checked = True
}
End If
End Sub
DGV_CompList -> DataGridView
DGC_Est -> Column Name of datagridview
MS_CV_Est -> - ToolStripMenuItem which need to checked
(Note: I will be changing the MenuItem Name to Match Datagrid Column name for Sync)
But the ToolStripMenuItem is not getting checked.
Actually I need function / Sub where I will be able to pass the grid name and the Menuname and loop through the grid columns and check if the column is visible or not if the particular column is visible then I need to check that item in the itemmenu.
I am requesting for the sub / function because it can be used for any toolstripmenuitem in any form.
Thanks and Regards.
As per #Jimi's hint, assigned each required Menuitem's Tag property with the datagridview column name and created the below sub / function :
Public Sub Fun_ToolStripMenuItem_Check(ByVal dgv As DataGridView, ByVal TS_Menu_Items As ToolStripItemCollection)
For Each item As ToolStripMenuItem In TS_Menu_Items.OfType(Of ToolStripMenuItem)
If Not item.Tag = "" Then
If dgv.Columns(item.Tag).Visible = True Then
item.Checked = True
Else
item.Checked = False
End If
End If
For Each submenu_item As ToolStripMenuItem In item.DropDownItems.OfType(Of ToolStripMenuItem)
If Not submenu_item.Tag = "" Then
If dgv.Columns(submenu_item.Tag).Visible = True Then
submenu_item.Checked = True
Else
submenu_item.Checked = False
End If
End If
Next
Next
End Sub
Note in the loop used - " OfType(Of ToolStripMenuItem) " because I have ToolStripSeparator between the Itemmenus.
On mouse over called the Sub by :
Private Sub MS_ColumnVisible_DropDownOpening(sender As Object, e As EventArgs) Handles MS_ColumnVisible.DropDownOpening
Fun_ToolStripMenuItem_Check(DGV_CompList, MS_CompDGV.Items)
End Sub
'DGV_CompList' - Datagridview Name and 'MS_CompDGV' - ContextMenuStrip Name
More important is that I did not assign any value to the Tag property to Menuitems which are not used show or hide the datagridview columns.

How to create my own columns with DataGridView.AutoGenerateColumns = False

Using Windows Forms. Following example from DataGridView.AutoGenerateColumns Property. I'm trying to figure out how to add custom columns with AutoGenerateColumns = False.
Private Sub Form_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
SetBinding3()
End Sub
Private Class Employee
Public Property Name() As String
Get
Return _Name
End Get
Set(ByVal value As String)
_Name = value
End Set
End Property
Private _Name As String
End Class
Private employees As New List(Of Employee)
Dim bs As New BindingSource
Private Sub SetBinding3()
employees.Add(New Employee With {.Name = "Henry"})
employees.Add(New Employee With {.Name = "Mary"})
dgBilling.AutoGenerateColumns = False
bs.DataSource = employees
dgBilling.DataSource = bs
Dim col2 As New DataGridViewTextBoxColumn
col2.HeaderText = "Name"
col2.Name = "Name"
col2.ValueType = GetType(String)
col2.DataPropertyName = "Name"
col2.Width = 500
col2.DefaultCellStyle.ForeColor = Color.Black
col2.DefaultCellStyle.BackColor = Color.Beige
dgBilling.Columns.Add(col2)
dgBilling.Refresh()
End Sub
It all seems to work fine except I don't see the data in the DataGridView. If I click on it the names become selected and visible. But if I don't select then it's not visible. I tried setting the ForeColor and BackColor to no avail. How do I properly add a column with AutoGenerateColumns = False?
There's probably an easier way, but I've had success with this. Note that I have custom classes being bound to the DataGridView with List(Of T) and not a BindingSource. The string columns is a pairing of the object's property name and the column's header text. i.e. "Name" is Product.Name and "Product #" is what is shown in the DataGridView column header.
dgvItemList.AutoGenerateColumns = False
dgvItemList.DataSource = Services.MasterLists.Products.GetList
Dim columns As String() = {"ID", "ID",
"Name", "Product #",
"Description", "Description",
"Family", "Family",
"Comments", "Comments"}
Helpers.Controls.AddColumnsToDataGridView(dgvItemList, columns)
dgvItemList.Columns(0).Visible = False
dgvItemList.Columns(1).Width = 90
dgvItemList.Columns(2).Width = 200
dgvItemList.Columns(3).Width = 100
dgvItemList.Columns(4).Width = 200
And the definition of Helpers.Controls.AddColumnsToDataGridView:
Public Shared Sub AddColumnsToDataGridView(ByRef dgv As DataGridView, ByVal columns As String())
dgv.Columns.Clear()
For i As Integer = 0 To columns.Length - 1 Step 2
Dim column As DataGridViewColumn = New DataGridViewTextBoxColumn()
' i = index of the object's property name. i + 1 = the column name to show in the grid
column.DataPropertyName = columns(i)
column.Name = columns(i + 1)
dgv.Columns.Add(column)
Next
End Sub
The reason I do it this way is because I don't want the DataGridView to show all Properties of the Product object, just the fields I want shown.
Turns out I had an error in the RowPrePaint handler. It was referencing a column that didn't exist. As a result of the error the rows were not being rendered.
Private Sub dgv_RowPrePaint(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewRowPrePaintEventArgs) Handles dgv.RowPrePaint
dgv.Rows(e.RowIndex).DefaultCellStyle.BackColor = dgv.Rows(e.RowIndex).Cells("RowColor").Value
End Sub
Once I fixed the error, everything showed fine!