1

首先:一旦我完成,我将自己提供这个问题的答案。
但是你可以在我的路上帮助我,我会感谢你的所有建议。

我有一个 DataGridView 不同的行属于一起。我的问题是找到一种合适的方式来显示和使用这些连接的行。

我的第一个想法是让每一行保持独立,但这有一些缺点:

  • 如何清楚地表明行属于一起?
    我可以添加另一列,其单元格对连接的行显示相同的数字,但这不容易看到并且需要另一列。
    我在这里的解决方案是所有连接的行都具有相同的背景颜色,并且颜色会在每组连接行的白色和浅灰色之间变化。

  • 如何使用连接的行?一旦我选择了一组中的一行,我就必须通过提取信息(保存在单元格的标签或隐藏单元格中)来分析这一行,哪些行属于一起并选择它们。在 DataGridView 中向上/向下移动行的工作更大:我还必须分析相邻的行集以查看我必须移动多远。

因此,我决定创建一个DataGridViewMultiRow.
完成后,我将在此处发布该课程的完整代码作为答案。

它将继承自DataGridViewRow("DGVR") 并存储单个 DGVR 或其他多行的列表,并通过使用自己的代码绘制行的单元格将它们显示为一个。但是,我仍然需要找出为此目的使用哪些事件。MSDN 建议使用DataGridView.RowPrePaint,但我更愿意使用绑定到 DGVR 本身的事件。或许我会分析一下DataGridViewRow.Paint()的源码,自己写个方法……

当添加到多行时,单行将变得不可见(它们可以通过滥用概念再次切换为可见,但 .net 本身有很多内容无法防止滥用;也许我什至不切换到不可见,所以这是用户的责任)。
通过强制每个 DGVR 成为与多行相同的 DGV 的一部分,可以简单地避免多行中的递归,并且因为每行只能添加到一个 DGV 并且只能添加一次,所以我不必检查递归了。

现在我正在努力如何实现内部行列表。我正在考虑使用 .net DataGridViewRowCollection,但我发现它的操作与自身紧密相关DataGridView:一个 DGV 只能有一个 DGVRColl,但每个 DGVRColl 指的是一个 DGV。所以在我的每个 DGVMultiRow 中都会有半连接的 DGVRColl。
我想问这是否会导致问题,但我已经发现在实例化 DGVRColl 时我必须提供一个 DGV,而在调用 DGVMultiRow ctor 时我没有这个 DGV。此外,当使用 DGVRColl 并为其提供公共 get 属性时,我只能挂钩到 'CollectionChanged' 事件并且无法控制单个操作,例如Add()and Remove()。所以我将使用一个简单的私人列表。

继续#1
我完成了主要工作,看起来已经很不错了:

在此处输入图像描述

我仍然需要修复细节,比如在移动滚动条时正确放置文本和其他小东西。

我决定不覆盖,DataGridViewRow.Paint()因为那个有太多的内部连接。所以我首先尝试使用CellPaintingDGV 的事件,这是一个好的开始。但是我需要同时拥有该行的所有单元格的信息,所以我继续DataGridView.RowPrePaint()按照 MSDN 的建议覆盖,请参见上面的链接。这真的很好用。

未完待续。

4

1 回答 1

0

在各种缺点之后,我终于创建了一个解决方案。
它是用 C++/CLI 编写的,因此你们中的大多数人必须对其进行调整以在 C# 中使用。
该解决方案包含一些不属于该解决方案的用户函数,但其​​用途应该很容易通过其名称猜出。

这是一个预览: 在此处输入图像描述

#pragma once

using namespace System;
using namespace System::Collections::Generic;
using namespace System::Drawing;
using namespace System::Windows::Forms;
using namespace System::ComponentModel;

