问题
.NET 3.5 Winforms
I have a datagridview bound to a datatable at RUNTIME. There are three columns. The third column is the only editable one. Sometimes the value is free text, sometimes the value is a selection from a combobox, or at least that is the design.
After I bind the data table to the Datagridview with this code:
With dgvColumnFilters
.DataSource = _dtFilter
.AllowUserToAddRows = False
.AllowUserToDeleteRows = False
.Columns(0).Visible = False
.Columns(0).ReadOnly = True
.Columns(1).ReadOnly = True
.Columns(1).Width = 170
.Columns(1).HeaderCell.Value = "Field"
.Columns(2).Width = 300
.Columns(2).HeaderCell.Value = "Filter List to Value"
I then proceed to iterate the rows of the dgv. If the row requires a combobox, I run code like this:
Select Case sOvrType
Case "NVARCHAR"
' do nothing. The default is a textbox.
Case "YESNO" ' an override type to say that I need to ask YES, NO or show ALL Values
Dim sTest As String = ""
If Not IsDBNull(dgvColumnFilters(2, i).Value) Then
sTest = CStr(dgvColumnFilters(2, i).Value)
Else
sTest = "*"
End If
dgvColumnFilters(2, i) = New DataGridViewComboBoxCell
CType(dgvColumnFilters(2, i), DataGridViewComboBoxCell).DataSource = YesNoDataTable()
CType(dgvColumnFilters(2, i), DataGridViewComboBoxCell).DisplayMember = "display"
CType(dgvColumnFilters(2, i), DataGridViewComboBoxCell).ValueMember = "value"
CType(dgvColumnFilters(2, i), DataGridViewComboBoxCell).Value = sTest
I still get a textbox even though when I step through the above code, the cell shows as a DataGridViewComboBoxCell, the selection value works, and the code throws no errors.
I am completely confused. Can anyone help me get past this? As I said some rows must be text boxes and others drop-down-list comboboxes.
What am I missing?
Thanks, John.
回答1:
I'd skip binding the CBO column to a datatable, especially a trivial one like Yes, No, All. That seems to confuse it a little. This "converts" col 3 for every other row to a CBO:
For n As Integer = 0 To 9
If n Mod 2 = 0 Then
' make a cell object
Dim cboCell As New DataGridViewComboBoxCell()
cboCell.DataSource = dtYN
cboCell.DisplayMember = "display"
cboCell.ValueMember = "value"
' change the dgv after all the props are set
dgv.Rows(n).Cells(2) = cboCell
End If
Next
When bound to a datasource the default value only showed in the first one for some reason. Its also possible adding it to the DGV before all the properties were set was part of the problem. Once it is added to the control it is able to start raising events.
Test code:
Dim dt As New DataTable()
dt.Columns.Add("Descr")
dt.Columns.Add("Foo")
dt.Columns.Add("Bar")
Dim dtYN As New DataTable ' fake domain
dtYN.Columns.Add("display")
dtYN.Columns.Add("value")
dtYN.Rows.Add("Yes", -1)
dtYN.Rows.Add("No", 0)
dtYN.Rows.Add("All", 1)
For j As Integer = 0 To 9
dt.Rows.Add("this is row ", j.ToString, "")
Next
dgv.DataSource = dt
dgv.Columns(0).Width = 200
' the code above goes here
It doesnt really "convert" a column, it adds components. This went from 3 DGV components to 8. If you had a lot of rows it could get pretty heavy.
A simpler way to code and portray this is to use a List(Of myFilter)
, populate a CBO with the filter objects displaying the filter name/description. When they pick one, look at cboFilter.SelectedItem.FilterType
to know whether to enable a text box or another CBO for the domain list. If the latter, populate it based on a query against cboFilter.SelectedItem.DomainTable
.
If the filter is not for immediate use, but being defined for later use, just save it to a new List(Of FilterDef)
which likely inherits from myFilter
or a common base class.
回答2:
OK, I figured this out and feel that I should share the complete answer to make the question legible.
First, I defined the columns of the grid explicitly:
With dgvColumnFilters
.AutoGenerateColumns = False
Dim dgvc As DataGridViewColumn = Nothing
dgvc = New DataGridViewColumn()
dgvc.Name = "sFilterCol"
dgvc.Visible = False
dgvc.Width = 0
dgvc.ReadOnly = True
dgvc.CellTemplate = New DataGridViewTextBoxCell()
dgvc.DataPropertyName = "sFilterCol"
.Columns.Add(dgvc)
dgvc = New DataGridViewColumn()
dgvc.Name = "sFilterName"
dgvc.Width = 170
dgvc.HeaderCell.Value = "Field"
dgvc.CellTemplate = New DataGridViewTextBoxCell()
dgvc.ReadOnly = True
dgvc.Visible = True
dgvc.DataPropertyName = "sFilterName"
.Columns.Add(dgvc)
dgvc = New DataGridViewColumn()
dgvc.Name = "sFilterValue"
dgvc.Width = 300
dgvc.HeaderCell.Value = "SearchValue"
dgvc.CellTemplate = New DataGridViewTextBoxCell()
dgvc.Visible = True
dgvc.ReadOnly = False
dgvc.DataPropertyName = "sFilterValue"
.Columns.Add(dgvc)
.DataSource = _dtFilter
.AllowUserToAddRows = False
.AllowUserToDeleteRows = False
End With
This set for me a fixed width for each column. Eliminating automatic column generation was a big help.
Then for each row, I examined the filter value type and generated the proper control for the value depending on the type, as follows:
With dgvColumnFilters
Dim sOvrType As String = ""
For i As Integer = 0 To .Rows.Count - 1
Dim _sCol As String = .Rows(i).Cells(0).Value.ToString.Trim.ToUpper
For j As Integer = 0 To _sFilterCol.Count - 1
If _sFilterCol(j).ToUpper.Trim = _sCol.ToUpper.Trim Then
sOvrType = _sFilterOvrType(j)
Exit For
End If
Next
Select Case sOvrType
Case "NVARCHAR"
' do nothing. The default is a textbox.
Case "YESNO" ' This is a combobox that lets the user choose YES or NO, and returns "Y" or "N" depending on choice.
Dim sTest As String = ""
If Not IsDBNull(dgvColumnFilters(2, i).Value) Then
sTest = CStr(dgvColumnFilters(2, i).Value)
Else
sTest = "*"
End If
dgvColumnFilters.Rows(i).Cells(2).Value = sTest
Dim dgvcb As New DataGridViewComboBoxCell
dgvcb.AutoComplete = True
dgvcb.DataSource = YesNoDataTable()
dgvcb.DisplayMember = "display"
dgvcb.ValueMember = "value"
dgvColumnFilters.Rows(i).Cells(2) = dgvcb
Case "LOOKUPCOMBO" ' looks up from a dataset generated from a SQL Query.
Dim sDisplayMember As String = ""
Dim sValueMember As String = ""
Dim sLookupTable As String = ""
Dim ds As DataSet = LookupComboDataSet(_sFilterCol(i), sLookupTable, sDisplayMember, sValueMember, sErr)
If ds Is Nothing Then
Throw New Exception("Cannot make lookup combo, error = " & sErr)
Exit Sub
End If
Dim sTest As String = ""
If Not IsDBNull(dgvColumnFilters(2, i).Value) Then
sTest = CStr(dgvColumnFilters(2, i).Value)
Else
sTest = "-1"
End If
dgvColumnFilters(2, i) = New DataGridViewComboBoxCell()
CType(dgvColumnFilters(2, i), DataGridViewComboBoxCell).AutoComplete = True
CType(dgvColumnFilters(2, i), DataGridViewComboBoxCell).DataSource = ds.Tables(0)
CType(dgvColumnFilters(2, i), DataGridViewComboBoxCell).DisplayMember = sDisplayMember
CType(dgvColumnFilters(2, i), DataGridViewComboBoxCell).ValueMember = sValueMember
CType(dgvColumnFilters(2, i), DataGridViewComboBoxCell).Value = sTest
End Select
Next
.Columns(0).Visible = False
End With
This scenario in the end generated the correct outputs.
It is vitally important to set the DisplayMember and ValueMember properties after DataSource property.
Thanks for the help!
John.
来源:https://stackoverflow.com/questions/23712774/datagridviewcomboboxcell-for-just-one-cell