news 2026/8/25 20:44:25

[Win32/WTL]_[虚拟列表]_[如何避免添加行数据时频繁刷新]

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
[Win32/WTL]_[虚拟列表]_[如何避免添加行数据时频繁刷新]

场景

  1. 在使用Win32/WTL虚拟列表时,有时候底层短时间内接收到批量的数据,之后需要更新到列表。这时,如果只用SetItemCount更新列表时,更新的越频繁,列表就会越闪烁。这样给用户的体验就会比较差,怎么解决闪烁的问题?

说明

  1. 虚拟列表[5]主要是用来大数据的显示卡顿问题。 但现在使用SetItemCount增加行数时会频繁刷新列表可见区域,如果可见区域有自定义的图片区域,那么刷新图片会有明显的闪烁问题。 这个虚拟列表已经有解决方案,使用SetItemCountEx或者ListView_SetItemCountEx函数,并传递LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL参数。
  • LVSICF_NOINVALIDATEALL:除非受影响的项目当前处于视图中,否则列表视图控件不会重新绘出。 即当前通过SetItemCountEx添加的行数刚好在列表的可见区域中,那么就会刷新。

  • LVSICF_NOSCROLL: 当项计数发生更改时,列表视图控件不会更改滚动位置。 即当前你滚动到4-10行,调用SetItemCountEx会影响当前的列表可见的行数4-10行, 只会滑块变小。

  1. 因此项目中把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

运行

SetItemCountExLVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL参数:

SetItemCount不带参数

  1. 不带参数时,当前可见区域一直刷新,这里Name一直变化就是说明在插入数据时一直刷新

参考

  1. LVM_GETNEXTITEM

  2. LVM_SETITEMCOUNT

  3. ListView_SetItemCount

  4. ListView_SetItemCountEx

  5. 使用虚拟列表视图来解决新增大量数据卡顿问题

  6. 关于列表视图控件

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/25 20:43:24

企业用AI,数据放云上安全吗?私有化部署的数字员工平台讲清楚

引言 企业用AI的热情起来了&#xff0c;但采购、研发、财务这些部门真要把活交给AI时&#xff0c;负责人往往先问一句&#xff1a;数据放云上安全吗。报价单、客户名单、工艺参数、还没对外公开的财务数据&#xff0c;喂给云端AI&#xff0c;等于商业机密出了门。这个顾虑不解决…

作者头像 李华
网站建设 2026/8/25 20:39:22

Minecraft红石隐藏门:8方块极简设计与双版本兼容方案

在《我的世界》中&#xff0c;建造隐藏门是许多玩家热衷的“红石工程”之一。无论是用于保护秘密基地&#xff0c;还是仅仅为了享受机关带来的乐趣&#xff0c;一个设计精巧、结构简单的隐藏门总能带来极大的成就感。然而&#xff0c;许多经典的红石隐藏门设计要么体积庞大、结…

作者头像 李华
网站建设 2026/8/25 20:37:46

用Git管理PPT项目:告别版本混乱,实现高效协作

在PPT制作过程中&#xff0c;你是否遇到过这样的困境&#xff1a;精心设计的图表、流程图、矢量图标&#xff0c;一旦需要修改&#xff0c;就得在PPT软件里一点点调整&#xff0c;费时费力&#xff1b;或者团队协作时&#xff0c;版本混乱&#xff0c;最终版到底是“PPT终版.pp…

作者头像 李华
网站建设 2026/8/25 20:36:03

UE5.8程序化植被编辑器(PVE)教程:从零创建自定义森林

在虚幻引擎5.8中&#xff0c;程序化植被编辑器&#xff08;Procedural Foliage Editor&#xff0c;简称PVE&#xff09;是一个强大的工具&#xff0c;它允许开发者通过规则和参数&#xff0c;而非手动放置&#xff0c;来大规模生成和分布植被。对于想要快速创建独特森林、丛林或…

作者头像 李华
网站建设 2026/8/25 20:31:06

物联网网关安全通信与长连接架构设计实战

1. 项目概述&#xff1a;从“远程网关”说起最近在梳理团队内部的一个老项目——OpenClaw远程网关&#xff0c;这名字听起来有点“江湖气”&#xff0c;但本质上它是一个处理海量设备长连接接入与安全通信的核心组件。你可能在很多地方见过类似的架构&#xff0c;比如物联网平台…

作者头像 李华
网站建设 2026/8/25 20:29:29

前端面试高频考点解析与应对策略

1. 为什么前端面试问题趋于固定化前端开发岗位的面试问题确实呈现出明显的固定化趋势&#xff0c;这背后有着深层次的行业逻辑。从我参与过的近百场技术面试来看&#xff0c;企业筛选候选人的核心诉求始终围绕三个维度展开&#xff1a;基础能力验证、框架熟练度考察以及实际解决…

作者头像 李华