0
votes

.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.

2
you cant assign a new column to a dgv like this dgvColumnFilters(2, i) = New DataGridViewComboBoxCell they need to be added to the DGV columns collection and then that column is there for all rows, not just the one you are on. - Ňɏssa Pøngjǣrdenlarp
Ok, then how do I have some rows that where column x is a combobox and some that are text boxes? If I assign at the column level I am hamstrung. Also the columns are automatically generated when I bind the datatable to the dgv. - John Tamburo
what is in this column? it sounds like the DB schema is flawed if a column is sometimes restricted to a domain and sometimes not. The only thing that comes close to what you describe is to add the missing column and make one or the other visible depending on the row they are on. The problem there is that sometimes they are not "on" any row. - Ňɏssa Pøngjǣrdenlarp
It is not being bound to a database table - just a data table used to get information for filtering a database table that is shown in a different dgv. The columns are COLUMN_TO_FILTER, DISPLAY_NAME, VALUE_TO_FILTER_ON. If the column to be filtered is a foreign key we do a lookup to the FK Table to get the value for filtering. Are you saying that we have no control over the control type at the cell level and only at the column level? that seems like a crushing limitation. - John Tamburo
What you have sounds similar to many search mechs: in a myMP3 lib, you might want to search for music belonging to a certain category defined in the DB (pick from CBO), but you might also want to find a song by a key word (text string in the title, keyword list or related album name). The point is that those are 2 different inputs and related actions (what they will search is different); so the way the Datatable is built seems flawed. - Ňɏssa Pøngjǣrdenlarp

2 Answers

0
votes

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.

0
votes

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.