Avalonia.Controls.DataGrid自动合并列

Winform在用的DataGridView修改为通过线条和透明的方式实现了合并列的效果。一段时间也在留意Avalonia.Controls.DataGrid是否也有类似的做法。一直没有心思去看那些代码,水平有限,实在搞不懂XML/Content一类如何实现绘制内容的。借着AI的帮助终于能试着去修改了。

让AI从DataGrid的以下方法入手

复制代码
        private void AddNewCellPrivate(DataGridRow row, DataGridColumn column)
        {
            DataGridCell newCell = new DataGridCell();
            PopulateCellContent(
                isCellEdited: false,
                dataGridColumn: column,
                dataGridRow: row,
                dataGridCell: newCell);
            if (row.OwningGrid != null)
            {
                newCell.OwningColumn = column;
                newCell.IsVisible = column.IsVisible;
                if (row.OwningGrid.CellTheme is {} cellTheme)
                {
                    newCell.SetValue(ThemeProperty, cellTheme, BindingPriority.Template);
                }
            }
            row.Cells.Insert(column.Index, newCell);
            
            // 智能AutoMerge逻辑:比较当前行和下一行的数据
            //ApplySmartAutoMerge(row, column, newCell);
        }

最后发现,DataGrid并没有为每行数据创建DataGridRow/DataGridCell,且还会复用这些创建出来的DataGridCell显其他行的数据。就是说上面的方法只是开始的显示内容是可预测的。

多次尝试后,发现绘制前都会调用DataGridCell的EnsureGridLine方法,于是将合并的代码主要放在其中

复制代码
internal void EnsureGridLine(DataGridColumn lastVisibleColumn)
{
    if (OwningGrid != null && _rightGridLine != null)
    {
        if (OwningGrid.VerticalGridLinesBrush != null && OwningGrid.VerticalGridLinesBrush != _rightGridLine.Fill)
        {
            _rightGridLine.Fill = OwningGrid.VerticalGridLinesBrush;
        }
        //忽略其他代码
    
    // 调用DataGrid的CheckCellToMerge方法
    var (isSameAsPrevious, isSameAsNext) = OwningGrid?.CheckCellToMerge(this) ?? (false, false);    
    // 根据不同的组合情况处理底边框和透明度
    HandleMergeResult(isSameAsPrevious, isSameAsNext);
}

至此,DataGrid决定如何合并,返回当前行与上一行和下一行的数据是否可以合并(现在只是简单的比较当前列的内容是否一致)。

有一个小小的问题,在滚动时合并列可能会显示空白。虽说在用的winform版的datagridview也有这个小问题。但新的东西多少有点改进,对吧。于是我向AI发出要求。最大的帮助就是判断是否是当前显示的第一列的方法了。

复制代码
/// <summary>
/// 判断指定行是否为可视区域的第一行
/// </summary>
/// <param name="row">要检查的行</param>
/// <returns>true表示是可视区域的第一行,false表示不是</returns>
public bool IsFirstVisibleRow(DataGridRow row)
{
    if (_rowsPresenter == null || row == null)
        return false;
        
    // 遍历可视区域的所有行,找到索引最小的行
    int firstVisibleRowIndex = int.MaxValue;
    foreach (Control element in _rowsPresenter.Children)
    {
        if (element is DataGridRow visibleRow && visibleRow.IsVisible)
        {
            firstVisibleRowIndex = Math.Min(firstVisibleRowIndex, visibleRow.Index);
        }
    }
    
    // 如果指定行索引等于可视区域最小索引,则为第一行
    return row.Index == firstVisibleRowIndex;
}

人工分析后发现,DataGridCell的EnsureGridLine不知为何只是lastVisibleColumn有调用,并不是行中的全部列。滚动的刷新由DataGridCellsPresenter发起,看以下方法

复制代码
internal void EnsureFillerVisibility()
{
    DataGridFillerColumn fillerColumn = OwningGrid.ColumnsInternal.FillerColumn;
    //忽略其他代码

    // This must be done after the Filler visibility is determined.  This also must be done
    // regardless of whether or not the filler visibility actually changed values because
    // we could scroll in a cell that didn't have EnsureGridLine called yet
    DataGridColumn lastVisibleColumn = OwningGrid.ColumnsInternal.LastVisibleColumn;
    if (lastVisibleColumn != null)
    {
        DataGridCell cell = OwningRow.Cells[lastVisibleColumn.Index];
        cell.EnsureGridLine(lastVisibleColumn);//不知道为什么只是最后一列需要执行。
    }

    // 检查是否为首行显示,如果是则清除合并单元格的透明度
    bool isFirstVisualRow = (OwningGrid?.IsFirstVisibleRow(OwningRow) == true);
    foreach (DataGridCell cell in OwningRow.Cells)
    {
        cell?.ClearMergeOpacity(isFirstVisualRow);//否的时候,可能需要恢复透明度
    }
}

好了,这就是全部修改。代码在gitee

https://gitee.com/kevin2y/Avalonia.Controls.DataGrid/tree/feature/auto-merge-enhancement