none
调用合并DataGridView单元格的类之后,无法释放这个类的实例! RRS feed

  • 问题

  • 前情:
      调用一个自定义的DataGridView单元格合并类,合并功能没有问题,但有一个BUG;

    问题:

      在一个窗体中,有两个按扭,一个DataGridView ,数据是通过按扭切换,加载不同的数据。

        当第一次调用这个合并单元格类建立实例后,在第一个表格中能按要求合并单元格,但是,当按下另一按扭,在同一个DataGridView中加载新的数据时,虽然没有在切换时加入合并单元格动作,但这些合并动作还在,也就是在新的显示表格中,依然还在进行合并。

        查看源码,并没有释放资源取消动作的功能,也不知道怎么实现这功能,请高手指教;

    '·合并单元格类
    '·调用示例:
    '   声明
    '   Private meg As MergeCells
    '
    '   表格加载完成后
    '   meg = New CmbDatagridbiew(Me.DataGridView1)
    '   meg.Add(0, 0, 1, 2)
    '   *   参数说明:([行索引·左上],[列索引·左上],[行索引·右下],[列索引·右下])
    '   *   【0,0】 【0,1】 【0,2】
    '   *   【1,0】 【1,1】 【1,2】 
    '***********************************************************************
    
    Public Class MergeCells
        Implements IDisposable
        Private data As New List(Of MyRect)
        Private Dgv As DataGridView
        Public Sub New(_dgv As DataGridView)
            Me.Dgv = _dgv
            AddHandler _dgv.CellPainting, AddressOf DGV_CellPainting
        End Sub
        Public Sub Add(_rect As MyRect)
            Me.data.Add(_rect)
            Me.SetCellEnabled(_rect)
        End Sub
        Public Sub Add(_top As Integer, _left As Integer, _bottom As Integer, _right As Integer)
            Me.data.Add(New MyRect(_top, _left, _bottom, _right))
            Me.SetCellEnabled(New MyRect(_top, _left, _bottom, _right))
        End Sub
        Private Sub SetCellEnabled(_rect As MyRect)
            For i = _rect.Top To _rect.Bottom
                For j = _rect.Left To _rect.Right
                    Me.Dgv.Rows(i).Cells(j).ReadOnly = True
                Next
            Next
        End Sub
    
        Private Function InRects(rowIndex As Integer, colIndex As Integer) As Integer
            For i = 0 To Me.data.Count - 1
                If Me.data(i).InRect(rowIndex, colIndex) Then Return i
            Next
            Return -1
        End Function
        Private Sub DGV_CellPainting(sender As Object, e As DataGridViewCellPaintingEventArgs)
            Using gridBrush As Brush = New SolidBrush(Me.Dgv.GridColor),
                      backColorBrush As SolidBrush = New SolidBrush(e.CellStyle.BackColor)
                Using gridLinePen = New Pen(gridBrush)
                    If Me.data.Count = 0 Then Return
                    Dim index As Integer = Me.InRects(e.RowIndex, e.ColumnIndex)
                    If index = -1 Then Return
                    e.Graphics.FillRectangle(backColorBrush, e.CellBounds)
                    If e.RowIndex = Me.data(index).Bottom Then e.Graphics.DrawLine(gridLinePen,
                       e.CellBounds.Left, e.CellBounds.Bottom - 1,
                       e.CellBounds.Right - 1, e.CellBounds.Bottom - 1)
                    If e.ColumnIndex = Me.data(index).Right Then e.Graphics.DrawLine(gridLinePen,
                       e.CellBounds.Right - 1, e.CellBounds.Top,
                       e.CellBounds.Right - 1, e.CellBounds.Bottom - 1)
                    e.Handled = True
                    For i = 0 To Me.data.Count - 1
                        Dim rect1 As Rectangle = Me.Dgv.GetCellDisplayRectangle(Me.data(i).Left, Me.data(i).Top, False)
                        Dim rect2 As Rectangle = Me.Dgv.GetCellDisplayRectangle(Me.data(i).Right, Me.data(i).Bottom, False)
                        Dim rect As New Rectangle(rect1.Left, rect1.Top, rect2.Right - rect1.Left, rect2.Bottom - rect1.Top)
                        Dim text As String
                        Try
                            text = Me.Dgv.Rows(Me.data(i).Top).Cells(Me.data(i).Left).Value.ToString().Trim()
                        Catch ex As Exception
                            text = ""
                        End Try
                        Dim sz As Drawing.SizeF = e.Graphics.MeasureString(text, e.CellStyle.Font)
                        e.Graphics.DrawString(text, e.CellStyle.Font, New SolidBrush(e.CellStyle.ForeColor),
                                             rect.Left + (rect.Width - sz.Width) / 2,
                                             rect.Top + (rect.Height - sz.Height) / 2,
                                            StringFormat.GenericDefault)
                    Next
                End Using
            End Using
        End Sub
    End Class
    Public Class MyRect
        Public Top As Integer
        Public Right As Integer
        Public Bottom As Integer
        Public Left As Integer
        Public Sub New(_top As Integer, _left As Integer, _bottom As Integer, _right As Integer)
            Me.Top = _top
            Me.Right = _right
            Me.Bottom = _bottom
            Me.Left = _left
        End Sub
        Public Function InRect(rowIndex As Integer, colIndex As Integer) As Boolean
            If rowIndex >= Me.Top And rowIndex <= Me.Bottom And colIndex >= Me.Left And colIndex <= Me.Right Then
                Return True
            Else
                Return False
            End If
        End Function
    End Class

    Public Class Form2
    
        Private meg As MergeCells
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            ' 初始测试数据 
            DgwA.Rows.Add(1, 2, 3, 4)
            DgwA.Rows.Add(5, 6, 7, 8)
            DgwA.Rows.Add(11, 23, 33, 44)
            DgwA.Rows.Add(6, 6, 6, 6)
        End Sub
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            '' 调用
            meg = New MergeCells(Me.DgwA)
            meg.Add(1, 0, 2, 1) ' 要合并的单元格  
            DgwA.Refresh()
        End Sub
        Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    
            DgwA.RowCount = 0
            DgwA.ColumnCount = 0
    
            Dim dtc(3) As DataGridViewTextBoxColumn
    
            For k = 0 To dtc.Length - 1
                dtc(k) = New DataGridViewTextBoxColumn
                DgwA.Columns.Add(dtc(k))
            Next
    
            DgwA.Rows.Add(9, 9, 8, 9)
            DgwA.Rows.Add(3, 3, 7, 5)
            DgwA.Rows.Add(44, 44, 44, 44)
            DgwA.Rows.Add(77, 77, 77, 77)
    
            DgwA.Refresh()      '问题:并没有要求合并单元格——但合并动作还在,依然进行了合并!
    
        End Sub
    
    End Class

        

    • 已编辑 kllnum 2017年8月30日 12:43
    2017年8月30日 12:42

