C#DataGridView:将多行合并为一行但保留原件

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

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

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

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

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

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

它将inheritanceDataGridViewRow (“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被调用的那一刻我没有的。 此外,当使用DGVRColl并为其提供公共get属性时,我只能挂钩’CollectionChanged’事件并且无法控制各个操作,如Add()Remove() 。 所以我将使用一个简单的私人列表。

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

在此处输入图像描述

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

我决定不覆盖DataGridViewRow.Paint()因为那个内部连接太多了。 所以我第一次使用CellPainting事件,这是一个良好的开端。 但是我需要同时拥有该行的所有单元格的信息,所以我按照MSDN的建议继续覆盖DataGridView.RowPrePaint() ,请参阅上面的链接。 这非常有效。

未完待续。

经过各种弊端,我终于创造了一个解决方案。
它是用C ++ / CLI编写的,因此大多数人都必须调整它以便在C#中使用。
此解决方案包含一些不属于解决方案的用户function,但其目的应该易于通过其名称进行猜测。

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

 #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^ 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^ aiNewLines = gcnew array(m_listdgvr->Count); array^ a2sValue = gcnew array(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(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^ afRowHeight = gcnew array(dgvmr->RowCount); array^ aiLines = gcnew array (dgvmr->RowCount); array^ a2iLines = gcnew array(dgvmr->Cells->Count, dgvmr->RowCount); array^ a2sValue = gcnew array(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{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; this->ReadOnly = true; }