public ref class CDataGridViewMultiRow : DataGridViewRow
{
public:
  //----------------------------------------------------------------------------
  // constructor
  //----------------------------------------------------------------------------
  CDataGridViewMultiRow ();
  CDataGridViewMultiRow (bool i_bHideRows);
  CDataGridViewMultiRow (bool i_bHideRows, ::DataGridView^ i_dgv);

  //----------------------------------------------------------------------------
  // Clone
  //----------------------------------------------------------------------------
  virtual Object^ Clone () override;

  //----------------------------------------------------------------------------
  // Clear
  //----------------------------------------------------------------------------
  void Clear ();

  //----------------------------------------------------------------------------
  // Add, Insert
  //----------------------------------------------------------------------------
  bool Add (DataGridViewRow^ i_dgvr);
  bool Insert (int i_ixRow, DataGridViewRow^ i_dgvr);

  //----------------------------------------------------------------------------
  // Remove
  //----------------------------------------------------------------------------
  bool Remove (int i_ixRow);
  bool Remove (DataGridViewRow^ i_dgvr);

  //----------------------------------------------------------------------------
  // Update
  //----------------------------------------------------------------------------
  void Update ();

  //----------------------------------------------------------------------------
  // PaintRow
  //
  // description: manually paints the row.
  //
  // !!! IMPORTANT NOTICE: !!!
  // This method must be attached to the DataGridView's RowPrePaint event.
  //----------------------------------------------------------------------------
  static void PaintRow (Object^ sender, DataGridViewRowPrePaintEventArgs^ e);

  //----------------------------------------------------------------------------
  // properties
  //----------------------------------------------------------------------------
  property DataGridViewRow^   Rows[int] { DataGridViewRow^  get (int i_ixRow);
                                          void              set (int i_ixRow, DataGridViewRow^ i_dgvr); }
  property int                RowCount  { int   get() { return m_listdgvr->Count;} }
  property bool               HideRows  { bool  get() { return m_bHideRows;}
                                          void  set(bool i_bHideRows); }

public:

protected:
  List<DataGridViewRow^>^     m_listdgvr;
  bool                        m_bHideRows;

private:

protected:
  virtual void OnDataGridViewChanged () override;

private:
  void CommonConstructor (bool            i_bHideRows,
                          ::DataGridView^ i_dgv);
};

#include "CDataGridViewMultiRow.h"

using namespace Schmoll_SwCore;

//----------------------------------------------------------------------------
// constructor
//----------------------------------------------------------------------------
CDataGridViewMultiRow::CDataGridViewMultiRow () : DataGridViewRow ()
{
  CommonConstructor (false, nullptr);
}

//----------------------------------------------------------------------------
CDataGridViewMultiRow::CDataGridViewMultiRow (bool i_bHideRows) : DataGridViewRow ()
{
  CommonConstructor (i_bHideRows, nullptr);
}

//----------------------------------------------------------------------------
CDataGridViewMultiRow::CDataGridViewMultiRow (bool i_bHideRows, ::DataGridView^ i_dgv) : DataGridViewRow ()
{
  CommonConstructor (i_bHideRows, i_dgv);
}

//----------------------------------------------------------------------------
// property: Rows
//----------------------------------------------------------------------------
DataGridViewRow^ CDataGridViewMultiRow::Rows::get (int i_ixRow)
{
  if (i_ixRow < 0 || i_ixRow >= m_listdgvr->Count)
    return nullptr;

  return m_listdgvr[i_ixRow];
}

//----------------------------------------------------------------------------
void CDataGridViewMultiRow::Rows::set (int i_ixRow, DataGridViewRow^ i_dgvr)
{
  if (!i_dgvr)
    return;
  if (i_ixRow < 0 || i_ixRow >= m_listdgvr->Count)
    return;

  int ixDgvr = -1;
  DataGridViewRow^ dgvr = m_listdgvr[i_ixRow];
  if (dgvr->DataGridView
  &&  dgvr->DataGridView == this->DataGridView)
  {
    ixDgvr = dgvr->Index;
    dgvr->DataGridView->Rows->Remove (dgvr);
  }

  m_listdgvr[i_ixRow] = i_dgvr;
  if (this->DataGridView)
  {
    if (ixDgvr < 0)
      ixDgvr = this->DataGridView->Rows->IndexOf (this) + 1 + i_ixRow;
    this->DataGridView->Rows->Insert (ixDgvr, i_dgvr);
    i_dgvr->Visible = !m_bHideRows;
  }

  Update();
}

//----------------------------------------------------------------------------
// property: HideRows
//----------------------------------------------------------------------------
void CDataGridViewMultiRow::HideRows::set (bool i_bHideRows)
{
  m_bHideRows = i_bHideRows;
  for (int ixRow = 0; ixRow < m_listdgvr->Count; ixRow++)
    m_listdgvr[ixRow]->Visible = !m_bHideRows;
}

