How To Refresh GridlookupEdit datasource with another datasource - vb.net

here I will explain what I want to ask.
before I explain what is my problem.
I created a program and I add 1 forms, in my form I add combobox and components of deexpress is GridLookupEdit.
the function of the combobox itself which displays options category.
while Gridlookup will display data from the database based on the category selected from Combobox.
At the time I chose the first category, namely 'Study Program' from the combobox, then the data is successfully performing in GridLookup.
But When I choose a category of the Other, which is the data based Category I choose the existing data in the database, but it can not show all GridLookup.
And Gridlookup only list items that previously emptied and menyisahkan Name column of the previous data.
for code that I use and I Attach screenshot on the following page:
Private Sub CBCariOpt_SelectedIndexChanged(sender As Object, e As EventArgs) Handles CBCariOpt.SelectedIndexChanged
With CBCariOpt
If .SelectedIndex = -1 Or .Text = "" Then Exit Sub
If .SelectedIndex = 0 Or .SelectedIndex = 1 Or .SelectedIndex = 4 Or .SelectedIndex = 5 Then
GLOptCari.Properties.DataSource = DBNull.Value
FilDataBantuGridLookup(.SelectedIndex, GLOptCari, DKritria, General.Constant.ConfigPath)
Else
GLOptCari.SendToBack()
With DKritria
.BringToFront()
.Text = ""
.Focus()
.Select()
End With
End If
End With
End Sub
Code For FillData into Data source Gridlookup
Private Sub FilDataBantuGridLookup(SelIndex As Integer, GL As GridLookUpEdit, dkritria As MetroTextBox, path As String)
With QD
If SelIndex = 0 Then .FillToGridLook(QProdi, GL, "PRODI", "KODE", path)
If SelIndex = 1 Then .FillToGridLook(QJursan, GL, "JURUSAN", "KODE", path)
If SelIndex = 4 Then .FillToGridLook(QThAnktan, GL, "THN_ANGKATAN", "THN_ANGKATAN", path)
If SelIndex = 5 Then .FillToGridLook(QThWisda, GL, "THN_WISUDA", "THN_WISUDA", path)
.Dispose()
GL.Properties.PopupFormSize = New Size With {.Width = GL.Width} 'GridLookUpEdit
GL.BringToFront()
dkritria.SendToBack()
End With
End Sub
Query To DataSource In GridLookupEdit :
Public Sub FillToGridLook(ByVal query As String, FilCtl As DevExpress.XtraEditors.GridLookUpEdit, SDis As String, SVal As String, ByVal path As String)
Using KN As New Koneksi.Koneksi
If KN.OpenFromFile(path) Then
Using da As New MySqlDataAdapter(query, KN.OKoneksi)
Dim dt As New DataTable
dt = New DataTable
dt.Rows.Clear()
dt.Columns.Clear()
da.Fill(dt)
If dt.Rows.Count > 0 Then
FilCtl.Refresh()
FilCtl.RefreshEditValue()
With FilCtl.Properties
.DataSource = Nothing
.DataSource = dt
.DisplayMember = SDis
.ValueMember = SVal
.ImmediatePopup = True
.View.OptionsView.ShowColumnHeaders = False
End With
FilCtl.Refresh()
End If
da.Dispose()
dt.Dispose()
End Using
End If
KN.Dispose()
End Using
End Sub
For Result :
Screen Shoot
therefore, I would like to ask,:
How to refresh the DataSource in gridlookup who initially had to accommodate the initial data prior to the new data?

Related

How to freeze merged columns in data grid view when scrolling vertically?