答案

  • Hi kllnum,

    您的问题是:调用合并DataGridView单元格的类之后,无法释放这个类的实例!

    看一下您的代码,您在创建MergeCells类的实例时有这样一个操作:

    AddHandler _dgv.CellPainting, AddressOf DGV_CellPainting

    那么您每次调用Refresh()方法时DGV_CellPainting()方法都会被调用,因为DGV_CellPainting()方法已经被您放在_dgv的Handler中了,每次绘制_dgv时都会使用DGV_CellPainting()方法,所以即使您“并没有要求合并单元格——但合并动作还在,依然进行了合并!

    要解决这个问题,在不需要合并单元格时要将DGV_CellPainting()去掉,您可以在类MergeCells中添加类似下面的方法,在不希望合并单元格时调用这个方法将DGV_CellPainting()从Handler中移除,然后再调用Refresh()方法,这样就不会继续合并单元格了。

    Public Sub RmvHandler()
            RemoveHandler _dgv.CellPainting, AddressOf DGV_CellPainting
    End Sub

    当然,您还需要添加类似AddHandler方法将DGV_CellPainting()方法添加到Handler中,在希望合并单元格时调用,然后再调用Refresh()方法以实现合并单元格。

    希望我的回答对您有帮助!

    Best Regards,

    Charles He


    2017年9月11日 2:41

