【问题标题】:Why is this event handler method being repeatedly called?为什么这个事件处理方法被重复调用?
【发布时间】:2019-01-26 04:38:32
【问题描述】:

DataGridViewComboBoxColumn 控件可能难以使用。两天来我一直在与这段代码的各种排列作斗争,所以我决定认输并寻求帮助。

奇怪的最新化身是ComboBox 事件处理程序,它为单个用户操作触发越来越多的次数。奇怪的是,增长率是之前计数的两倍(即1, 2, 4, 8, 16, 32, 64 等)

首先,我将解释我要完成的工作并澄清一些术语。

我有一个Dictionary(Of Integer, String)。在我的域规则中,我将其 Key 属性称为 Channel 并将其 Value 属性称为 Label。我将每个KeyValuePair 映射到第三个String 值,称为TargetDictionary(Of Integer, String) 项目是固定的——它们作为用户的视觉辅助存在,因此他可以轻松地从 List(Of String) 中选择 目标

我已经决定使用DataGridView 控件来提供此功能。我正在使用三列,如下所示:

请注意,已经映射的目标列表项以几​​乎不可见的颜色显示,以阻止用户再次尝试使用它们。 (这就是事件处理程序问题的来源——当一个已经映射的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

【问题讨论】:

    标签: vb.net winforms datagridview datagridviewcombobox


    【解决方案1】:

    固定。

    我正在为每个 AddHandler/RemoveHandler 调用实例化一个新的事件处理程序对象。

    当我删除实例化并改用简单表达式时,ComboBoxes 开始正常运行。

    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
    

    现在可以正常工作了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-09-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多