有没有一种方法可以强制DataGridView对所有单元格触发它的CellFormatting事件?

16

我们使用CellFormatting事件在应用程序的各个网格中对单元格进行颜色编码。

我们有一些通用代码,可以处理导出到Excel(和打印),但它是以黑白方式进行的。现在我们想要改变这个,并从网格中选择颜色。

这个问题和答案有所帮助(而且它有效)... 除了一个问题,就是超过单个屏幕的较大网格存在问题。尚未显示在屏幕上的网格部分(逻辑上)永远不会触发其CellFormatting代码,因此它们的底层颜色永远不会被设置。结果,在Excel中,颜色编码只能在页面中间停止。

似乎有三种解决方案:

1)告诉用户在执行导出到Excel之前必须滚动到网格的所有部分。哈!不是一个严肃的解决方案

2)在将数据导出到Excel之前,以编程方式滚动到网格的所有部分。仅比(1)略好

3)在我们的导出到Excel代码中,在顶部触发某些东西,告诉DataGridView绘制/格式化其整个区域,例如:

  MyDataGridView.FormatAllCells()

有没有类似这样的东西可以做到这一点???

哦,还有第四个选项,但这将涉及触及大量现有代码:

4)停止使用CellFormatting事件,在加载时格式化单元格。问题在于,我们必须重新调整应用程序中的每个网格,因为自年代以来,CellFormatting是我们所采用的方式。


我有一种不好的感觉,认为选项3可能没有解决方案。我的建议是增加一个选项5:您当前拥有格式逻辑,该逻辑根据单元格值设置颜色和其他样式属性。如果您将该逻辑作为新方法,那么您可以从单元格格式化和Excel代码中调用此方法。这样,您就可以确保Excel和单元格格式始终相同,但不再依赖于单元格格式化已被调用以导出Excel数据。 - David Hall
另一个选项(4的变体)是使用CellValueChanged来进行格式化。 - David Hall
5个回答

9
正如其他答案中所指出的,访问 DataGridViewCell.FormattedValue 确实是强制调用特定单元格的 CellFormatting 事件的简单方法。然而,在我的情况下,这个属性还导致了不良的副作用,包括自动调整列宽。在长时间寻找可行解决方案时,我最终发现了以下完美运行的神奇方法: DataGridView.Invalidate()DataGridView.InvalidateColumn()DataGridView.InvalidateRow()DataGridView.InvalidateCell()

这 4 个方法仅针对指定范围(单元格、列、行或整个表)强制重新调用 CellFormatting 事件,并且也不会引起任何令人讨厌的自动调整大小问题。


6
我有一个可能的解决方案 - 在您的导出函数中访问每个单元格的Cell.FormattedValue属性。根据Microsoft,这将强制触发CellFormatting事件。

1
实际上,那听起来是一个公平的建议。 - hawbsl
1
谢谢。 Dim strValue As String For r As Integer = 0 To dgv.Rows.Count - 1 For c As Integer = 0 To dgv.Columns.Count - 1 strValue = dgv.Rows(r).Cells(c).FormattedValue() Next Next - AjV Jsy
1
https://dev59.com/1Wgv5IYBdhLWcg3wXPou#43778173 - Alex P.

3
假设像@DavidHall建议的那样,没有魔法.FormatAllCells,我们唯一的选择是停止使用CellFormatting。
然而,新问题是,在加载期间应用单元格样式格式似乎没有任何效果。如果你在Google上搜索它,会有很多帖子。他们还指出,如果你将相同的代码放在表单上的按钮下,并在加载后单击它(而不是在加载期间),则代码将起作用...因此,必须在样式应用之前可见网格。大多数关于这个主题的建议都建议使用...鼓声... CellFormatting。哎呀!
最终找到一篇文章建议使用网格的DataBindingComplete事件。这个方法可行。
诚然,这个解决方案是我不想要的选项“4”的一个变体。

2

我曾经遇到过同样的问题,最终得出了与您的解决方案 #4 相似的结果。和您一样,我使用了 DataBindingComplete 事件。但是,由于我使用了扩展方法,因此对现有代码的更改是可以承受的:

internal static class DataGridViewExtention
{
    public static void SetGridBackColorMyStyle(this DataGridView p_dgvToManipulate)
    {
        p_dgvToManipulate.RowPrePaint += p_dgvToManipulate_RowPrePaint;
        p_dgvToManipulate.DataBindingComplete += p_dgvToManipulate_DataBindingComplete;
    }