I have a data grid view where I need the columns to be frozen or fixed when scrolling vertically.
I have a data grid view control in vb.net windows application which displays the data in a parent-child hierarchy(as shown below). The first column displays the parent data and the second column displays all its child data. The child data in the second column can be as much as 100 rows or even more. So when scrolling down through the grid, the value in the first column does not remain there as it is while the values in the second column(i.e. the child data) scrolls down. So if the user wants to check to which parent, the current child info belongs to, then again he will have to scroll up to the starting of the column to find the name of the parent. I want the values in the first column to be displayed or frozen till it reaches the end of the list of its child values in the grid or at least till the next row where the next parent data starts. I have suggested the client to go with a tree view but they are not agreeing and need it in a data grid view itself. Is there anyway to achieve this in a data grid view?
Thanks in advance.
You can't freeze a row (in runtime, on dgv scrolling) with index greater than zero because all those before are frozen and at that point you can't scroll your datagridview.
If I understood correctly what you want I wrote this class quickly (probably should be optimized). Usage is simple.
1 - First create your own datagridview.
2 - then add your columns and rows (IMPORTANT: Put a “X” in the Tag in each row is a Parent or is considered as title for other rows as you seen in TestPopulate method) .
3 - Call the class I made by passing the datagridview (you created first) as a parameter. At this point this control takes its size, placement and REPLACE YOUR DATAGRIDVIEW .
Private Class CustomDgv
Inherits Panel
Dim WithEvents TopDgv As DataGridView = New DataGridView
Dim WithEvents DownDgv As DataGridView = New DataGridView
Dim Cols As Integer
' This variable is in case you have more rows as "headrow"
' In TestPopulate you can see how to get those
Dim listOfOwnerRows As List(Of Integer) = New List(Of Integer)
Dim currentTopRow As Integer = -1
Protected Overloads Property Height As Integer
Get
Return MyBase.Height
End Get
Set(value As Integer)
MyBase.Height = value
TopDgv.Height = TopDgv.RowTemplate.Height - 1
DownDgv.Height = value - TopDgv.Height - 1
End Set
End Property
Protected Overloads Property Width As Integer
Get
Return MyBase.Width
End Get
Set(value As Integer)
MyBase.Width = value
TopDgv.Width = value - 1
DownDgv.Width = value - 1
End Set
End Property
Sub New(dgvOriginal As DataGridView)
DownDgv = dgvOriginal
Dim parentCtrl As Control = dgvOriginal.Parent
parentCtrl.Controls.Remove(dgvOriginal)
parentCtrl.Controls.Add(Me)
Me.Location = DownDgv.Location
Me.Size = DownDgv.Size
Me.BorderStyle = DownDgv.BorderStyle
TopDgv.Width = Width - 2 - SystemInformation.VerticalScrollBarWidth
TopDgv.Height = TopDgv.RowTemplate.Height
TopDgv.ScrollBars = ScrollBars.None
TopDgv.ColumnHeadersVisible = False
TopDgv.BorderStyle = BorderStyle.None
DownDgv.ColumnHeadersVisible = False
DownDgv.BorderStyle = BorderStyle.None
TopDgv.Left = 0
DownDgv.Left = 0
DownDgv.Width = Width - 2
DownDgv.Height = Height - 2
For Each Col As DataGridViewColumn In DownDgv.Columns
Dim cIndex As Integer = TopDgv.Columns.Add(Col.Clone)
If Col.Frozen Then
TopDgv.Columns(cIndex).Frozen = True
End If
Cols += 1
Next
DownDgv.Top = 0
Me.Controls.Add(TopDgv)
Me.Controls.Add(DownDgv)
If DownDgv.Rows.Count > 0 Then
listOfOwnerRows = (From R As DataGridViewRow In DownDgv.Rows
Where R.Tag = "X"
Select R.Index).ToList
If listOfOwnerRows.Count > 0 Then
SetFrosenRow(listOfOwnerRows(0))
End If
End If
End Sub
Protected Sub SetFrosenRow(index As Integer)
If DownDgv.Rows.Count > index Then
TopDgv.Rows.Clear()
TopDgv.Rows.Add()
Dim currentRIndex As Integer = DownDgv.FirstDisplayedScrollingRowIndex
'If you want onlly the base row
For i As Integer = 0 To Cols - 1
TopDgv.Rows(0).Cells(i).Value = DownDgv.Rows(index).Cells(i).Value
Next
'Or else get the diplayed on top row
TopDgv.Rows(0).DefaultCellStyle = New DataGridViewCellStyle With {
.BackColor = Color.Bisque
}
currentTopRow = index
End If
End Sub
Protected Sub SetChildValuesInTopRow(index As Integer)
For i As Integer = 1 To Cols - 1
TopDgv.Rows(0).Cells(i).Value = DownDgv.Rows(index).Cells(i).Value
Next
End Sub
Private Sub DownDgv_Scroll(sender As Object, e As ScrollEventArgs) Handles DownDgv.Scroll
Try
If e.ScrollOrientation = ScrollOrientation.VerticalScroll Then
Dim topR As Integer = DownDgv.FirstDisplayedScrollingRowIndex
'If you want in top row the current value that is in the top uncomment this
SetChildValuesInTopRow(topR)
If listOfOwnerRows.Count > 0 Then
Dim rToSetAsOwner As Integer = listOfOwnerRows(listOfOwnerRows.Count - 1)
For i As Integer = listOfOwnerRows.Count - 1 To 0 Step -1
If listOfOwnerRows(i) <= topR Then
rToSetAsOwner = listOfOwnerRows(i)
Exit For
End If
Next
If rToSetAsOwner <> currentTopRow Then
SetFrosenRow(rToSetAsOwner)
End If
Console.WriteLine("rToSetAsOwner: " & rToSetAsOwner)
End If
Else
TopDgv.HorizontalScrollingOffset = DownDgv.HorizontalScrollingOffset
End If
Catch ex As Exception
Console.WriteLine(ex.ToString)
End Try
End Sub
End Class
Usage:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Try
' first populate you grid putting a tag in each row which is a header/parent/title for other rows
TestPopulate()
Dim customControl As Control = New CustomDgv(DataGridView1)
Catch ex As Exception
Console.WriteLine(ex.ToString)
End Try
End Sub
Sub TestPopulate()
For i As Integer = 0 To 100
DataGridView1.Rows.Add()
If i = 0 Then
DataGridView1.Rows.Item(0).Cells(0).Value = "Owner 0"
DataGridView1.Rows(0).Tag = "X"
End If
If i = 50 Then
DataGridView1.Rows.Item(50).Cells(0).Value = "Owner 50"
DataGridView1.Rows(50).Tag = "X"
End If
If i = 70 Then
DataGridView1.Rows.Item(70).Cells(0).Value = "Owner 70"
DataGridView1.Rows(70).Tag = "X"
End If
DataGridView1.Rows.Item(i).Cells(1).Value = "child_" & i.ToString & "_1"
DataGridView1.Rows.Item(i).Cells(2).Value = "child_" & i.ToString & "_2"
Next
End Sub
I hope I have been helpful

