C# DataGridView:将多行合并为一行但保留原件
C# DataGridView: combine multiple rows to one but keep originals
首先:我将自己回答这个问题,我会尽快回答。
但是你可能会帮助我,我会感谢你的所有建议。
我有一个 DataGridView,其中包含属于同一行的不同行。我的问题是找到一种合适的方式来显示和处理这些连接的行。
我的第一个想法是让每一行保持独立,但这有一些缺点:
如何清楚地显示行属于一起?
我可以添加另一列,其单元格为连接的行显示相同的数字,但这不容易看到并且需要另一列。
我在这里的解决方案是所有连接的行都具有相同的背景颜色,并且颜色会发生变化,例如每组连接的行在白色和浅灰色之间。
如何处理连接的行?一旦我 select 一组中的一行,我就必须通过提取信息(保存在单元格的标签或隐藏单元格中)来分析这一行,哪些行属于一起,select 它们也是如此.在 DataGridView 中移动行 up/down 的工作量更大:我还必须分析相邻的行集,看看我必须移动多远。
因此我决定创建一个DataGridViewMultiRow
。
完成后,我将 post class 的完整代码作为答案。
它将继承自DataGridViewRow
("DGVR") 并存储单个DGVR 或其他multi-rows 的列表,并通过自己绘制行的单元格将它们显示为一个代码。但是,我仍然需要找出为此目的使用哪些事件。 MSDN suggests to use DataGridView.RowPrePaint
, but I rather want to use an event that is bound to the DGVR itself. Maybe I will analyse the source code of DataGridViewRow.Paint() 并编写我自己的方法...
当添加到 multi-row 时,单行将变得不可见(可以通过滥用该概念将它们再次切换为可见,但 .net 本身有很多内容没有防止滥用;也许我什至没有切换到隐形,所以这是用户的责任。
通过强制每个 DGVR 成为与 multi-row 相同的 DGV 的一部分,可以简单地避免 multi-rows 中的递归,并且因为每一行只能添加到一个 DGV 并且只能添加一次,所以我不这样做必须再检查递归。
现在我正在为如何实现内部行列表而苦恼。我正在考虑使用 .net DataGridViewRowCollection
,但我发现它的操作与 DataGridView
本身紧密相关:一个 DGV 只能有一个 DGVRColl,但每个 DGVRColl 都引用一个 DGV。所以我的每个 DGVMultiRow 中都会有 half-connected DGVRColl。
我本来想问这是否会导致问题,但我已经发现在实例化 DGVRColl 时我必须提供 DGV,而在调用 DGVMultiRow ctor 时我没有提供 DGV。此外,当使用 DGVRColl 并向其提供 public get 属性 时,我只能挂接到 'CollectionChanged' 事件并且无法控制 Add()
和Remove()
。所以我将使用一个简单的私有列表。
续#1
我完成了主要工作,看起来已经很不错了:
我仍然需要修复一些细节问题,例如移动滚动条时正确放置文本以及其他小问题。
我决定不重写 DataGridViewRow.Paint()
,因为它的内部连接太多。所以我首先尝试使用 DGV 的 CellPainting
事件,这是一个好的开始。但是我需要同时获得该行所有单元格的信息,所以我按照 MSDN 的建议继续覆盖 DataGridView.RowPrePaint()
,请参见上面的 link。这真的很好用。
待续。
经过各种弊端,我终于创建了一个解决方案。
它是用 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;
}
首先:我将自己回答这个问题,我会尽快回答。
但是你可能会帮助我,我会感谢你的所有建议。
我有一个 DataGridView,其中包含属于同一行的不同行。我的问题是找到一种合适的方式来显示和处理这些连接的行。
我的第一个想法是让每一行保持独立,但这有一些缺点:
如何清楚地显示行属于一起?
我可以添加另一列,其单元格为连接的行显示相同的数字,但这不容易看到并且需要另一列。
我在这里的解决方案是所有连接的行都具有相同的背景颜色,并且颜色会发生变化,例如每组连接的行在白色和浅灰色之间。如何处理连接的行?一旦我 select 一组中的一行,我就必须通过提取信息(保存在单元格的标签或隐藏单元格中)来分析这一行,哪些行属于一起,select 它们也是如此.在 DataGridView 中移动行 up/down 的工作量更大:我还必须分析相邻的行集,看看我必须移动多远。
因此我决定创建一个DataGridViewMultiRow
。
完成后,我将 post class 的完整代码作为答案。
它将继承自DataGridViewRow
("DGVR") 并存储单个DGVR 或其他multi-rows 的列表,并通过自己绘制行的单元格将它们显示为一个代码。但是,我仍然需要找出为此目的使用哪些事件。 MSDN suggests to use DataGridView.RowPrePaint
, but I rather want to use an event that is bound to the DGVR itself. Maybe I will analyse the source code of DataGridViewRow.Paint() 并编写我自己的方法...
当添加到 multi-row 时,单行将变得不可见(可以通过滥用该概念将它们再次切换为可见,但 .net 本身有很多内容没有防止滥用;也许我什至没有切换到隐形,所以这是用户的责任。
通过强制每个 DGVR 成为与 multi-row 相同的 DGV 的一部分,可以简单地避免 multi-rows 中的递归,并且因为每一行只能添加到一个 DGV 并且只能添加一次,所以我不这样做必须再检查递归。
现在我正在为如何实现内部行列表而苦恼。我正在考虑使用 .net DataGridViewRowCollection
,但我发现它的操作与 DataGridView
本身紧密相关:一个 DGV 只能有一个 DGVRColl,但每个 DGVRColl 都引用一个 DGV。所以我的每个 DGVMultiRow 中都会有 half-connected DGVRColl。
我本来想问这是否会导致问题,但我已经发现在实例化 DGVRColl 时我必须提供 DGV,而在调用 DGVMultiRow ctor 时我没有提供 DGV。此外,当使用 DGVRColl 并向其提供 public get 属性 时,我只能挂接到 'CollectionChanged' 事件并且无法控制 Add()
和Remove()
。所以我将使用一个简单的私有列表。
续#1
我完成了主要工作,看起来已经很不错了:
我仍然需要修复一些细节问题,例如移动滚动条时正确放置文本以及其他小问题。
我决定不重写 DataGridViewRow.Paint()
,因为它的内部连接太多。所以我首先尝试使用 DGV 的 CellPainting
事件,这是一个好的开始。但是我需要同时获得该行所有单元格的信息,所以我按照 MSDN 的建议继续覆盖 DataGridView.RowPrePaint()
,请参见上面的 link。这真的很好用。
待续。
经过各种弊端,我终于创建了一个解决方案。
它是用 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;
}