    // for the first part - Coloring the whole grid I used the `DataGridView.DataBindingComplete` event:
    private static void p_dgvToManipulate_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
    {
        foreach (DataGridViewRow objCurrRow in ((DataGridView)sender).Rows)
        {
            // Get the domain object from row 
            DomainObject objSelectedItem = (DomainObject)objCurrRow.DataBoundItem;

            // if item is valid ....
            if objSelectedItem != null)
            {
                // Change backcolor of row using my method
                objCurrRow.DefaultCellStyle.BackColor = GetColorForMyRow(objSelectedItem);
            }
        }
    }

    // for the second part (disabling the Selected row from effecting the BackColor i've setted myself, i've used `DataGridView.RowPrePaint` event:
    private static void p_dgvToManipulate_RowPrePaint(object sender, DataGridViewRowPrePaintEventArgs e)
    {
        // If current row about to be painted is selected by user
        if (((DataGridView)sender).Rows[e.RowIndex].Selected)
        {
            // Get current grid row
            var objGridRow = ((DataGridView)sender).Rows[e.RowIndex];

            // Get selectedItem
            DomainObject objSelectedItem = (DomainObject)objGridRow.DataBoundItem;

            // if item is valid ....
            if (objSelectedItem != null && objSelectedItem.ObjectId != 0)
            {
                // Set color for row instead of "DefaultCellStyle" (same color as we used at DataBindingComplete event)
                objGridRow.DefaultCellStyle.SelectionBackColor = GetColorForMyRow(objSelectedItem);
            }

            // Since the selected row is no longer unique, we need to let the used to identify it by making the font Bold
            objGridRow.DefaultCellStyle.Font = new Font(((DataGridView)sender).Font.FontFamily, ((DataGridView)sender).Font.Size, FontStyle.Bold);
        }
        // If current row is not selected by user
        else
        {
            // Make sure the Font is not Bold. (for not misleading the user about selected row...)
            ((DataGridView)sender).Rows[e.RowIndex].DefaultCellStyle.Font = new Font(((DataGridView)sender).Font.FontFamily,
                                                                                   ((DataGridView)sender).Font.Size, FontStyle.Regular);
        }
    }
}

1
如果您想要重用Cellformatting事件中提供的格式(例如,字体加粗和背景颜色等单元格样式元素),则可能的解决方案是捕获这些单元格样式。这些单元格样式似乎仅在“cellformatting”和“cellpainting”事件之间可用,而不在datagridview-cell的样式本身中。请使用第二个处理程序在cellformatting事件期间捕获单元格样式,如下所示:
  1. in the exportmodule add a shared list, array or dictionary to store the cellstyles:

    Dim oDataGridFormattingDictionary as  Dictionary(Of String, DataGridViewCellStyle) = nothing
    
  2. initialize the dictionary and add a second handler to the datagridview in your printing or export-code. In vb.net something like this:

     oDataGridFormattingDictionary = New Dictionary(Of String, DataGridViewCellStyle)
     AddHandler MyDatagridviewControl.CellFormatting, AddressOf OnPrintDataGridView_CellFormatting
    
  3. Add the code for the handler

    Private Sub OnPrintDataGridView_CellFormatting(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellFormattingEventArgs)
    If e.RowIndex > -1 AndAlso e.ColumnIndex > -1 AndAlso Not e.CellStyle Is Nothing Then
       If Not oDataGridFormattingDictionary Is Nothing andalso oDataGridFormattingDictionary.ContainsKey(e.RowIndex & "_" & e.ColumnIndex) = False Then
        oDataGridFormattingDictionary.Add(e.RowIndex & "_" & e.ColumnIndex, e.CellStyle)
       End If
    End If
    End Sub
    
  4. Very important: to make sure the original cellformating-event (AND the second cellformatting-handler after that) are actually called you have to request the formattedvalue for each cell that you want to print (e.g.

    oValue = Datagridview.rows(printRowIndex).Cells(printColumnIndex).FormattedValue)
    

    !

  5. When printing you can now check if the cell has formatting. E.g.:

    if not oDataGridFormattingDictionary is nothing andalso oDataGridFormattingDictionary.ContainsKey(printRowIndex & "_" & printColumnIndex) Then
    ... the cellstyle is accesible via:
    oDataGridFormattingDictionary(printRowIndex & "_" & printColumnIndex)
    end if 
    
  6. at the end of the export or printcode remove the handler and set the dictionary to nothing

    RemoveHandler DirectCast(itemToPrint.TheControl, DataGridView).CellFormatting, AddressOf OnPrintDataGridView_CellFormatting
    oDataGridFormattingDictionary = nothing
    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接