VB.NET Concurrency Exception with DataGridView

I have encountered a with my datagridview, I bound it to a datatable and am calling and the handler on row is as follows
Dim cmdBuild As New SqlCommandBuilder(sda)
sda.Update(dgv.DataSource)
sda is SqlDataAdapter
so the problem is it works fine, any changes are updated to the database, but the concurrency exception pops up and I understand it is due to updating a ghost row in the database, now what I don't know is how do I go about it, how do I prevent it from happening. spent a lot of time on this one thing so any help that will sort me out will be HIGHLY appreciated. Thanks in advance.
P.S. If more code is needed I'll post it.
Edit
Public Sub editUsers(ByRef mainPanel As CustomTabControl, ByRef dt As System.Data.DataTable)
For i As Integer = 0 To mainPanel.TabCount - 1
If mainPanel.TabPages(i).Text = "Excel users" Then
mainPanel.SelectedTab = mainPanel.TabPages(i)
Exit Sub
End If
Next
Dim tb As New TabPage("Excel users")
Dim datagrid As New DataGridView
datagrid.AllowUserToAddRows = False
dt.Rows.Add(dt.NewRow)
datagrid.DataSource = dt
AddHandler datagrid.RowLeave, AddressOf Home.dataUpdate
tb.Controls.Add(datagrid)
datagrid.Dock = DockStyle.Fill
mainPanel.TabPages.Add(tb)
mainPanel.SelectedTab = tb
datagrid.Columns("ID").Visible = False
datagrid.Columns("Username").AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
datagrid.Columns("Username").FillWeight = 45
datagrid.Columns("Password").AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
datagrid.Columns("Password").FillWeight = 45
End Sub
Public Sub dataUpdate()
Dim dgv As DataGridView = Nothing
For i As Integer = 0 To mainPanel.TabCount - 1
If mainPanel.TabPages(i).Text = "Excel users" Then
dgv = mainPanel.TabPages(i).Controls(0)
End If
Next
If IsNothing(dgv) Then
Exit Sub
End If
For i As Integer = dgv.DataSource.Rows.Count - 1 To 0 Step -1
If dgv.DataSource.Rows(i).RowState = DataRowState.Deleted Then
Continue For
End If
If IsDBNull(dgv.DataSource.Rows(i).Item(1)) Or IsDBNull(dgv.DataSource.Rows(i).Item(2)) Then
Exit Sub
End If
Next
Dim cmdBuild As New SqlCommandBuilder(sda)
sda.Update(dgv.DataSource)
dgv.DataSource.Rows.Add(dtt.NewRow)
End Sub
The code above is all the code that is involved with datagridview control

