[Win32/WTL]_[虚拟列表]_[如何避免添加行数据时频繁刷新]
场景
- 在使用
Win32/WTL虚拟列表时,有时候底层短时间内接收到批量的数据,之后需要更新到列表。这时,如果只用SetItemCount更新列表时,更新的越频繁,列表就会越闪烁。这样给用户的体验就会比较差,怎么解决闪烁的问题?
说明
- 虚拟列表[5]主要是用来大数据的显示卡顿问题。 但现在使用
SetItemCount增加行数时会频繁刷新列表可见区域,如果可见区域有自定义的图片区域,那么刷新图片会有明显的闪烁问题。 这个虚拟列表已经有解决方案,使用SetItemCountEx或者ListView_SetItemCountEx函数,并传递LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL参数。
LVSICF_NOINVALIDATEALL:除非受影响的项目当前处于视图中,否则列表视图控件不会重新绘出。 即当前通过SetItemCountEx添加的行数刚好在列表的可见区域中,那么就会刷新。LVSICF_NOSCROLL: 当项计数发生更改时,列表视图控件不会更改滚动位置。 即当前你滚动到4-10行,调用SetItemCountEx会影响当前的列表可见的行数4-10行, 只会滑块变小。
- 因此项目中把
SetItemCount替换为SetItemCountEx, 即不会闪烁也提供性能。
listview_.SetItemCountEx(photos_.size(),LVSICF_NOINVALIDATEALL|LVSICF_NOSCROLL);listview_.SetItemCountEx(photos_.size(),LVSICF_NOINVALIDATEALL|LVSICF_NOSCROLL);例子
View.h
// View.h : interface of the CView class///////////////////////////////////////////////////////////////////////////////#pragmaonce#include<utility>#include<string>#include<vector>#include<memory>#include<atlmisc.h>#include<atlctrls.h>#include<atlctrlx.h>#include<GdiPlus.h>usingnamespacestd;enum{kMyButtonId=WM_USER+1,kMyButtonId2,kMyButtonId3,kMyListViewId};classPhoto{public:wstring name;wstring createDate;wstring path;wstring format;};classCView:publicCWindowImpl<CView>{public:DECLARE_WND_CLASS(NULL)BOOLPreTranslateMessage(MSG*pMsg);BEGIN_MSG_MAP_EX(CView)MSG_WM_CREATE(OnCreate)MESSAGE_HANDLER(WM_PAINT,OnPaint)NOTIFY_HANDLER(kMyListViewId,NM_CLICK,OnNMClickListResult)NOTIFY_HANDLER(kMyListViewId,LVN_GETDISPINFO,OnGetListViewData)NOTIFY_HANDLER(kMyListViewId,LVN_ODCACHEHINT,OnPrepareListViewData)NOTIFY_HANDLER(kMyListViewId,LVN_ODFINDITEM,OnFindListViewData)COMMAND_RANGE_HANDLER_EX(kMyButtonId,kMyButtonId3,OnCommandIDHandlerEX)MSG_WM_TIMER(OnTimer)REFLECT_NOTIFICATIONS()END_MSG_MAP()protected:// Handler prototypes (uncomment arguments if needed):// LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)// LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)// LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)intOnCreate(LPCREATESTRUCT lpCreateStruct);LRESULTOnPaint(UINT/*uMsg*/,WPARAM/*wParam*/,LPARAM/*lParam*/,BOOL&/*bHandled*/);voidUpdateLayout();LRESULTOnNMClickListResult(intidCtrl,LPNMHDR pnmh,BOOL&bHandled);LRESULTOnGetListViewData(intidCtrl,LPNMHDR pnmh,BOOL&bHandled);LRESULTOnPrepareListViewData(intidCtrl,LPNMHDR pnmh,BOOL&bHandled);LRESULTOnFindListViewData(intidCtrl,LPNMHDR pnmh,BOOL&bHandled);voidOnCommandIDHandlerEX(UINT uNotifyCode,intnID,CWindow wndCtl);voidReloadMockData();voidReloadListView();voidOnTimer(UINT_PTR nIDEvent);private:std::wstringGetControlText(HWND hwnd,wchar_t*buf=NULL);CListViewCtrl listview_;vector<shared_ptr<Photo>*>photos_;CFont font_normal_;CFont font_bold_;CBrushHandle brush_white_;CBrushHandle brush_hollow_;CBrush brush_red_;CButton buttonReloadMockData_;CButton buttonReloadListView_;CButton buttonDeleteListViewOneRow_;};View.cpp
// View.cpp : implementation of the CView class///////////////////////////////////////////////////////////////////////////////#include"stdafx.h"#include"resource.h"#include<utility>#include<sstream>#include<stdint.h>#include<assert.h>#include<Strsafe.h>#include<random>#include"View.h"#include<CommCtrl.h>#include<string>#include<regex>usingnamespacestd;BOOLCView::PreTranslateMessage(MSG*pMsg){returnFALSE;}LRESULTCView::OnPaint(UINT/*uMsg*/,WPARAM/*wParam*/,LPARAM/*lParam*/,BOOL&/*bHandled*/){CPaintDCdc(m_hWnd);CMemoryDCmdc(dc,dc.m_ps.rcPaint);CRect rect_client;GetClientRect(&rect_client);mdc.FillSolidRect(rect_client,RGB(255,255,255));//TODO: Add your drawing code herereturn0;}staticHFONTGetFont(intpixel,boolbold,constwchar_t*font_name){LOGFONT lf;memset(&lf,0,sizeof(LOGFONT));// zero out structurelf.lfHeight=pixel;// request a 8-pixel-height fontif(bold){lf.lfWeight=FW_BOLD;}lstrcpy(lf.lfFaceName,font_name);// request a face name "Arial"HFONT font=::CreateFontIndirect(&lf);returnfont;}std::wstringCView::GetControlText(HWND hwnd,wchar_t*buf){autolength=::GetWindowTextLength(hwnd);boolbufNull=false;if(!buf){buf=newwchar_t[length+1]();bufNull=true;}::GetWindowText(hwnd,buf,length+1);std::wstringstr(buf);if(bufNull)delete[]buf;returnstr;}staticstd::wstringGetProductBinDir(){staticwchar_tszbuf[MAX_PATH];GetModuleFileName(NULL,szbuf,MAX_PATH);PathRemoveFileSpec(szbuf);intlength=lstrlen(szbuf);szbuf[length]=L'\\';szbuf[length+1]=0;returnstd::wstring(szbuf);}inttoRandInt(intmin,intmax){// `thread_local` 修饰符的变量只初始化一次,每个线程一个`gen`,避免多线程调用同一个`gen`导致数据竞争。// 使用`minstd_rand`生成性能高,简单的可预测的随机数。// 使用`mt19937` 生成性能稍低,很难预测的随机数。//thread_local std::mt19937 gen(std::random_device{}());thread_localstd::minstd_randgen(std::random_device{}());std::uniform_int_distribution<>dist(min,max);returndist(gen);}LRESULTCView::OnGetListViewData(intidCtrl,LPNMHDR pnmh,BOOL&bHandled){NMLVDISPINFO*plvdi=(NMLVDISPINFO*)pnmh;autoiItem=plvdi->item.iItem;if(-1==iItem)return0;autocount=photos_.size();if(!count||count<=iItem)return0;autophoto=photos_[iItem];if(plvdi->item.mask&LVIF_TEXT){switch(plvdi->item.iSubItem){case0:{autovalue=to_wstring((int64_t)iItem+1);StringCchCopy(plvdi->item.pszText,plvdi->item.cchTextMax,value.c_str());break;}case1:{autovalue=photo->get()->name+L"-"+to_wstring(toRandInt(1,100));StringCchCopy(plvdi->item.pszText,plvdi->item.cchTextMax,value.c_str());break;}case2:StringCchCopy(plvdi->item.pszText,plvdi->item.cchTextMax,photo->get()->format.c_str());break;case3:StringCchCopy(plvdi->item.pszText,plvdi->item.cchTextMax,photo->get()->createDate.c_str());break;}}return0;}LRESULTCView::OnPrepareListViewData(intidCtrl,LPNMHDR pnmh,BOOL&bHandled){return0;}LRESULTCView::OnFindListViewData(intidCtrl,LPNMHDR pnmh,BOOL&bHandled){LPNMLVFINDITEM pnmfi=(LPNMLVFINDITEM)pnmh;autoiItem=pnmfi->iStart;if(-1==iItem)return-1;autocount=photos_.size();if(!count||count<=iItem)return-1;return0;}LRESULTCView::OnNMClickListResult(intidCtrl,LPNMHDR pnmh,BOOL&bHandled){return0;}voidCView::OnCommandIDHandlerEX(UINT uNotifyCode,intnID,CWindow wndCtl){switch(nID){casekMyButtonId:{ReloadMockData();break;}casekMyButtonId2:{ReloadListView();MessageBox(L"刷新表格数据完成");break;}casekMyButtonId3:{std::vector<int>rows;intiItem=-1;while((iItem=listview_.GetNextItem(iItem,LVNI_SELECTED))!=-1)rows.push_back(iItem);if(rows.empty())break;for(inti=rows.size()-1;i>=0;--i)photos_.erase(photos_.begin()+rows.at(i));listview_.SetItemCountEx(photos_.size(),LVSICF_NOINVALIDATEALL|LVSICF_NOSCROLL);MessageBox(L"已删除");break;}}}voidCView::ReloadListView(){listview_.SetItemCount(0);for(autoi=0;i<photos_.size();++i)deletephotos_[i];photos_.clear();}voidCView::OnTimer(UINT_PTR nIDEvent){staticintconstkMaxCount=200;staticintindex=0;staticintcount=0;if(count==kMaxCount){count=0;index=0;KillTimer(nIDEvent);MessageBox(L"刷新模拟数据完成");return;}wchar_tbuf[MAX_PATH]={0};LVCOLUMN co;memset(&co,0,sizeof(co));co.mask=LVCF_TEXT;co.pszText=buf;co.cchTextMax=MAX_PATH;listview_.GetColumn(1,&co);std::wstringc0Text(buf);listview_.GetColumn(2,&co);std::wstringc1Text(buf);listview_.GetColumn(3,&co);std::wstringc2Text(buf);for(inti=0;i<500&&count<kMaxCount;++i,++index){autophoto=newPhoto();wsprintf(buf,(c0Text+L"-%d").c_str(),index);photo->name=buf;wsprintf(buf,(c1Text+L"-%d").c_str(),index);photo->format=buf;wsprintf(buf,(c2Text+L"-%d").c_str(),index);photo->createDate=buf;photos_.push_back(newshared_ptr<Photo>(photo));}listview_.SetItemCountEx(photos_.size(),LVSICF_NOINVALIDATEALL|LVSICF_NOSCROLL);//listview_.SetItemCount(photos_.size());++count;}voidCView::ReloadMockData(){SetTimer(1,500);}intCView::OnCreate(LPCREATESTRUCT lpCreateStruct){font_normal_=::GetFont(16,false,L"Arial");font_bold_=::GetFont(16,true,L"Arial");brush_hollow_=AtlGetStockBrush(HOLLOW_BRUSH);brush_white_=AtlGetStockBrush(WHITE_BRUSH);brush_red_.CreateSolidBrush(RGB(255,0,0));// 1.创建CListViewCtrllistview_.Create(m_hWnd,0,NULL,WS_CHILD|WS_TABSTOP|WS_VISIBLE|LVS_ALIGNLEFT|LVS_REPORT|LVS_SHOWSELALWAYS|WS_BORDER|LVS_OWNERDATA,0,kMyListViewId);listview_.SetExtendedListViewStyle(LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES|LVS_EX_DOUBLEBUFFER);//listview_.SetBkColor(RGB(255, 255, 255));listview_.SetTextBkColor(RGB(255,255,255));listview_.SetFont(font_normal_);autoheader=listview_.GetHeader();header.SetFont(font_bold_);listview_.SetBkColor(RGB(255,255,255));listview_.InsertColumn(0,L"No.",LVCFMT_LEFT,100);listview_.InsertColumn(1,L"Name",LVCFMT_LEFT,100);listview_.InsertColumn(2,L"Format",LVCFMT_LEFT,100);listview_.InsertColumn(3,L"Create Date",LVCFMT_LEFT,100);// 2.创建按钮buttonReloadMockData_.Create(m_hWnd,0,L"加载新数据",WS_CHILD|WS_VISIBLE,0,kMyButtonId);buttonReloadMockData_.SetFont(font_normal_);buttonReloadListView_.Create(m_hWnd,0,L"刷新表格",WS_CHILD|WS_VISIBLE,0,kMyButtonId2);buttonReloadListView_.SetFont(font_normal_);buttonDeleteListViewOneRow_.Create(m_hWnd,0,L"删除选中行",WS_CHILD|WS_VISIBLE,0,kMyButtonId3);buttonDeleteListViewOneRow_.SetFont(font_normal_);UpdateLayout();return0;}voidCView::UpdateLayout(){CRect rect;GetClientRect(&rect);CClientDCdc(m_hWnd);dc.SelectFont(font_normal_);CSizesize_control(500,300);CRect rect_control=CRect(CPoint(20,20),size_control);listview_.MoveWindow(rect_control);CSize size_button;buttonReloadMockData_.GetIdealSize(&size_button);rect_control=CRect(CPoint(rect_control.left,rect_control.bottom+10),size_button);buttonReloadMockData_.MoveWindow(rect_control);CSize sizeButton2;buttonReloadListView_.GetIdealSize(&sizeButton2);rect_control=CRect(CPoint(rect_control.right+10,rect_control.top),sizeButton2);buttonReloadListView_.MoveWindow(rect_control);CSize sizeButton3;buttonDeleteListViewOneRow_.GetIdealSize(&sizeButton3);rect_control=CRect(CPoint(rect_control.right+10,rect_control.top),sizeButton3);buttonDeleteListViewOneRow_.MoveWindow(rect_control);}项目
项目源码,VS2010以上 https://download.csdn.net/download/infoworld/93325545
运行
带SetItemCountEx带LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL参数:
SetItemCount不带参数
- 不带参数时,当前可见区域一直刷新,这里Name一直变化就是说明在插入数据时一直刷新
参考
LVM_GETNEXTITEM
LVM_SETITEMCOUNT
ListView_SetItemCount
ListView_SetItemCountEx
使用虚拟列表视图来解决新增大量数据卡顿问题
关于列表视图控件
