DataGridViewComboBoxColumn
控件可能很难使用。我已经花了两天时间来与该代码的各种排列作斗争,所以我决定投入工作并寻求帮助。
最新的怪异形式是ComboBox
事件处理程序,该事件处理程序为单个用户操作触发的次数越来越多。奇怪的是,增长率是前一个计数的精确翻倍(即1, 2, 4, 8, 16, 32, 64
等)
首先,我将解释我要完成的工作并阐明一些术语。
我有一个Dictionary(Of Integer, String)
。根据我的域规则,我将其Key
属性称为 Channel ,并将其Value
属性称为 Label 。我正在将每个KeyValuePair
映射到名为 Target 的第三个String
值。 Dictionary(Of Integer, String)
项是固定的,它们作为用户的视觉辅助而存在,因此他可以轻松地从List(Of String)
中选择 Target 。
我已经决定使用DataGridView
控件来提供此功能。我正在使用三列,就像这样:
请注意,已映射的 Target 列表项以几乎不可见的颜色显示,以防止用户尝试再次使用它们。 (当选择了已映射的 Target 映射到其他 Label 时,事件处理程序就会出现问题。)
我将在下面包含完整的代码库,但是为了快速浏览,这里是重复的事件处理程序:
Private Sub ComboBox_SelectionChangeCommitted(Sender As ComboBox, e As EventArgs)
' '
' Look for other labels that have already been mapped to this target '
' '
If Me.OtherTargetCells.Any(Function(Cell) Cell.FormattedValue = Sender.Text) Then
If Me.IsInteractiveChange Then
MsgBox("Target [] is already mapped to Label []. If you want to map Target [] to Label [], you must first set Label [] to [Not mapped].", MsgBoxStyle.Exclamation, Me.DataGridView.FindForm.Text)
Me.IsInteractiveChange = False
Sender.SelectedIndex = 0
Me.IsInteractiveChange = True
End If
End If
End Sub
这是我将其全部连接的方式:
Public Sub New()
Task.Run(Sub()
Dim oHandler As DataGridViewEditingControlShowingEventHandler
While Me.DataGridView Is Nothing
End While
oHandler = New DataGridViewEditingControlShowingEventHandler(AddressOf DataGridView_EditingControlShowing)
RemoveHandler Me.DataGridView.EditingControlShowing, oHandler
AddHandler Me.DataGridView.EditingControlShowing, oHandler
End Sub)
End Sub
Private Sub DataGridView_EditingControlShowing(Sender As DataGridView, e As DataGridViewEditingControlShowingEventArgs)
Dim oComboBox As ComboBox
If TypeOf e.Control Is ComboBox Then
oComboBox = e.Control
oComboBox.DrawMode = DrawMode.OwnerDrawFixed
RemoveHandler oComboBox.DrawItem, New DrawItemEventHandler(AddressOf ComboBox_DrawItem)
AddHandler oComboBox.DrawItem, New DrawItemEventHandler(AddressOf ComboBox_DrawItem)
RemoveHandler oComboBox.SelectionChangeCommitted, New EventHandler(AddressOf ComboBox_SelectionChangeCommitted)
AddHandler oComboBox.SelectionChangeCommitted, New EventHandler(AddressOf ComboBox_SelectionChangeCommitted)
End If
End Sub
当我从与以前不同的列表中选择一个已映射的 Target 时,重复计数会成倍增加(例如,从 SCC 中选择两次不会增加计数,但是选择从 SCC 开始,然后从 Scale 开始。)
为此,我尝试了很多很多可能的解决方案-太多了,在这里列出,其中大多数我不记得了-但没有一个成功。
对于每次选择更改,我该如何限制处理程序仅触发一次?
Mapping.TargetsColumn.vb
Namespace Mapping
Public Class TargetsColumn
Inherits DataGridViewComboBoxColumn
Public Sub New()
Task.Run(Sub()
Dim oHandler As DataGridViewEditingControlShowingEventHandler
While Me.DataGridView Is Nothing
End While
oHandler = New DataGridViewEditingControlShowingEventHandler(AddressOf DataGridView_EditingControlShowing)
RemoveHandler Me.DataGridView.EditingControlShowing, oHandler
AddHandler Me.DataGridView.EditingControlShowing, oHandler
End Sub)
End Sub
Private Sub DataGridView_EditingControlShowing(Sender As DataGridView, e As DataGridViewEditingControlShowingEventArgs)
Dim oComboBox As ComboBox
If TypeOf e.Control Is ComboBox Then
oComboBox = e.Control
oComboBox.DrawMode = DrawMode.OwnerDrawFixed
RemoveHandler oComboBox.DrawItem, New DrawItemEventHandler(AddressOf ComboBox_DrawItem)
AddHandler oComboBox.DrawItem, New DrawItemEventHandler(AddressOf ComboBox_DrawItem)
RemoveHandler oComboBox.SelectionChangeCommitted, New EventHandler(AddressOf ComboBox_SelectionChangeCommitted)
AddHandler oComboBox.SelectionChangeCommitted, New EventHandler(AddressOf ComboBox_SelectionChangeCommitted)
End If
End Sub
Private Sub ComboBox_DrawItem(Sender As ComboBox, e As DrawItemEventArgs)
Dim sThisTarget As String
Dim oForeColor As Color
Dim _
iSeparatorBottom,
iSeparatorRight,
iSeparatorLeft As Integer
Dim _
oSeparatorStart,
oSeparatorStop As Point
sThisTarget = DirectCast(Me.Items(e.Index), Target).Value
iSeparatorBottom = e.Bounds.Bottom - 2
iSeparatorRight = e.Bounds.Right
iSeparatorLeft = e.Bounds.Left
e.DrawBackground()
If e.Index = 0 Then
oSeparatorStart = New Point(iSeparatorLeft, iSeparatorBottom)
oSeparatorStop = New Point(iSeparatorRight, iSeparatorBottom)
oForeColor = SystemColors.HotTrack
e.Graphics.FillRectangle(SystemBrushes.Control, e.Bounds)
e.Graphics.DrawLine(SystemPens.ControlDark, oSeparatorStart, oSeparatorStop)
Else
If Me.OtherTargets.Contains(sThisTarget) Then
oForeColor = SystemColors.ControlLight
Else
oForeColor = e.ForeColor
End If
End If
Using oBrush As New SolidBrush(oForeColor)
e.Graphics.DrawString(sThisTarget, e.Font, oBrush, e.Bounds)
End Using
If e.State.HasFlag(DrawItemState.Focus) Then e.DrawFocusRectangle()
Me.DataGridView.FindForm.Text = sThisTarget
End Sub
Private Sub ComboBox_SelectionChangeCommitted(Sender As ComboBox, e As EventArgs)
' '
' Look for other labels that have already been mapped to this target '
' '
If Me.OtherTargetCells.Any(Function(Cell) Cell.FormattedValue = Sender.Text) Then
If Me.IsInteractiveChange Then
MsgBox("Target [] is already mapped to Label []. If you want to map Target [] to Label [], you must first set Label [] to [Not mapped].", MsgBoxStyle.Exclamation, Me.DataGridView.FindForm.Text)
Me.IsInteractiveChange = False
Sender.SelectedIndex = 0
Me.IsInteractiveChange = True
End If
End If
End Sub
Private ReadOnly Property OtherTargets As List(Of String)
Get
Return Me.OtherTargetCells.Select(Function(Cell) DirectCast(Cell.FormattedValue, String)).ToList
End Get
End Property
Private ReadOnly Property CurrentTargetCell As DataGridViewCell
Get
Return Me.AllTargetCells(Me.DataGridView.CurrentRow.Index)
End Get
End Property
Private ReadOnly Property AllTargetCells As List(Of DataGridViewCell)
Get
Dim oAllCells As IEnumerable(Of DataGridViewCell)
Dim oRows As IEnumerable(Of DataGridViewRow)
oRows = Me.DataGridView.Rows.Cast(Of DataGridViewRow)
oAllCells = oRows.SelectMany(Function(Row) Row.Cells.Cast(Of DataGridViewCell))
Return oAllCells.Where(Function(Cell) TypeOf Cell Is DataGridViewComboBoxCell).ToList
End Get
End Property
Private ReadOnly Property OtherTargetCells As List(Of DataGridViewCell)
Get
Return Me.AllTargetCells.Where(Function(Cell) Cell.RowIndex <> Me.RowIndex).ToList
End Get
End Property
Private ReadOnly Property RowIndex As Integer
Get
Return Me.DataGridView.CurrentRow.Index
End Get
End Property
Private IsInteractiveChange As Boolean = True
Private ReadOnly ComboBoxes As New Dictionary(Of Integer, ComboBox)
End Class
End Namespace
Form1.vb
Public Class Form1
Inherits Form
Public Sub New()
Dim oColTargets As Mapping.TargetsColumn
Dim oTargets As IEnumerable(Of String)
Dim oQuery As Func(Of Target, Boolean)
Dim sChannel As String
Dim oTarget As Target
Dim oMaps As Dictionary(Of Integer, String)
Dim oMap As Map
Dim _
oColChannels,
oColLabels As DataGridViewTextBoxColumn
Me.InitializeComponent()
Me.Targets.Add(New Target("Not mapped"))
sChannel = String.Empty
oQuery = Function(Target) Target.Value = sChannel
'oTargets = Reader.Client.Create.Call(Function(Service As Reader.IService) Service.GetChannelTargets)'
oTargets = New List(Of String) From {"Scale", "SCC", "CO", "O2"}
oTargets.ToList.ForEach(Sub(Target)
Me.Targets.Add(New Target(Target))
End Sub)
'oMaps = Reader.Client.Create.Call(Function(Service As Reader.IService) Service.GetChannelMaps)'
oMaps = New Dictionary(Of Integer, String) From {{3, "Test"}, {7, "SCC"}, {8, "Scale"}, {9, "CO"}, {10, "O2"}}
oMaps.ToList.ForEach(Sub(Map)
sChannel = Map.Value
If Me.Targets.Any(oQuery) Then
oTarget = Me.Targets.Single(oQuery)
Else
oTarget = Me.Targets.First
End If
oMap = New Map With {
.Channel = Map.Key,
.Label = Map.Value,
.Target = oTarget
}
Me.Maps.Add(oMap)
End Sub)
oColChannels = New DataGridViewTextBoxColumn With {
.DataPropertyName = NameOf(Map.Channel),
.AutoSizeMode = DataGridViewAutoSizeColumnMode.ColumnHeader,
.HeaderText = NameOf(Map.Channel),
.ReadOnly = True,
.Name = NameOf(oColChannels)
}
oColLabels = New DataGridViewTextBoxColumn With {
.DataPropertyName = NameOf(Map.Label),
.AutoSizeMode = DataGridViewAutoSizeColumnMode.ColumnHeader,
.HeaderText = NameOf(Map.Label),
.ReadOnly = True,
.Name = NameOf(oColLabels)
}
oColTargets = New Mapping.TargetsColumn With {
.DataPropertyName = NameOf(Map.Target),
.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
.DisplayMember = NameOf(Target.Value),
.ValueMember = NameOf(Target.Self),
.HeaderText = NameOf(Map.Target),
.DataSource = Me.Targets,
.Name = NameOf(oColTargets)
}
dgvMapping.AutoGenerateColumns = False
dgvMapping.Columns.AddRange({oColChannels, oColLabels, oColTargets})
For Each oColumn As DataGridViewColumn In dgvMapping.Columns
oColumn.HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleCenter
If oColumn.Index = 0 Then
oColumn.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
End If
Next
dgvMapping.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize
dgvMapping.DataSource = New BindingList(Of Map)(Me.Maps)
If dgvMapping.RowCount = 0 Then
dgvMapping.Height = 150
Else
dgvMapping.Height = ((dgvMapping.RowCount + 0) * dgvMapping.Rows(0).Height) + dgvMapping.ColumnHeadersHeight
End If
End Sub
Private Sub Form1_FormClosing(Sender As Form1, e As FormClosingEventArgs) Handles Me.FormClosing
Dim oPolicy As Target = Me.Maps.First.Target
Dim sName As String = Me.Maps.First.Channel
End Sub
Private Sub _dgvMapping_DataError(Sender As DataGridView, e As DataGridViewDataErrorEventArgs) Handles dgvMapping.DataError
MsgBox(e.Exception.Message, MsgBoxStyle.Critical, Me.Text)
End Sub
Private Targets As New BindingList(Of Target)
Private Maps As New List(Of Map)
End Class
Public Class Map
Public Property Channel As Integer
Public Property Label As String
Public Property Target As Target
End Class
Public Class Target
Public Sub New(Target As String)
Me.Value = Target
End Sub
Public ReadOnly Property Self As Target
Get
Return Me
End Get
End Property
Public ReadOnly Property Value As String
End Class
Form1.Designer.vb
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()>
Partial Class Form1
Inherits System.Windows.Forms.Form
'Form overrides dispose to clean up the component list.'
<System.Diagnostics.DebuggerNonUserCode()>
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
Finally
MyBase.Dispose(disposing)
End Try
End Sub
'Required by the Windows Form Designer'
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer'
'It can be modified using the Windows Form Designer.'
'Do not modify it using the code editor.'
<System.Diagnostics.DebuggerStepThrough()>
Private Sub InitializeComponent()
Me.dgvMapping = New System.Windows.Forms.DataGridView()
CType(Me.dgvMapping, System.ComponentModel.ISupportInitialize).BeginInit()
Me.SuspendLayout()
' '
'dgvMapping'
' '
Me.dgvMapping.AllowUserToAddRows = False
Me.dgvMapping.AllowUserToDeleteRows = False
Me.dgvMapping.AllowUserToOrderColumns = True
Me.dgvMapping.AllowUserToResizeColumns = False
Me.dgvMapping.AllowUserToResizeRows = False
Me.dgvMapping.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize
Me.dgvMapping.EditMode = System.Windows.Forms.DataGridViewEditMode.EditOnEnter
Me.dgvMapping.Location = New System.Drawing.Point(12, 12)
Me.dgvMapping.Name = "dgvMapping"
Me.dgvMapping.RowHeadersVisible = False
Me.dgvMapping.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect
Me.dgvMapping.Size = New System.Drawing.Size(250, 150)
Me.dgvMapping.TabIndex = 0
' '
'Form1'
' '
Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New System.Drawing.Size(800, 450)
Me.Controls.Add(Me.dgvMapping)
Me.Font = New System.Drawing.Font("Segoe UI", 8.0!, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, CType(0, Byte))
Me.Name = "Form1"
Me.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen
Me.Text = "Form1"
CType(Me.dgvMapping, System.ComponentModel.ISupportInitialize).EndInit()
Me.ResumeLayout(False)
End Sub
Friend WithEvents dgvMapping As DataGridView
End Class
答案 0 :(得分:1)
固定。
我正在为每个AddHandler
/ RemoveHandler
调用实例化一个新的事件处理程序对象。
当我删除实例化并使用简单表达式时,ComboBox
的行为开始正确。
Public Sub New()
Task.Run(Sub()
While Me.DataGridView Is Nothing
End While
RemoveHandler Me.DataGridView.EditingControlShowing, AddressOf DataGridView_EditingControlShowing
AddHandler Me.DataGridView.EditingControlShowing, AddressOf DataGridView_EditingControlShowing
End Sub)
End Sub
Private Sub DataGridView_EditingControlShowing(Sender As Object, e As DataGridViewEditingControlShowingEventArgs)
Dim oComboBox As ComboBox
If TypeOf e.Control Is ComboBox Then
oComboBox = e.Control
oComboBox.DrawMode = DrawMode.OwnerDrawFixed
RemoveHandler oComboBox.DrawItem, AddressOf ComboBox_DrawItem
AddHandler oComboBox.DrawItem, AddressOf ComboBox_DrawItem
RemoveHandler oComboBox.SelectionChangeCommitted, AddressOf ComboBox_SelectionChangeCommitted
AddHandler oComboBox.SelectionChangeCommitted, AddressOf ComboBox_SelectionChangeCommitted
End If
End Sub
我不得不在事件处理程序方法中将Sender
参数类型放松为Object
,但这并没有带来任何严重的后果。
Private Sub DataGridView_EditingControlShowing(Sender As Object, e As DataGridViewEditingControlShowingEventArgs)
End Sub
Private Sub ComboBox_DrawItem(Sender As Object, e As DrawItemEventArgs)
End Sub
Private Sub ComboBox_SelectionChangeCommitted(Sender As Object, e As EventArgs)
End Sub
有什么用:我通常更喜欢将Sender
参数限制为调用类型,以提高编码效率,但是在这种情况下是不可能的。但是,唯一的影响是需要将Sender
放在一个方法主体中的某个位置:
Dim oQuery = Function(Cell) Cell.FormattedValue = DirectCast(Sender, ComboBox).Text
它现在按预期工作。