iterating through a dataset and apply values from 5 of 7 columns to textboxs in an active report

I want to iterate through a dataSet and apply each value to a textbox in an active report. i dont know if these text boxes need to be a the Group/Header area or what. i know that my code below is only retrieving the first row. how can I iterate through all rows and apply the data to text boxes that active reports manages to get multiple rows in the group section
Private Sub rptUserCellPhoneSwap_ReportStart(sender As Object, e As System.EventArgs) Handles Me.ReportStart
Me.PageSettings.Orientation = GrapeCity.ActiveReports.Document.Section.PageOrientation.Landscape
DateTxt.Text = Now.ToShortDateString & " " & Now.ToShortTimeString
Dim DataSet = GrabInformation(FirstName, LastName)
UserTxt.Text = LastName + ", " + FirstName
'For Each dr As DataRow In DataSet.Tables(0).Rows
' OldIMEITxt.Text = DataSet.Tables(0).Rows(dr("OldIMEI")).ToString
' NewIMEITxt.Text = DataSet.Tables(0).Rows(dr("NewIMEI")).ToString
' ReasonTxt.Text = DataSet.Tables(0).Rows(dr("SwapReason")).ToString
' DateRepTxt.Text = DataSet.Tables(0).Rows(dr("DateSwapped")).ToString
' ValueTxt.Text = DataSet.Tables(0).Rows(dr("EstimatedAccumulatedValue")).ToString
'Next
If Not IsNothing(DataSet) Then
If DataSet.Tables(0).Rows.Count > 0 Then
OldIMEITxt.Text = DataSet.Tables(0).Rows(0)("OldIMEI")
NewIMEITxt.Text = DataSet.Tables(0).Rows(0)("NewIMEI")
ReasonTxt.Text = DataSet.Tables(0).Rows(0)("SwapReason")
DateRepTxt.Text = DataSet.Tables(0).Rows(0)("DateSwapped")
ValueTxt.Text = DataSet.Tables(0).Rows(0)("EstimateAccumulatedValue")
End If
End If
End Sub
ActiveReports can read data from your DataSet without additional loops in code.
if you return the data table to DataSource property of report object, then it is enough to set DataField property of TextBox controls and GroupHeader section correctly to show data in report. the rendering engine will go through all data rows automatically:
Private Sub SectionReport1_ReportStart(sender As Object, e As EventArgs) Handles MyBase.ReportStart
' bind TextBox controls to fields in table
Me.txtF1.DataField = "F1"
Me.txtF2.DataField = "F2"
' set the grouping field
Me.GroupHeader1.DataField = "F2"
' set the report data source
Me.DataSource = GetSampleData().Tables(0)
End Sub
Private Function GetSampleData() As DataSet
Dim ds = New DataSet()
Dim dt = ds.Tables.Add("TestData")
dt.Columns.Add("F1")
dt.Columns.Add("F2")
dt.Rows.Add("1", "0")
dt.Rows.Add("2", "0")
dt.Rows.Add("1", "1")
dt.Rows.Add("2", "1")
Return ds
End Function
if you prefer to read data row by row in "semi-automatic mode", then FetchData event handler can help here:
Dim i As Integer
Dim dt As DataTable = Nothing
Private Sub SectionReport2_ReportStart(sender As Object, e As EventArgs) Handles MyBase.ReportStart
i = 0
' bind TextBox controls to fields in table
Me.txtF1.DataField = "F1"
Me.txtF2.DataField = "F2"
' set the grouping field
Me.GroupHeader1.DataField = "F2"
dt = GetSampleData().Tables(0)
End Sub
Private Sub SectionReport2_DataInitialize(sender As Object, e As EventArgs) Handles MyBase.DataInitialize
Me.Fields.Add("F1")
Me.Fields.Add("F2")
End Sub
Private Sub SectionReport2_FetchData(sender As Object, eArgs As FetchEventArgs) Handles MyBase.FetchData
If dt.Rows.Count > i Then
Me.Fields("F1").Value = dt.Rows(i)(0)
Me.Fields("F2").Value = dt.Rows(i)(1)
eArgs.EOF = False
Else
eArgs.EOF = True
End If
i = i + 1
End Sub
Private Function GetSampleData() As DataSet
Dim ds = New DataSet()
Dim _dt = ds.Tables.Add("TestData")
_dt.Columns.Add("F1")
_dt.Columns.Add("F2")
_dt.Rows.Add("1", "0")
_dt.Rows.Add("2", "0")
_dt.Rows.Add("3", "1")
_dt.Rows.Add("4", "1")
Return ds
End Function
also, i would recommend to look at the sample with run time data binding in the ActiveReports installation package.
here is a link to the sample description on the official site:
Unbound Data
This is how you would loop through to get all rows, but in this example the only data that will be left in the textboxes will be the last row.
if you want to concatenate each rows information in the specified text box then you should have the information like this.
OldIMEITxt.Text = OldIMEITxt.Text & dr("OldIMEI")
Loop Code
For each dr as Datarow in DataSet.Tables(0).Rows
OldIMEITxt.Text = dr("OldIMEI")
NewIMEITxt.Text = dr("NewIMEI")
ReasonTxt.Text = dr("SwapReason")
DateRepTxt.Text = dr("DateSwapped")
ValueTxt.Text = dr("EstimateAccumulatedValue")
Next

