List Control的使用问题
本帖最后由 董督秀 于 2024-10-20 08:01 编辑1.如何实现插入并显示近万条索引结果的同时,耗时<1s?网上资料说要可以使用虚拟列表。我试了没成功,如果使用虚拟列表,应该在消息循环里添加什么对应的case消息处理?
目前还没成功使用虚拟列表,导致插入效率较低。在2s左右才能显示结果。
2.如何使得匹配的索引顶行显示而不是在底部显示?
我尝试通过SendMessage匹配索引,但匹配到的索引是在控件底部显示。
3.如何使得匹配到的索引单击一次响应而不是两次?
存在索引匹配到后,需要额外点击别的地方,再单击一次才能显示的这种情况。
问题1.与3.已解决,感谢yes2
问题2.也已完美解决 你这个插件接管了ctrl+G吗 还是你保留了原有的功能,然后自己再自定义了一个快捷键? bester 发表于 2024-10-17 11:01
你这个插件接管了ctrl+G吗 还是你保留了原有的功能,然后自己再自定义了一个快捷键?
完全接管OD的Ctrl+G功能。 参考 https://learn.microsoft.com/zh-cn/cpp/mfc/virtual-list-controls?view=msvc-170
我用MFC测试了一下1万条数据,速度确实很快。
控件设置LVS_OWNERDATA样式,MFC中可以直接在资源界面的属性中修改;
设置预计的数量,这样滚动条会展示正确的范围。m_listCtrl.SetItemCount(10000); m_listCtrl是我定义的控件变量CListCtrl m_listCtrl;
添加LVN_GETDISPINFO响应函数,在MFC中是
ON_NOTIFY(LVN_GETDISPINFO, IDC_LIST1, &CtestListCtrlDlg::OnGetDispInfo)
IDC_LIST1是控件ID,CtestListCtrlDlg是我的对话框类,OnGetDispInfo是自定义的响应函数。
void CtestListCtrlDlg::OnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult)
{
NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO *>(pNMHDR);
LVITEM *pItem = &(pDispInfo)->item;
int iItem = pItem->iItem;
if (pItem->mask & LVIF_TEXT) //valid text buffer?
{
switch (pItem->iSubItem)
{
case 0: //fill in main text
_tcscpy_s(pItem->pszText, pItem->cchTextMax,
m_Items.m_strItemText);
break;
case 1: //fill in sub item 1 text
_tcscpy_s(pItem->pszText, pItem->cchTextMax,
m_Items.m_strSubItem1Text);
break;
case 2: //fill in sub item 2 text
_tcscpy_s(pItem->pszText, pItem->cchTextMax,
m_Items.m_strSubItem2Text);
break;
}
}
if (pItem->mask & LVIF_IMAGE) //valid image?
{
pItem->iImage = m_Items.m_iImage;
}
*pResult = 0;
}
你只需要根据iItem索引,把相关数据设置进去就可以了 yes2 发表于 2024-10-17 11:26
参考 https://learn.microsoft.com/zh-cn/cpp/mfc/virtual-list-controls?view=msvc-170
我用MFC测试了一 ...
由于是通过hook完全接管OD的ctrl+g功能,因此不能直接用mfc的消息映射。是通过win32的api,给list control所在的窗体动态设置消息回调。回调是WndProc。现在尝试在回调的wm_notify处理虚拟列表。老哥方便的话,用win32试试... 董督秀 发表于 2024-10-17 11:06
完全接管OD的Ctrl+G功能。
接管的方法方便的时候放一下,我好奇很久了,因为我看很多插件都可以接管系统级的功能,但是我没想到怎么来的 不能上传zip包可能是会员组权限限制吧,贴上cpp源码凑合看吧。
// Win32ListCtrl.cpp : 定义应用程序的入口点。
//
#include "stdafx.h"
#include "Win32ListCtrl.h"
#include <CommCtrl.h>
#define MAX_LOADSTRING 100
// 全局变量:
HINSTANCE hInst; // 当前实例
TCHAR szTitle; // 标题栏文本
TCHAR szWindowClass; // 主窗口类名
// 此代码模块中包含的函数的前向声明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
// 自定义数据
struct OWNER_ITEM
{
TCHAR subItem; // 可以用std::string或TCHAR*以获得不定长字符串支持
};
#define ITEM_COUNT100000
struct OWNER_ITEM g_owner_data;
HWND g_hListControl = 0;
int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// 准备数据的时间是无法省略的。可以从文件读取
for (int i = 0; i < ITEM_COUNT; i++)
{
_stprintf_s(g_owner_data.subItem, 16, _T("%d.col0"), i);
_stprintf_s(g_owner_data.subItem, 16, _T("%d.col1"), i);
_stprintf_s(g_owner_data.subItem, 16, _T("%d.col2"), i);
}
// TODO:在此放置代码。
MSG msg;
HACCEL hAccelTable;
// 初始化全局字符串
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_WIN32LISTCTRL, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WIN32LISTCTRL));
// 主消息循环:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
//
//函数:MyRegisterClass()
//
//目的:注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32LISTCTRL));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_WIN32LISTCTRL);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassEx(&wcex);
}
//
// 函数:InitInstance(HINSTANCE, int)
//
// 目的:保存实例句柄并创建主窗口
//
// 注释:
//
// 在此函数中,我们在全局变量中保存实例句柄并
// 创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // 将实例句柄存储在全局变量中
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
//函数:WndProc(HWND, UINT, WPARAM, LPARAM)
//
//目的: 处理主窗口的消息。
//
//WM_COMMAND - 处理应用程序菜单
//WM_PAINT - 绘制主窗口
//WM_DESTROY - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
// 创建ListView
case WM_CREATE:
{
g_hListControl = CreateWindow(WC_LISTVIEW, _T(""),
WS_CHILD | WS_VISIBLE | WS_BORDER |
LVS_OWNERDATA | LVS_REPORT | LVS_SHOWSELALWAYS,// 设置LVS_OWNERDATA
50, 50,
300, 300,
hWnd,
(HMENU)IDC_LIST_1,
NULL,
NULL);
//整行选择
ListView_SetExtendedListViewStyle(g_hListControl, LVS_EX_FULLROWSELECT); //扩展列表视图样式
LVCOLUMN colInfo_0 = { 0 };
colInfo_0.mask |= LVCF_WIDTH | LVCF_TEXT;
colInfo_0.cx = 100;
colInfo_0.pszText = (LPTSTR)_T("第0列");
SendMessage(g_hListControl, LVM_INSERTCOLUMN, 0, (LPARAM)&colInfo_0); //插入列
LVCOLUMN colInfo_1 = { 0 };
colInfo_1.mask |= LVCF_WIDTH | LVCF_TEXT;
colInfo_1.cx = 100;
colInfo_1.pszText = (LPTSTR)_T("第1列");
SendMessage(g_hListControl, LVM_INSERTCOLUMN, 1, (LPARAM)&colInfo_1);
LVCOLUMN colInfo_2 = { 0 };
colInfo_2.mask |= LVCF_WIDTH | LVCF_TEXT;
colInfo_2.cx = 100;
colInfo_2.pszText = (LPTSTR)_T("第2列");
ListView_InsertColumn(g_hListControl, 2, (LPARAM)&colInfo_2); //插入列
// 设置条目总数量
ListView_SetItemCount(g_hListControl, ITEM_COUNT);
}
// 响应WM_NOTIFY
case WM_NOTIFY:
// 区分具体的NOTIFY消息
switch (((LPNMHDR)lParam)->code)
{
case LVN_GETDISPINFO:
// 如果多个LIST需要根据ID区分
if (((LPNMHDR)lParam)->idFrom == IDC_LIST_1)
{
NMLVDISPINFO* plvdi = (NMLVDISPINFO*)lParam;
plvdi->item.pszText = g_owner_data.subItem;
return TRUE;
}
break;
case LVN_ODFINDITEM:
if (((LPNMHDR)lParam)->idFrom == IDC_LIST_1)
{
NMLVFINDITEM* pFindInfo = (NMLVFINDITEM*)lParam;
int result = -1;
if ((pFindInfo->lvfi.flags & LVFI_STRING) == 0)
{
return result;
}
int total_count = ListView_GetItemCount(pFindInfo->hdr.hwndFrom);
//这是我们要找的字符串
const TCHAR* searchstr = pFindInfo->lvfi.psz;
DWORD searchLen = _tcslen(searchstr);
int startPos = pFindInfo->iStart;//保存起始位置
//判断是否最后一行
if (startPos >= total_count)
startPos = 0;
int currentPos = startPos;
//开始查找
do
{
if (_tcsnicmp(g_owner_data.subItem, searchstr, searchLen) == 0)
{
//选中这个元素,停止查找
result = currentPos;
break;
}
currentPos++;
//从头开始
if (currentPos >= total_count)
currentPos = 0;
} while (currentPos != startPos);
return result;
}
break;
}
break;
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// 分析菜单选择:
switch (wmId)
{
// 修改菜单的“关于”响应,用于测试寻找某条目
case IDM_ABOUT:
{
LVFINDINFO info;
int nIndex;
int total_count = ListView_GetItemCount(g_hListControl);
info.flags = LVFI_PARTIAL | LVFI_STRING;
info.psz = _T("98765");
nIndex = ListView_FindItem(g_hListControl, 0, &info);
if (nIndex != -1)
{
for (int n = nIndex; n < total_count; n++)
{
ListView_EnsureVisible(g_hListControl, n, FALSE);
if (ListView_GetTopIndex(g_hListControl) == nIndex)
break;
}
}
ListView_SetItemState(g_hListControl, nIndex, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
}
// DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO:在此添加任意绘图代码...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// “关于”框的消息处理程序。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
yes2 发表于 2024-10-17 14:41
不能上传zip包可能是会员组权限限制吧,贴上cpp源码凑合看吧。
// Win32ListCtrl.c ...
首先谢谢大佬,你提供的代码我单独创建工程并测试了,确实可行。
但不适合我的情况,我可能没描述清楚。
我的情况是:在项目中添加资源对话框,然后在资源对话框中直接加入现成的List Control。也就是说List Control是资源中已有的控件,并非是API创建的控件。
然后资源对话框的句柄是g_hMainDlg; 资源对话框里的List Control的控件句柄是g_hListControl;
我通过SetWindowLong给资源对话框(g_hMainDlg)动态绑定了消息回调函数WinProc。
然后变量std::set<std::string> g_alllines; 储存了几万行不同内容。
我尝试在消息回调函数WinProc中这样处理:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
switch (message)
case WM_NOTIFY:
LPNMHDR nmhdr = reinterpret_cast<LPNMHDR>(lParam);
if (nmhdr->hwndFrom == g_hListControl && (nmhdr->code == LVN_GETDISPINFO))
{
MessageBox(NULL,"123456","123456",0x40);
...
// 下面是尝试在List Control中通过虚拟列表的形式显示g_alllines的代码逻辑,使得耗时最短
...
}
我发现无论如何都不会执行到MessageBox(NULL,"123456","123456",0x40); ...{:1_937:}
给你个例子。rc里是一个对话框,对话框上一个ListControl,一个Edit,一个Button。
// win32DlgListCtrl.cpp : 定义应用程序的入口点。
//
#include "stdafx.h"
#include "win32dlglistctrl.h"
#include <CommCtrl.h>
// Global Variables:
HINSTANCE hInst;// current instance
#define ITEM_COUNT100000
struct OWNER_ITEM
{
TCHAR subItem; // 可以用std::string或TCHAR*以获得不定长字符串支持
};
struct OWNER_ITEM g_owner_data;
// Forward declarations of functions included in this code module:
BOOL InitInstance(HINSTANCE, int);
INT_PTR CALLBACK Dialog1Proc(HWND, UINT, WPARAM, LPARAM);
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
for (int i = 0; i < ITEM_COUNT; i++)
{
_stprintf_s(g_owner_data.subItem, 16, _T("%d.col0"), i);
_stprintf_s(g_owner_data.subItem, 16, _T("%d.col1"), i);
_stprintf_s(g_owner_data.subItem, 16, _T("%d.col2"), i);
}
// TODO: Place code here.
MSG msg;
// Perform application initialization:
if (!InitInstance(hInstance, nCmdShow))
{
return FALSE;
}
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
// if (!IsDialogMessage(msg.hwnd, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
}
//
// FUNCTION: InitInstance(HINSTANCE, int)
//
// PURPOSE: Saves instance handle and creates main window
//
// COMMENTS:
//
// In this function, we save the instance handle in a global variable and
// create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // Store instance handle in our global variable
hWnd = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, Dialog1Proc);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
// Message handler for dialog1.
INT_PTR CALLBACK Dialog1Proc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
{
// Initialize the List Control
HWND hList = GetDlgItem(hDlg, IDC_LIST1);
SetWindowLong(hList, GWL_STYLE, GetWindowLong(hList, GWL_STYLE) | LVS_OWNERDATA | LVS_REPORT | LVS_SHOWSELALWAYS | LVS_SINGLESEL);
ListView_SetExtendedListViewStyle(hList, LVS_EX_FULLROWSELECT);
LVCOLUMN colInfo_0 = { 0 };
colInfo_0.mask |= LVCF_WIDTH | LVCF_TEXT;
colInfo_0.cx = 100;
colInfo_0.pszText = (LPTSTR)_T("第0列");
ListView_InsertColumn(hList, 0, (LPARAM)&colInfo_0); //插入列
LVCOLUMN colInfo_1 = { 0 };
colInfo_1.mask |= LVCF_WIDTH | LVCF_TEXT;
colInfo_1.cx = 100;
colInfo_1.pszText = (LPTSTR)_T("第1列");
ListView_InsertColumn(hList, 1, (LPARAM)&colInfo_1); //插入列
LVCOLUMN colInfo_2 = { 0 };
colInfo_2.mask |= LVCF_WIDTH | LVCF_TEXT;
colInfo_2.cx = 100;
colInfo_2.pszText = (LPTSTR)_T("第2列");
ListView_InsertColumn(hList, 2, (LPARAM)&colInfo_2); //插入列
// 设置条目总数量
ListView_SetItemCount(hList, ITEM_COUNT);
}
break;
// 响应WM_NOTIFY
case WM_NOTIFY:
// 区分具体的NOTIFY消息
switch (((LPNMHDR)lParam)->code)
{
case LVN_GETDISPINFO:
// 如果多个LIST需要根据ID区分
if (((LPNMHDR)lParam)->idFrom == IDC_LIST1)
{
NMLVDISPINFO* plvdi = (NMLVDISPINFO*)lParam;
plvdi->item.pszText = g_owner_data.subItem;
return TRUE;
}
break;
case LVN_ODFINDITEM:
if (((LPNMHDR)lParam)->idFrom == IDC_LIST1)
{
NMLVFINDITEM* pFindInfo = (NMLVFINDITEM*)lParam;
int result = -1;
if ((pFindInfo->lvfi.flags & LVFI_STRING) == 0)
{
return result;
}
int total_count = ListView_GetItemCount(pFindInfo->hdr.hwndFrom);
//这是我们要找的字符串
const TCHAR* searchstr = pFindInfo->lvfi.psz;
DWORD searchLen = _tcslen(searchstr);
int startPos = pFindInfo->iStart;//保存起始位置
//判断是否最后一行
if (startPos >= total_count)
startPos = 0;
int currentPos = startPos;
//开始查找
do
{
if (_tcsnicmp(g_owner_data.subItem, searchstr, searchLen) == 0)
{
//选中这个元素,停止查找
result = currentPos;
break;
}
currentPos++;
//从头开始
if (currentPos >= total_count)
currentPos = 0;
} while (currentPos != startPos);
// 重要!!!对话框的WM_NOTIFY返回值要手动通过SetWindowLong设置
SetWindowLong(hDlg, DWL_MSGRESULT, result);
return result;
}
break;
}
break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDOK:
case IDCANCEL:
EndDialog(hDlg, LOWORD(wParam));
PostQuitMessage(0);
return (INT_PTR)TRUE;
break;
case IDC_BUTTON1:
{
TCHAR buff;
GetDlgItemText(hDlg, IDC_EDIT1, buff, 128);
HWND hList = GetDlgItem(hDlg, IDC_LIST1);
LVFINDINFO info;
int nIndex;
int total_count = ListView_GetItemCount(hList);
nIndex = ListView_GetSelectionMark(hList);
if (nIndex != -1)
{
// 清除当前选中
ListView_SetItemState(hList, nIndex, 0, LVIS_SELECTED | LVIS_FOCUSED);
}
info.flags = LVFI_PARTIAL | LVFI_STRING;
info.psz = buff;
nIndex = ListView_FindItem(hList, -1, &info);
if (nIndex != -1)
{
for (int n = nIndex; n < total_count; n++)
{
// 使目标项在最上
ListView_EnsureVisible(hList, n, FALSE);
if (ListView_GetTopIndex(hList) == nIndex)
break;
}
// 使目标项选中
ListView_SetItemState(hList, nIndex, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
}
}
break;
}
}
return (INT_PTR)FALSE;
}
对话框有一点要注意,WM_NOTIFY返回值要手动通过SetWindowLong设置
Typically, the dialog box procedure should return TRUE if it processed the message, and FALSE if it did not. If the dialog box procedure returns FALSE, the dialog manager performs the default dialog operation in response to the message.
If the dialog box procedure processes a message that requires a specific return value, the dialog box procedure should set the desired return value by calling SetWindowLong(hwndDlg, DWL_MSGRESULT, lResult) immediately before returning TRUE. Note that you must call SetWindowLong immediately before returning TRUE; doing so earlier may result in the DWL_MSGRESULT value being overwritten by a nested dialog box message.
The following messages are exceptions to the general rules stated above. Consult the documentation for the specific message for details on the semantics of the return value. yes2 发表于 2024-10-18 11:34
给你个例子。rc里是一个对话框,对话框上一个ListControl,一个Edit,一个Button。
谢谢大佬。解决了!原来是消息循环的处理不当问题!
页:
[1]