CListView是那样简单好用,以至于咱们爱它就好像老鼠爱大米一样。可是你是否知道它的控制类CListCtrl有很多雷区呢?尤其当 Items非常大时(5000以上),对CListCtrl的用法不当,会导致CListView的加载明显延迟。来看看下面载入逗号分隔文本CSV的常见用法:
--------------------------------------------------------------------------------
void CRecordDoc::LoadFileCSV(CStdioFile& file_Open,
CListCtrl& openListCtrl, int& ReadNum)
{
ReadNum = 0;
int nSubItem = 0;
int nStart = 0, nEnd = 0;
CString strTempOpen;
const TCHAR tcSplit = _T(',');
while(1)
{
if(file_Open.ReadString(strTempOpen)==FALSE) break;
for (nSubItem = nStart = 0; nSubItem < 11; nSubItem++)
{
nEnd = strTempOpen.Find(tcSplit, nStart);
if (nEnd == -1)
{
break;
}
if (nSubItem == 0)
{
ReadNum ++;
openListCtrl.InsertItem(0, strTempOpen.Mid(nStart, nEnd - nStart));
}
else
{
openListCtrl.SetItemText(0, nSubItem, strTempOpen.Mid(nStart, nEnd - nStart));
}
nStart = nEnd + 1;
}
};
}
--------------------------------------------------------------------------------
看出问题了么?
优化1:巧用CListCtrl::SetRedraw(BOOL bRedraw)函数,性能提升75%
--------------------------------------------------------------------------------
void CRecordDoc::LoadFileCSV(CStdioFile& file_Open,
CListCtrl& openListCtrl, int& ReadNum)
{
SetCursor(LoadCursor(NULL, IDC_WAIT));
openListCtrl.SetRedraw(FALSE);
ReadNum = 0;
int nSubItem = 0;
int nStart = 0, nEnd = 0;
CString strTempOpen;
const TCHAR tcSplit = _T(',');
while(1)
{
if(file_Open.ReadString(strTempOpen)==FALSE) break;
for (nSubItem = nStart = 0; nSubItem < 11; nSubItem++)
{
nEnd = strTempOpen.Find(tcSplit, nStart);
if (nEnd == -1)
{
break;
}
if (nSubItem == 0)
{
ReadNum ++;
openListCtrl.InsertItem(0, strTempOpen.Mid(nStart, nEnd - nStart));
}
else
{
openListCtrl.SetItemText(0, nSubItem, strTempOpen.Mid(nStart, nEnd - nStart));
}
nStart = nEnd + 1;
}
};
openListCtrl.SetRedraw(TRUE);
SetCursor(LoadCursor(NULL, IDC_ARROW));
}
--------------------------------------------------------------------------------
不卖关子了,接着来
优化2:新纪录项追加在List底部,性能提高35%
--------------------------------------------------------------------------------
void CRecordDoc::LoadFileCSV(CStdioFile& file_Open,
CListCtrl& openListCtrl, int& ReadNum)
{
SetCursor(LoadCursor(NULL, IDC_WAIT));
openListCtrl.SetRedraw(FALSE);
ReadNum = 0;
int nSubItem = 0;
int nRootItem = -1;//插入项
int nStart = 0, nEnd = 0;
CString strTempOpen;
const TCHAR tcSplit = _T(',');
while(1)
{
if(file_Open.ReadString(strTempOpen)==FALSE) break;
for (nSubItem = nStart = 0; nSubItem < 11; nSubItem++)
{
nEnd = strTempOpen.Find(tcSplit, nStart);
if (nEnd == -1)
{
break;
}
if (nSubItem == 0)
{
nRootItem ++;
openListCtrl.InsertItem(nRootItem, strTempOpen.Mid(nStart, nEnd - nStart));
}
else
{
openListCtrl.SetItemText(nRootItem, nSubItem, strTempOpen.Mid(nStart, nEnd - nStart));
}
nStart = nEnd + 1;
}
};
ReadNum = nRootItem + 1;
openListCtrl.SetRedraw(TRUE);
SetCursor(LoadCursor(NULL, IDC_ARROW));
}
//优化3:删除某些选择行的数据,从后往前删,并且删之前把进度条置顶,并关刷新,性能提升80%
--------------------------------------------------------------------------------
void CRecordDoc::DeleteCSV(CListCtrl& openListCtrl)
{
SetCursor(LoadCursor(NULL, IDC_WAIT));
openListCtrl.SetRedraw(FALSE);
::SendMessage(openListCtrl.m_hWnd, WM_VSCROLL, SB_TOP, 0);
DWORD dwNum = openListCtrl.GetItemCount();
for (int i = dwNum - 1; i > 0; i--)
{
if(openListCtrl.GetCheck(i))
openListCtrl.DeleteItem(i);
}
//或者这里是openListCtrl.DeleteAllItems();
openListCtrl.SetRedraw(TRUE);
SetCursor(LoadCursor(NULL, IDC_ARROW));
}
--------------------------------------------------------------------------------
如何解决CListCtrl的数据更新时候的闪烁问题
方案1:窗口锁定技术。
m_list.LockWindowUpdate()
while()
{
//取出数据,插入到数据库中。
}
m_list.UnlockWindowUpdate()
程序在每次循环的时候不闪烁了,但是每个查询周期还是闪烁。
方案2: 不知道是否用对,每个查询周期还是要闪烁一次
SetWindowRedraw(hwnd, FALSE);
//过程同上
SetWindowRedraw(hwnd, TRUE);
方案3:虚拟列表技术
codePrject上下载的例子是针对一次插入大容量数据时候的不闪烁技术。更改以后,
每个周期改变数组的值,达到了要求。还是存在一些问题:
(1)虚拟列表技术必须要CListCtrl控件设置成自绘。原来的一个类用来设置每行的背景
颜色,不能用了。是在CodePrject上下载了一个CListCtrl例子,类CReportCtrl例子,
二者不能融合。调试时候发现是OnCustomdrawMyList ()函数中错误?
它使用了消息反射技术,如函数
OnCustomdrawMyList ( NMHDR* pNMHDR, LRESULT* pResult ),不知道和虚拟列表
技术使用的LVN_GETDISPINFO消息(对应函数OnGetdispinfoList(NMHDR* pNMHDR,
LRESULT* pResult))二者是怎么样的对应关系?