質問

I have a datagridview with two textbox columns and one combobox column. The combobox's DataSource is bound to enums for the values of the dropdown. The datagridview's DataSource is bound to a custom class with datatypes of string, string and enum.

The first two columns are pre-populated with values and in the third column the user must select a value from the dropdown. All this is working excellent so far except....

The combobox field should be a one-to-one kind of mapping, meaning no two comboboxes should have the same value. I am really not sure how to implement this kind of behavior. Should the chosen value be removed from the remaining dropdowns? should the chosen value remain in the dropdown and just give an error when two of the same are selected?

Any ideas and how to implement these ideas will be of great help.

Thanks

note the only acceptable value that can be used more than once is 'None'

enter image description here

役に立ちましたか?

解決 2

I decided to use the CellValidating event of the DataGridView to check whether or not the same value is selected more than once. If it is then a error message is displayed in the row header column.

The combobox in error will not lose focus until the error is resolved.

Private Sub DataGridView1_CellValidating(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellValidatingEventArgs) Handles DataGridView1.CellValidating

    Dim headerText As String = DataGridView1.Columns(e.ColumnIndex).HeaderText

    'Abort validation if cell is not in the Mapping column. 
    If Not headerText.Equals("Column Mapping") Then Return

    'Clear error on current row.
    DataGridView1.Rows(e.RowIndex).ErrorText = Nothing
    e.Cancel = False

    Dim newMappingValue As XmlElement = DirectCast([Enum].Parse(GetType(XmlElement), e.FormattedValue), XmlElement)

    ' Abort validation if cell value equal XmlElement.None 
    If newMappingValue.Equals(XmlElement.None) Then Return

    For Each dgvRow As DataGridViewRow In DataGridView1.Rows
        Dim currentMappingValue As XmlElement = dgvRow.Cells.Item(headerText).Value

        If dgvRow.Index <> e.RowIndex Then
            If currentMappingValue.Equals(newMappingValue) Then
                DataGridView1.Rows(e.RowIndex).ErrorText = "Value already selected, please select a different value."
                e.Cancel = True
            End If
        End If
    Next

End Sub

enter image description here

他のヒント

I have an idea based off your intent to possibly remove the chosen value from the remaining dropdowns.

I have a class called Runner which has a similar setup to your example.

Public Class Runner

    Public Property FirstName As String
    Public Property LastName As String
    Public Property Placement As Result

    Public Sub New(fn As String, ln As String)
        FirstName = fn
        LastName = ln
        Placement = Result.None
    End Sub

End Class

I also have an enum called Result which will get populated into the ComboBoxCell:

Public Enum Result
    None = 0
    Bronze = 1
    Silver = 2
    Gold = 3
End Enum

When I create data objects and bind them to the DataGridView, I do so like this (note - Placements is a global List(Of Result) and Runners is a global List(Of Runner):

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Placements.Add(Result.None)
    Placements.Add(Result.Bronze)
    Placements.Add(Result.Silver)
    Placements.Add(Result.Gold)
    Runners.Add(New Runner("John", "Smith"))
    Runners.Add(New Runner("Jane", "Doe"))
    Runners.Add(New Runner("Bill", "Jones"))
    Column1.DataPropertyName = "FirstName"
    Column2.DataPropertyName = "LastName"
    Column3.DataPropertyName = "Placement"
    Column3.DataSource = Placements
    DataGridView1.DataSource = Runners
End Sub

Now, whenever the value of a cell in the ComboBoxColumn changes, you remove the new value from the list that contains the enums:

Private Sub DataGridView1_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellValueChanged
    If (e.RowIndex > -1 And e.ColumnIndex = 2) Then
        Dim currentvalue As Result = DataGridView1.Rows(e.RowIndex).Cells(e.ColumnIndex).Value
        If currentvalue <> Result.None Then
            Placements.Remove(currentvalue)
        End If
    End If
End Sub

Be careful when changing drop down selections... as per this Discussion, you have to trick the DataGridView into committing values as soon as the ComboBox value changes. I used an answer from that discussion and did something like this:

Private Sub DataGridView1_CurrentCellDirtyStateChanged(sender As Object, e As EventArgs) Handles DataGridView1.CurrentCellDirtyStateChanged
    Dim col As DataGridViewColumn = DataGridView1.Columns(DataGridView1.CurrentCell.ColumnIndex)
    If col.Name = "Column3" Then
        DataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit)
        Dim selected As DataGridViewCell = DataGridView1.CurrentCell
        DataGridView1.CurrentCell = Nothing //This line and the next one simply hide
        DataGridView1.CurrentCell = selected //an odd display effect that occurs 
                                             //because we remove a value and change the
                                             //selection at the same time
    End If
End Sub

Finally, you want to handle the DataError event for the DataGridView and leave it blank so that you don't get Exceptions thrown at you when removing values from your list of enums.

This gets you about 90% of the way there. It will not re-add items to the list when you change a value. For example if I change from Gold to Silver, Gold should be added back to the list. You can probably figure out what events to handle to get the old value and the new one, and insert the old value back into the list at the correct index based on its enum value.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top