SortCompare Event not Firing for DataGridView Dynamically Added to TabControl

Use of the datatable as a source of data for the datagridview works very nicely, however, I can't get the Dgv_SortCompare event to be fired when a column is being sorted. The main issue is that numeric values in (purely) numeric columns columns in the dgv values are being sorted as text, where e.g. 1211.6 is smaller than 89.7
In Button1:
Dim datagridview1 As New DataGridView
datagridview1.AutoSize = True
datagridview1.AutoResizeRows()
datagridview1.AutoResizeColumns()
datagridview1.ClearSelection()
DoubleBuffered(datagridview1, True)
datagridview1.AutoResizeRowHeadersWidth(DataGridViewRowHeadersWidthSizeMode.AutoSizeToAllHeaders)
Dim dgvColumnHeaderStyle As New DataGridViewCellStyle()
dgvColumnHeaderStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
datagridview1.ColumnHeadersDefaultCellStyle = dgvColumnHeaderStyle
datagridview1.AllowUserToAddRows = False
datagridview1.ScrollBars = ScrollBars.Both
datagridview1.Dock = DockStyle.Fill
datagridview1.Refresh()
datagridview1.VirtualMode = False
Dim dgv As New DGVCREATE(datagridview1, dataarray, columnheaders, rowheaders, InputFeatureNames, InputObjectNames)
TabControl2.SelectedTab = TabControl2.TabPages.Item(0)
TabControl2.TabPages(0).Controls.Clear()
AddHandler() datagridview1.SortCompare, AddressOf dgv_SortCompare
TabControl2.TabPages(0).Controls.Add(datagridview1)
AddHandler CType(TabControl2.TabPages(0).Controls(0), DataGridView).SortCompare, AddressOf Me.dgv_SortCompare
Public Class DGVCREATE
Sub New(ByRef dgv As DataGridView, ByVal dataarray(,) As Object, ByVal columnheaders() As String, ByVal rowheaders() As String, ByVal FieldNames() As String, ByVal RowNames() As String)
dgv.RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.DisableResizing
' Create the output table.
GetResultsTable(rxdataarray, columnheaders, rowheaders, FieldNames, RowNames)
'new trial code
dgv.AutoGenerateColumns = False
dgv.DataSource = Form1.MainDataTable
dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill
'Set Column FillWeight in very large DGVs in order to prevent exception related to “Sum of FillWeight exceeds 65000”
For i As Integer = 0 To Form1.MainDataTable.Columns.Count - 1
Dim Column As New DataGridViewTextBoxColumn
Column.Name = Form1.MainDataTable.Columns(i).ColumnName
Column.DataPropertyName = Form1.MainDataTable.Columns(i).ColumnName
Column.HeaderText = Form1.MainDataTable.Columns(i).ColumnName
Column.FillWeight = 20
Column.MinimumWidth = 20
dgv.Columns.Add(Column)
Next i
For i As Integer = 0 To dgv.Columns.Count - 1
If InputFeatureType(i + 1) = 1 Then
dgv.Columns(i).ValueType = GetType(Int64)
dgv.Columns(i).SortMode = DataGridViewColumnSortMode.Automatic
End If
If InputFeatureType(i + 1) = 2 Then
dgv.Columns(i).ValueType = GetType(Double)
dgv.Columns(i).SortMode = DataGridViewColumnSortMode.Automatic
End If
If InputFeatureType(i + 1) = 3 Then
dgv.Columns(i).ValueType = GetType(Double)
dgv.Columns(i).SortMode = DataGridViewColumnSortMode.Automatic
End If
If InputFeatureType(i + 1) = 4 Then
dgv.Columns(i).ValueType = GetType(String)
dgv.Columns(i).SortMode = DataGridViewColumnSortMode.Automatic
End If
Next i
dgv.AutoSize = True
End Sub
Public Sub GetResultsTable(ByVal dataarray(,) As Object, ByVal columnheaders() As String, ByVal rowheaders() As String, ByVal fieldnames() As String, ByVal rownames() As String)
Form1.MainDataTable.Clear()
Form1.MainDataTable.Rows.Clear()
Form1.MainDataTable.Columns.Clear()
' Loop through all process names.
For j As Integer = 0 To UBound(columnheaders) - 1
' The current process name.
' Add the program name to our columns.
Form1.MainDataTable.Columns.Add(fieldnames(j + 1))
'Form1.MainDataTable.Columns.Add(fieldnames(j + 1))
' Keep adding rows until we have enough.
Do While Form1.MainDataTable.Rows.Count < UBound(rowheaders)
Form1.MainDataTable.Rows.Add()
Loop
' Add each item to the cells in the column.
For i As Integer = 0 To UBound(rowheaders) - 1
Form1.MainDataTable.Rows(i)(j) = dataarray(i, j)
Next i
Next j
End Sub
End Class
Any suggestions?
You have a lot of unneeded code there. First, you want to avoid boxing your data As Object because it prevents things like the DGV from seeing what type it is. A DataTable is perfectly capable of storing the typed data. If you have to, you could create a class to act like a DTO:
Public Class DataItem
Public Property Able As Int64
Public Property Baker As Double
Public Property Charlie As Double
Public Property Delta As String
End Class
Since a DataTable can hold multiple types, use it in place of DataArray and avoid the data hopscotching its way there:
Private dtData As DataTable
...
' create it somewhere
dtData = New DataTable
dtData.Columns.Add("Able", GetType(Int64))
dtData.Columns.Add("Baker", GetType(Double))
dtData.Columns.Add("Chanrlie", GetType(Double)) ' aka Charlie
dtData.Columns.Add("Delta", GetType(String))
Fill it with data:
For n As Int32 = 0 To 999
dr = dtData.NewRow
dr(0) = RNG.Next() ' random value
dr(1) = RNG.NextDouble() ' random 0.00 to 1.0
dr(2) = RNG.Next(-19, 20) + RNG.NextDouble() ' -19.xx to +19.xx
dr(3) = RD.GetNames(2, 35) ' 2 random names
' add new row:
dtData.Rows.Add(dr)
Next
When it is filled, let the DGV create the columns for you - it will read the data types etc from the source:
dgv1.DataSource = dtData
For Each dc As DataGridViewColumn In dgv1.Columns
Console.WriteLine(dc.ValueType)
Next
The DGV sees the types and will sort any of them correctly:
System.Int64
System.Double
System.Double
System.String