全部回复

  • Hi kllnum,

    根据你的描述,你刚开始用datagridview绑定一个数据源, 然后点击Button 1对它进行合并单元格,然后尝试着点击Button 2绑定另一个数据源, 但是想消除之前对datagridview的合并单元格的动作。

    Datagridview的合并单元格主要通过DataGridView_CellPainting事件实现,我到现在还没有找到适合的方法去消除它, 你有尝试过使用两个DataGridview控件吗?我觉得相对于消除DataGridView_CellPainting事件,用两个DataGridView控件会相对简单点。

    Best Regards,

    Cherry


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    2017年8月31日 3:54
    版主
  • 如果数据量小,只需要两个表格可完全全部显示,这当然没问题。可在窗体中设置两个DataGridView,一个隐藏一个显示,互相切换,各自初始化合并单元格类——不用实验,可知这是可行的。

    但凡应用程序需要用DataGridView显示数据,一般不会只有一屏数据,比如我的程序就几十万条数据需要显示,而且数据的种类不一,显示样式各不相同,想想,假设需要用100页(屏)或者更多页来显示,且每一页都有合并不同单元格的需求,用这种方法岂不是至少要用到100个以上的DataGridView?

    所以这思路对大数据显示而言完全没有实用价值。还是得从如何实现实例的销毁这方面来考虑比较妥当!

    2017年8月31日 7:30
  • Hi kllnum,

    为什么要用同一个datagridview加载不同的数据源?

    像Cherry说的,你可以用两个datagridview, 每个datagridview都可以分页显示很多数据,第一个datagridview加载第一个数据源,并合并一些单元格,第二个datagridview加载第二个数据源并且不合并单元格。

    如果不想要用两个datagridview控件,可以尝试在加载第二个数据源的时候将已经合并了单元格的datagridview的表格线画出来,通过GDI+ DrawLine() 方法,当然这种方法属于费力不讨好,不一定可以成功画出来,所以并不推荐。

    Regards,

    Stanly


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    2017年9月1日 8:50
  • 不是你说的这种应用场景!

    与分页无关!——数据是多样的,并不是同一类数据的多条记录,明白不?说白了,每页的数据都是完全不同的的字段。

    在不需要合并单元格的情形下,用同一个datagridview显示,只需在加载数据前将datagridview的行、列置为0,再添加需要行、列,再加载数据就可。

    但在需要合并单元格显示的数据量,同样不只一页,且每页数据同样是完全不同的字段,需要合并的单元格的位置也各页不同,用上面的方法,将datagridview的行、列置为0,加载数据后,合并动作同样存在!数据的量和字段特性,决定了无法用一到二个datagridview来显示数据——这就是问题所在。

    如果只需一屏或者很少的几屏显示,或者要合并的单元格的位置每页一样,那就不是问题了,对不? 



    • 已编辑 kllnum 2017年9月2日 2:45
    2017年9月2日 2:21
  • Hi kllnum,

    我首先自己注册了一个事件给DataGridView1_CellPainting, 在button1.Click的事件中,把这个事件给DataGridView1.CellPainting, 然后再button2.click事件中, 移除这个注册的事件,不要直接从datagridview——属性——事件中直接注册, 像下面这个个样子:

     Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Dim dt As New DataTable
            dt.Columns.Add("Column1", GetType(String))
            dt.Columns.Add("Column2", GetType(String))
            dt.Columns.Add("Column3", GetType(String))
            dt.Columns.Add("Column4", GetType(String))
    
            dt.Rows.Add("中国", "上海", "5000", "7000")
            dt.Rows.Add("中国", "北京", "10000", "12000")
            dt.Rows.Add("美国", "纽约", "6000", "8600")
            dt.Rows.Add("美国", "华盛顿", "7000", "9000")
            DataGridView1.DataSource = dt
            AddHandler DataGridView1.CellPainting, AddressOf DataGridView1_CellPainting
    
        End Sub
    
        Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
            RemoveHandler DataGridView1.CellPainting, AddressOf DataGridView1_CellPainting
            Dim dt As New DataTable
            dt.Columns.Add("Column1", GetType(String))
            dt.Columns.Add("Column2", GetType(String))
            dt.Columns.Add("Column3", GetType(String))
            dt.Columns.Add("Column4", GetType(String))
    
            dt.Rows.Add("中国", "上海", "5000", "7000")
            dt.Rows.Add("中国", "北京", "10000", "12000")
            dt.Rows.Add("美国", "纽约", "6000", "8600")
            dt.Rows.Add("美国", "华盛顿", "7000", "9000")
            DataGridView1.DataSource = dt
        End Sub
    
        Private Sub Form11_Load(sender As Object, e As EventArgs) Handles MyBase.Load
              End Sub
    
        Private Sub DataGridView1_CellPainting(sender As Object, e As DataGridViewCellPaintingEventArgs)
            If e.ColumnIndex = 0 AndAlso e.RowIndex <> -1 Then
                Dim bruch As Brush = New SolidBrush(DataGridView1.GridColor)
                Dim sbruch As New SolidBrush(e.CellStyle.BackColor)
                Using pen As New Pen(bruch)
                    e.Graphics.FillRectangle(sbruch, e.CellBounds)
                    If e.RowIndex < DataGridView1.Rows.Count - 1 AndAlso DataGridView1.Rows(e.RowIndex + 1).Cells(e.ColumnIndex).Value IsNot Nothing AndAlso DataGridView1.Rows(e.RowIndex + 1).Cells(e.ColumnIndex).Value.ToString() <> e.Value.ToString() Then
                        e.Graphics.DrawLine(pen, e.CellBounds.Left, e.CellBounds.Bottom - 1, e.CellBounds.Right, e.CellBounds.Bottom - 1)
                        e.Graphics.DrawLine(pen, e.CellBounds.Right - 1, e.CellBounds.Top, e.CellBounds.Right - 1, e.CellBounds.Bottom)
                    Else
                        e.Graphics.DrawLine(pen, e.CellBounds.Right - 1, e.CellBounds.Top, e.CellBounds.Right - 1, e.CellBounds.Bottom)
                    End If
                    If e.RowIndex = DataGridView1.Rows.Count - 1 Then
                        e.Graphics.DrawLine(pen, e.CellBounds.Left, e.CellBounds.Bottom - 1, e.CellBounds.Right, e.CellBounds.Bottom - 1)
                    End If
                    If e.Value IsNot Nothing Then
                        If Not (e.RowIndex > 0 AndAlso DataGridView1.Rows(e.RowIndex - 1).Cells(e.ColumnIndex).Value.ToString() = e.Value.ToString()) Then
                            e.Graphics.DrawString(e.Value.ToString(), e.CellStyle.Font, Brushes.Black, e.CellBounds.X + 2, e.CellBounds.Y + 5, StringFormat.GenericDefault)
                        End If
                    End If
                    e.Handled = True
                End Using
            End If
        End Sub

    Best Regards,

    Cherry


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    2017年9月5日 9:07
    版主

    Cherry Bu  的 方案有存在两个问题:

    1.只有当相邻的单元格的内容完全一样时才可合并——不能对不同内容的单元格进行合并,这完全不行;

    2.合并也不是真正意义上的合并,只是重绘格线而已——就是说,不能将合并后的单元格当作一个单元格进行赋值或者设置样式。

    就合并的功能而言,这个完全不如我给出的合并类的功能。也没有解决我说的问题。



    • 已编辑 kllnum 2017年9月9日 12:02
    2017年9月9日 12:00
  • Hi kllnum,

    您的问题是:调用合并DataGridView单元格的类之后,无法释放这个类的实例!

    看一下您的代码,您在创建MergeCells类的实例时有这样一个操作:

    AddHandler _dgv.CellPainting, AddressOf DGV_CellPainting

    那么您每次调用Refresh()方法时DGV_CellPainting()方法都会被调用,因为DGV_CellPainting()方法已经被您放在_dgv的Handler中了,每次绘制_dgv时都会使用DGV_CellPainting()方法,所以即使您“并没有要求合并单元格——但合并动作还在,依然进行了合并!

    要解决这个问题,在不需要合并单元格时要将DGV_CellPainting()去掉,您可以在类MergeCells中添加类似下面的方法,在不希望合并单元格时调用这个方法将DGV_CellPainting()从Handler中移除,然后再调用Refresh()方法,这样就不会继续合并单元格了。

    Public Sub RmvHandler()
            RemoveHandler _dgv.CellPainting, AddressOf DGV_CellPainting
    End Sub

    当然,您还需要添加类似AddHandler方法将DGV_CellPainting()方法添加到Handler中,在希望合并单元格时调用,然后再调用Refresh()方法以实现合并单元格。

    希望我的回答对您有帮助!

    Best Regards,

    Charles He


    2017年9月11日 2:41
  • 感谢大神 Charles He,问题解决。

    按照Charles He先生的提示,在合并类中添加了两个方法:

       Public Sub Add_Handler()
            If SHD = 0 Then
                AddHandler Dgv.CellPainting, AddressOf DGV_CellPainting
                SHD = 1
            End If
        End Sub       '添加绘制Handler   ——在需要合并时调用
        Public Sub Rmv_Handler()
            If SHD > 0 Then
                RemoveHandler Dgv.CellPainting, AddressOf DGV_CellPainting
                SHD = 0
            End If
        End Sub      '移除绘制Handler__在不需要合并时调用

    原代码进行了4处改进:

    1、初始化时,不要添加   AddHandler _dgv.CellPainting, AddressOf DGV_CellPainting ;

    2、添加上述两个方法;

    3、SHD为计数器,反复调用时先判断是否已添加 Handler——如果不判断,反复调用合并时会出现重复添加,这样在需要移除时,必须有与添加的次数相同数操作才可移除(如添加10次,必须移除10次)!

    4、 调用有相应的调整(同一DataGridView): 

     Private meg As MergeCells =   New MergeCells(Dgv)

    1)、合并调用    ' button1 

        If meg IsNot Nothing Then      '必须重新初始化,否则,如果新数据的列与之前的列不同,合并会有问题
               meg.Rmv_Handler()        '移除合并动作
                meg = Nothing
        End If

            meg  =   New MergeCells(Dgv)    ‘重新初始化

            meg.Add_Handler()      ' 加入合并Handler
            meg.Add(0, 0, 2, 2)      ' 要合并的单元格 

    2)、非合并调用   ' button2 

        If meg IsNot Nothing Then      '必须重新初始化,否则,如果新数据的列与之前的列不同,合并会有问题
               meg.Rmv_Handler()        '移除合并动作
               meg = Nothing
        End If 

           addDate()                     ' 同一DataGridView装载新数据

    ——再次感谢 Charles He 先生的无私帮助!


    • 已编辑 kllnum 2017年9月14日 5:46
    2017年9月14日 3:54