//----------------------------------------------------------------------------
// Clone
//----------------------------------------------------------------------------
Object^ CDataGridViewMultiRow::Clone ()
{
  CDataGridViewMultiRow^ dgvr = (CDataGridViewMultiRow^)DataGridViewRow::Clone();
  if (dgvr)
  {
    dgvr->m_bHideRows = this->m_bHideRows;
    dgvr->m_listdgvr->Clear();
    dgvr->m_listdgvr->AddRange (this->m_listdgvr);
  }
  return dgvr;
}

//----------------------------------------------------------------------------
// Clear
//----------------------------------------------------------------------------
void CDataGridViewMultiRow::Clear ()
{
  for (int ixRow = 0; ixRow < m_listdgvr->Count; ixRow++)
  {
    if (m_listdgvr[ixRow]->DataGridView
    &&  m_listdgvr[ixRow]->DataGridView == this->DataGridView)
      m_listdgvr[ixRow]->DataGridView->Rows->Remove (m_listdgvr[ixRow]);
    m_listdgvr[ixRow]->Visible = true;
  }
  m_listdgvr->Clear();

  Update();
}

//----------------------------------------------------------------------------
// Add
//----------------------------------------------------------------------------
bool CDataGridViewMultiRow::Add (DataGridViewRow^ i_dgvr)
{
  return Insert (m_listdgvr->Count, i_dgvr);
}

//----------------------------------------------------------------------------
// Insert
//----------------------------------------------------------------------------
bool CDataGridViewMultiRow::Insert (int i_ixRow, DataGridViewRow^ i_dgvr)
{
  if (!i_dgvr)
    return false;
  if (i_dgvr->Index < 0)
    return false;  // block shared rows and rows that are not part of a DGV
  if (i_ixRow < 0)
    return false;
  else if (i_ixRow > m_listdgvr->Count)
    i_ixRow = m_listdgvr->Count;

  m_listdgvr->Insert (i_ixRow, i_dgvr);

  if (i_dgvr->DataGridView
  &&  i_dgvr->DataGridView != this->DataGridView)
    i_dgvr->DataGridView->Rows->Remove (i_dgvr);
  if (this->DataGridView)
  {
    int ixDgvr = this->DataGridView->Rows->IndexOf (this) + 1 + i_ixRow;
    if (i_dgvr->DataGridView == this->DataGridView
    &&  i_dgvr->Index != ixDgvr)
      i_dgvr->DataGridView->Rows->Remove (i_dgvr);

    ixDgvr = this->DataGridView->Rows->IndexOf (this) + 1 + i_ixRow;
    if (i_dgvr->DataGridView != this->DataGridView)
      this->DataGridView->Rows->Insert (ixDgvr, i_dgvr);
  }
  i_dgvr->Visible = !m_bHideRows;

  Update();

  return true;
}

//----------------------------------------------------------------------------
// Remove
//----------------------------------------------------------------------------
bool CDataGridViewMultiRow::Remove (int i_ixRow)
{
  return Remove (Rows[i_ixRow]);
}

//----------------------------------------------------------------------------
// Remove
//----------------------------------------------------------------------------
bool CDataGridViewMultiRow::Remove (DataGridViewRow^ i_dgvr)
{
  bool bResult = m_listdgvr->Remove (i_dgvr);

  if (i_dgvr)
  {
    if (i_dgvr->DataGridView
    &&  i_dgvr->DataGridView == this->DataGridView)
      i_dgvr->DataGridView->Rows->Remove (i_dgvr);
    i_dgvr->Visible = true;
  }

  Update();

  return bResult;
}