Mutually Exclusive Comboboxes?

So, I've got 4 Comboboxes. They all share the same list of options, lets say Apple, Banana, Carrot, Dill.
However, once the user selects one, that selection should not be available in the other comboboxes. E.g. if they pick Apple in Combobox1, then the only selections available in Combobox2, 3, and 4 should be Banana, Carrot, and Dill.
Is there a good way to do this? I originally thought to link the boxes datasource to a list containing the options, but they need separate datasources for separate choices.
Radio and checkboxes aren't really an option in the actual program, as the list of options is much larger than 4. A Combobox seems to be the best way to represent the user's available choices here.
Here's a fairly simple way to do what you are asking. This code assumes that the datasource uses an integer value to keep track of the items. If you are using the string value itself {Apple, Banana, etc.} as the id, the code will need to be amended slightly. If you need further help let me know but hopefully this gets you on the right track.
You can test this by creating a new blank form (Form1) and drop 4 combo boxes onto it (ComboBox1, ComboBox2, ComboBox3, ComboBox4). Copy/paste this code over top of the form code and run to see how it works:
Public Class Form1
Dim oComboBoxArray(3) As ComboBox
Dim bPreventSelectedChange As Boolean = False
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' Set up an array with the combo box references to reduce code size by using loops later
oComboBoxArray(0) = Me.ComboBox1
oComboBoxArray(1) = Me.ComboBox2
oComboBoxArray(2) = Me.ComboBox3
oComboBoxArray(3) = Me.ComboBox4
' Populate all four combo boxes with the same datasource to start
For Each oCbo In oComboBoxArray
PopulateDataSource(oCbo)
Next
End Sub
Private Sub PopulateDataSource(oCombo As ComboBox, Optional nIdToSelect As Integer = 0)
bPreventSelectedChange = True ' Prevent code in ComboBox_SelectedIndexChanged from executing while we change the datasource
' Using manually populated datatable as datasource because it's quick and easy to use
Dim dt As New DataTable
Dim dr As DataRow
dt.Columns.Add("ID", GetType(Int32))
dt.Columns.Add("Name", GetType(String))
' Need to have some kind of "Please select an item:" in the list or else we will be unable to clear an already selected combo box
dr = dt.NewRow
dr("ID") = 0
dr("Name") = "Select..."
dt.Rows.Add(dr)
' If you are populating from a database or other dynamic source you will only have one of these 'if' statements within a loop
If CheckSkipItem(oCombo, 1) = False Then
dr = dt.NewRow
dr("ID") = 1
dr("Name") = "Apple"
dt.Rows.Add(dr)
End If
If CheckSkipItem(oCombo, 2) = False Then
dr = dt.NewRow
dr("ID") = 2
dr("Name") = "Banana"
dt.Rows.Add(dr)
End If
If CheckSkipItem(oCombo, 3) = False Then
dr = dt.NewRow
dr("ID") = 3
dr("Name") = "Carrot"
dt.Rows.Add(dr)
End If
If CheckSkipItem(oCombo, 4) = False Then
dr = dt.NewRow
dr("ID") = 4
dr("Name") = "Dill"
dt.Rows.Add(dr)
End If
oCombo.DataSource = dt
oCombo.DisplayMember = "Name"
oCombo.ValueMember = "ID"
oCombo.SelectedValue = nIdToSelect ' Set value to either a) the "Select..." item or b) the item that was selected previously depending on the situation
bPreventSelectedChange = False ' Allow code in ComboBox_SelectedIndexChanged to be executed by user again
End Sub
Private Function CheckSkipItem(oCombo As ComboBox, nID As Integer) As Boolean
Dim bSkip As Boolean = False
' Loop through all combo boxes and see if this id has already been chosen in another combo box
For Each oCbo In oComboBoxArray
If oCbo IsNot oCombo AndAlso oCbo.SelectedValue = nID Then
' It has been chosen already so it is not valid for the current combo box that we are checking
bSkip = True
Exit For
End If
Next
Return bSkip
End Function
Private Sub ComboBox_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBox1.SelectedIndexChanged, ComboBox2.SelectedIndexChanged, ComboBox3.SelectedIndexChanged, ComboBox4.SelectedIndexChanged
' Jump out of this event if the bPreventSelectedChange boolean is set to true (ie. if the combo box is being repopulated)
If bPreventSelectedChange = False Then
' A value was chosen by the user. Reset all other combo box datasources and remove the recently selected value
For Each oCbo As ComboBox In oComboBoxArray
If sender IsNot oCbo Then
PopulateDataSource(oCbo, If(oCbo.SelectedValue = Nothing, 0, oCbo.SelectedValue))
End If
Next
End If
End Sub
End Class