实际使用中发现,虚表模式编辑表格时,挺啰嗦的。我觉得虚表只适合列表模式下的数据展示,比如中数据库或者文件中读取行去显示。
上一个版本中,实表模式有点鸡肋,不仅速度慢还非常占用内存。这次改动很大,主要是奔着实表模式下提升性能(节约内存和提高速度)去的,我在原有的基础上做改动,结果推倒重来无数次,忍无可忍只好从白板开始重新写了。
CGridListUI、CGridListHeaderUI、GGridListBodyUI、CGridListRowUI、CGridListCellUI统一改名为CGridUI、CGridHeaderUI、GGridBodyUI、CGridRowUI、CGridCellUI,长痛不如短痛吧。
这次改动实表模式有点类似于虚表,CGridBody根据数据内容动态创建控件,数据行使用结构体TRowData保存,单元格数据使用结构体TCellData保存。TRowData和TCellData使用内存池技术new和delete,极大的提高了速度。我测试了100000行6列,也就是60万个单元格,载入表格只需要几秒钟,几乎没感觉,仅占用内存100多MB。过程中有个奇怪的发现,原本TCellData中用std::string保存字符串,析构60万个std::string,我的破电脑需要27秒。换成CDuiString之后不仅速度快了,内存占用还变低了。
建议使用中,有许多大数据表格时,隐藏表格时调用ResetGridBody来清空表格数据,这个操作会把单元格内存归还内存池。显示表格时,重新载入表格数据。原因是,程序结束之前,内存池所占的内存无法还给操作系统,也无法手动调用某个接口去主动清理内存池。
很显然的,不要在GridBody插入任何控件,这里面的控件都是动态创建的。
表格内置了几种单元格类型:分别是文本,编辑框,复选框,下拉框,时间选取,图片,容器。
typedef enum enumGridCellType
{
celltypeText = 0,
celltypeEdit = 1,
celltypeCheckBox = 2,
celltypeCombo = 3,
celltypeDateTime = 4,
celltypeDate = 5,
celltypeTime = 6,
celltypePicture = 7,
celltypeContainer = 8
}GridCellType;
单元格类型看名字就知道什么意思了,比上一个版本多了一个图像类型celltypePicture,允许在单元格中显示图片。celltypeContainer表明是一个容器CHorizontalLayoutUI,可以在单元格中插入自定义控件。celltypeText为默认类型。
下面演示一下如何操作,具体实现请看源代码。
1,设置单元格类型,
//设置整列单元格类型
void SetCellType(int col, GridCellType cellType);
//设置单元格类型
void SetCellType(int row, int col, GridCellType cellType);
作为约定,表格头的单元格类型只能调用void SetCellType(int row, int col, GridCellType cellType)去设置,实际使用中建议在设计器中编辑好表头。
原则上,能设置整列就不要设置单个,它们的保存位置不一样。如果对60万个单元格单独去设置类型,将会至少占用60万*4字节的内存。
单个设置的类型优先于整列设置的类型。
2,初始化图像单元格和容器单元格
实表模式下,单元格控件也是动态创建的,需要提供一个时机去初始化。
void CMainFrame::OnNotifyInitCell(TNotifyUI& msg)
{
if(IsControl(msg, _T("grid_main")))
{
int row = msg.wParam;
int col = msg.lParam;
CGridCellUI *pCellUI = m_pGrid->GetCellUI(row, col);
if(!pCellUI) return;
//在表格第8列,插入2个按钮
if(col == 8)
{
pCellUI->SetChildPadding(10);
CButtonUI *pButtonModify = new CButtonUI;
pCellUI->Add(pButtonModify);
pButtonModify->ApplyAttributeList(_T("style_button"));
pButtonModify->SetForeImage(_T("images\\edtico.png"));
pButtonModify->SetFixedWidth(16);
pButtonModify->SetFixedHeight(16);
pButtonModify->SetName(_T("grid_innerbutton_modify"));
CButtonUI *pButtonDelete = new CButtonUI;
pCellUI->Add(pButtonDelete);
pButtonDelete->ApplyAttributeList(_T("style_button"));
pButtonDelete->SetForeImage(_T("images\\delico.png"));
pButtonDelete->SetFixedWidth(16);
pButtonDelete->SetFixedHeight(16);
pButtonDelete->SetName(_T("grid_innerbutton_delete"));
}
}
}
响应消息DUI_MSGTYPE_INITCELL,判断行和列进行初始化。当窗口大小变化时,CGirdBodyUI根据当前大小创建新行时,便会触发这个消息。新创建行只会触发一次。
3,实表数据填充
//表格填充
m_pGrid->SetRowCount(12);
for (int i=0; i<m_pGrid->GetRowCount(); i++)
{
for (int j=1; j<m_pGrid->GetColumnCount(); j++)
{
CString temp;
temp.Format(_T("%02d,%02d"), i, j);
m_pGrid->Cell(i,j).SetText(temp);
}
}
//插入单行
int row = m_pGrid->InsertRow();
for (int j=1; j<m_pGrid->GetColumnCount(); j++)
{
CString temp;
temp.Format(_T("%02d,%02d"), row, j);
m_pGrid->Cell(row,j).SetText(temp);
}
4,消息响应
//celltypeCombo选中项时
void CMainFrame::OnNotifyItemSelect(TNotifyUI& msg)
{
if(IsControl(msg, _T("grid_main")))
{
CDuiString s;
s.Format(_T("combo cell select item, row=%d, col=%d"), msg.wParam, msg.lParam);
InsertMsgUI(s);
}
}
//celltypeCheckBox类型,选中状态变更时
void CMainFrame::OnNotifySelectChanged(TNotifyUI& msg)
{
if(IsControl(msg, _T("grid_main")))
{
TCellData *pCellData = m_pGrid->GetCellData(msg.wParam, msg.lParam);
CDuiString s;
s.Format(_T("checkbox cell selectchanged, row=%d, col=%d, %s"), msg.wParam, msg.lParam, pCellData->IsCheckBoxCheck() ? _T("Select") : _T("UnSelect"));
InsertMsgUI(s);
}
}
//下拉框点击下拉按钮时,还未显示时,更新下拉项
void CMainFrame::OnNotifyPreDropDown(TNotifyUI& msg)
{
if(IsControl(msg, _T("grid_main")))
{
CGridCellUI *pCellUI = m_pGrid->GetCellUI(msg.wParam, msg.lParam);
CComboExUI *pCombo = (CComboExUI *)pCellUI->GetInnerControl();
CDuiString s;
s.Format(_T("combo cell predropdown, row=%d, col=%d"), msg.wParam, msg.lParam);
InsertMsgUI(s);
pCombo->RemoveAll();
for (int i=0; i<10; i++)
{
CString s;
s.Format(_T("combo text %d"), i);
pCombo->AddString(s);
}
}
}
//下拉框点击下拉按钮之后
void CMainFrame::OnNotifyDropDown(TNotifyUI& msg)
{
if(IsControl(msg, _T("grid_main")))
{
CDuiString s;
s.Format(_T("combo cell dropdown, row=%d, col=%d"), msg.wParam, msg.lParam);
InsertMsgUI(s);
}
}
//编辑框开始编辑时
void CMainFrame::OnNotifyStartEdit(TNotifyUI& msg)
{
if(IsControl(msg, _T("grid_main")))
{
CDuiString s;
s.Format(_T("edit cell start edit, row=%d, col=%d"), msg.wParam, msg.lParam);
InsertMsgUI(s);
}
}
//编辑框结束编辑时
void CMainFrame::OnNotifyEndEdit(TNotifyUI& msg)
{
if(IsControl(msg, _T("grid_main")))
{
CDuiString s;
s.Format(_T("edit cell end edit, row=%d, col=%d"), msg.wParam, msg.lParam);
InsertMsgUI(s);
}
}
//单元格文本内容变更时
void CMainFrame::OnNotifyTextChanged(TNotifyUI& msg)
{
if(IsControl(msg, _T("grid_main")))
{
TCellData *pCellData = m_pGrid->GetCellData(msg.wParam, msg.lParam);
CDuiString s;
s.Format(_T("edit cell text changed, row=%d, col=%d, text=%s"), msg.wParam, msg.lParam, pCellData->GetText());
InsertMsgUI(s);
}
}
5,虚表部分
需要手动调用m_pGrid->SetVirtualGrid(TRUE),这个不能在xml中定义了。
然后调用m_pGrid->SetRowCount(10 * 10000);
虚表数据填充,响应消息DUI_MSGTYPE_DRAWITEM
void CMainFrame::OnNotifyDrawItem(TNotifyUI& msg)
{
//for virtual grid,
if(IsControl(msg, _T("grid_main")))
{
int lo = msg.wParam; //begin row
int hi = msg.lParam; //end row
InsertMsgUiV(_T("DrawRange=%d,%d"), lo, hi);
int sort_col = m_pGrid->GetSortColumn();
BOOL bAscending = m_pGrid->GetSortAscending();
//fill grid content
for (int i=lo; i<=hi; i++)
{
for (int j=1; j<m_pGrid->GetColumnCount(); j++)
{
if(sort_col > 0 && !bAscending)
{
CDuiString s;
s.Format(_T("%d,%d"), m_pGrid->GetRowCount()-i, j);
m_pGrid->GetCellUI(i,j)->SetText(s);
}
else
{
CDuiString s;
s.Format(_T("%d,%d"), i, j);
m_pGrid->GetCellUI(i,j)->SetText(s);
}
}
}
}
}
和实表的填充有个区别,m_pGrid->GetCellUI(i,j)->SetText(s),获取的是DuiLib原生控件去设置的。
响应点击表格头排序,响应消息DUI_MSGTYPE_SORTITEM。
void CMainFrame::OnNotifySortItem(TNotifyUI& msg)
{
if(IsControl(msg, _T("grid_main")))
{
//for virtual grid, when a sort message is received, we have to refresh the virtual order.
//and then we receive a drawitem message to sort the local data
if(m_pGrid->IsVirtualGrid())
{
//这时候可以对本地数据进行排序, 接着会触发OnNotifyDrawItem刷新显示。
m_pGrid->Refresh(true);
}
}
}
注意:虚表能进行的操作很有限,很多函数都不支持。
--------------------------------------------------------------------------------------
根据这个原理,TreeUI也可以做了。我原本想从GridUI继承一个TreeUI,但是诸多麻烦,所以我又从白板开始写TreeUI,把GridUI中能用的代码抄过来。
TreeUI不支持虚表模式,树控件各种递归循环,调用方要构造数组去动态显示的话,还不如直接用我这个实表模式。
--------------------------------------------------------------------------------------
时间有限,精力有限,水平也很有限,不可能花大量时间去测试每个细节,总之就是满足我自己的需求了。如果很荣幸你有在使用,欢迎提出你的建议。
代码共享地址:
https://gitee.com/Liqs99/DuiLib_DuiEditor
https://github.com/xfcanyue/DuiLib_DuiEditor
duilib设计器交流群:819272442