//----------------------------------------------------------------------------
// Update
//----------------------------------------------------------------------------
void CDataGridViewMultiRow::Update ()
{
  if (!this->DataGridView)
    return;
  if (this->Index < 0)
    throw gcnew InvalidOperationException ("Index is < 0. This may happen if the row was created by CreateCells(), then added to a DGV, which made a previously shared row become unshared, and then being accessed by the same invalidated object. Get the updated row object from the DGV.");

  array<int>^ aiNewLines = gcnew array<int>(m_listdgvr->Count);
  array<String^, 2>^ a2sValue = gcnew array<String^, 2>(this->Cells->Count, m_listdgvr->Count);

  for (int ixCell = 0; ixCell < Cells->Count; ixCell++)
  {
    for (int ixRow = 0; ixRow < m_listdgvr->Count; ixRow++)
    {
      if (m_listdgvr[ixRow]->Index < 0)
        continue;
      Object^ oValue = m_listdgvr[ixRow]->Cells[ixCell]->Value;
      if (oValue)
      {
        a2sValue[ixCell, ixRow] = oValue->ToString();
        int iNewLines = CString::Count (a2sValue[ixCell, ixRow], CONSTS::CRLF, StringComparison::InvariantCultureIgnoreCase);
        aiNewLines[ixRow] = Math::Max (aiNewLines[ixRow], iNewLines);
      }
    }
  }

  for (int ixCell = 0; ixCell < Cells->Count; ixCell++)
  {
    String^ sText = nullptr;
    for (int ixRow = 0; ixRow < m_listdgvr->Count; ixRow++)
    {
      if (ixRow > 0)
        sText += CONSTS::CRLF;
      sText += a2sValue[ixCell, ixRow];
      int iNewLines = CString::Count (a2sValue[ixCell, ixRow], CONSTS::CRLF, StringComparison::InvariantCultureIgnoreCase);
      sText += CString::Repeat (CONSTS::CRLF, aiNewLines[ixRow] - iNewLines);
    }
    this->Cells[ixCell]->Value = sText;
  }
}

//----------------------------------------------------------------------------
// OnDataGridViewChanged
//----------------------------------------------------------------------------
void CDataGridViewMultiRow::OnDataGridViewChanged ()
{
  try
  {
    if (this->DataGridView)
    {
      int ixDgvr = this->DataGridView->Rows->IndexOf (this) + 1;
      for (int ixCnt = 0; ixCnt < m_listdgvr->Count; ixCnt++)
        DataGridView->Rows->Insert (ixDgvr + ixCnt, m_listdgvr[ixCnt]);
    }
    else
    {
      for (int ixCnt = 0; ixCnt < m_listdgvr->Count; ixCnt++)
        m_listdgvr[ixCnt]->DataGridView->Rows->Remove (m_listdgvr[ixCnt]);
    }
  }
  finally
  {
    DataGridViewRow::OnDataGridViewChanged();
  }
}

//----------------------------------------------------------------------------
// PaintRow
//----------------------------------------------------------------------------
void CDataGridViewMultiRow::PaintRow (Object^ sender, DataGridViewRowPrePaintEventArgs^ e)
{
  ::DataGridView^ dgv = dynamic_cast<::DataGridView^>(sender);
  if (!dgv)
    return;
  if (e->RowIndex < 0 || e->RowIndex >= dgv->RowCount)
    return;
  CDataGridViewMultiRow^ dgvmr = dynamic_cast<CDataGridViewMultiRow^>(dgv->Rows->SharedRow(e->RowIndex));
  if (!dgvmr)
    return;
  if (dgvmr->DataGridView != dgv)
    return;

  bool bAutoHeight = dgv->AutoSizeRowsMode == DataGridViewAutoSizeRowsMode::AllCells
                  || dgv->AutoSizeRowsMode == DataGridViewAutoSizeRowsMode::AllCellsExceptHeaders
                  || dgv->AutoSizeRowsMode == DataGridViewAutoSizeRowsMode::DisplayedCells
                  || dgv->AutoSizeRowsMode == DataGridViewAutoSizeRowsMode::DisplayedCellsExceptHeaders;
  Graphics^ g = e->Graphics;
  StringFormatFlags enFlags = (StringFormatFlags)0;
  if (dgvmr->InheritedStyle->WrapMode != DataGridViewTriState::True)
    enFlags = enFlags | StringFormatFlags::NoWrap;
  StringFormat^ oStringFormat = gcnew StringFormat(enFlags);

  array<float>^       afRowHeight = gcnew array<float>(dgvmr->RowCount);
  array<int>^         aiLines     = gcnew array<int>  (dgvmr->RowCount);
  array<int,     2>^  a2iLines    = gcnew array<int,     2>(dgvmr->Cells->Count, dgvmr->RowCount);
  array<String^, 2>^  a2sValue    = gcnew array<String^, 2>(dgvmr->Cells->Count, dgvmr->RowCount);
  for (int ixRow = 0; ixRow < dgvmr->RowCount; ixRow++)
  {
    DataGridViewRow^ dgvr = dgvmr->Rows[ixRow];
    for (int ixCell = 0; ixCell < dgvmr->Cells->Count; ixCell++)
    {
      if (dgvr->Index < 0)
        continue;
      Object^ oValue = dgvr->Cells[ixCell]->Value;
      if (!oValue)
        continue;
      a2sValue[ixCell, ixRow] = oValue->ToString();
      int iCharacters = 0, iLines = 0;
      SizeF oLayoutArea ((float)dgvmr->Cells[ixCell]->Size.Width, 0);
      SizeF oTextSize = g->MeasureString (a2sValue[ixCell, ixRow],
                                          dgvmr->Cells[ixCell]->InheritedStyle->Font,
                                          oLayoutArea,
                                          oStringFormat,
                                          iCharacters,
                                          iLines);
      float fHeight = oTextSize.Height;
      if (!bAutoHeight)
        fHeight += 4;
      afRowHeight[ixRow] = Math::Max (afRowHeight[ixRow], fHeight);
      a2iLines[ixCell, ixRow] = iLines;
      aiLines[ixRow] = Math::Max (aiLines[ixRow], iLines);
    }
  }
  int iLength = dgv->Columns->GetColumnsWidth(DataGridViewElementStates::Visible);
  int iHeight = (int)Math::Ceiling(CMath::Sum (afRowHeight));
  dgvmr->Height = iHeight;

  e->PaintCellsBackground (e->ClipBounds, true);

  int iPositionX = e->RowBounds.X + dgvmr->HeaderCell->Size.Width - dgv->HorizontalScrollingOffset;
  int iPositionY = 0;
  for (int ixCell = 0; ixCell < dgvmr->Cells->Count; ixCell++)
  {
    String^ sText = nullptr;
    DataGridViewCell^ oCell = dgvmr->Cells[ixCell];
    Color oTextColor = oCell->Selected ? oCell->InheritedStyle->SelectionForeColor : oCell->InheritedStyle->ForeColor;
    Drawing::Brush^ oBrush = gcnew Drawing::SolidBrush (oTextColor);
    iPositionY = e->RowBounds.Y;
    if (!bAutoHeight)
      iPositionY += 2;
    for (int ixRow = 0; ixRow < dgvmr->RowCount; ixRow++)
    {
      if (ixRow > 0)
        sText += CONSTS::CRLF;
      sText += a2sValue[ixCell, ixRow];
      sText += CString::Repeat (CONSTS::CRLF, aiLines[ixRow] - a2iLines[ixCell, ixRow]);

      Rectangle oRectText (iPositionX, iPositionY, oCell->Size.Width, oCell->Size.Height);
      g->DrawString (a2sValue[ixCell, ixRow], oCell->InheritedStyle->Font, oBrush, oRectText, oStringFormat);
      iPositionY += (int)afRowHeight[ixRow];
    }
    dgvmr->Cells[ixCell]->Value = sText;
    iPositionX += oCell->Size.Width;
  }

  Color oLineColor = dgvmr->Selected ? dgvmr->InheritedStyle->SelectionForeColor : dgvmr->InheritedStyle->ForeColor;
  Pen^ oPen = gcnew Pen(oLineColor , 1);
  oPen->DashPattern = gcnew array<float>{5, 15};
  iPositionX = e->RowBounds.X + dgvmr->HeaderCell->Size.Width - dgv->HorizontalScrollingOffset;
  iPositionY = e->RowBounds.Y;
  for (int ixRow = 0; ixRow < dgvmr->RowCount - 1; ixRow++)
  {
    iPositionY += (int)afRowHeight[ixRow];
    g->DrawLine (oPen, iPositionX,           iPositionY,
                       iPositionX + iLength, iPositionY);
  }

  e->PaintHeader (true);

  e->Handled = true;
}

//----------------------------------------------------------------------------
// CommonConstructor
//----------------------------------------------------------------------------
void CDataGridViewMultiRow::CommonConstructor ( bool            i_bHideRows,
                                                ::DataGridView^ i_dgv)
{
  m_bHideRows = i_bHideRows;

  if (i_dgv)
    this->CreateCells(i_dgv);

  m_listdgvr = gcnew List<DataGridViewRow^>;
  this->ReadOnly = true;
}
于 2017-05-27T12:16:09.990 回答