尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

【MFC笔记】List Control 右键菜单与 DELETE 按钮删除行:TaoToken 配置骨架与验证

发布时间:2026/9/29 4:28:12

资讯中心
01
ARTICLE

【MFC笔记】List Control 右键菜单与 DELETE 按钮删除行:TaoToken 配置骨架与验证

【MFC笔记】List Control 右键菜单与 DELETE 按钮删除行:TaoToken 配置骨架与验证
1. MFC List Control 右键菜单与 DELETE 删除行的真实开发场景如果你正在用 MFC 写桌面工具List Control 几乎是绕不开的控件设备列表、日志列表、任务队列、文件清单最后都要落到「选中一行、右键操作、删掉它」这条交互链路上。听起来简单但真正动手时会发现几个坑右键菜单弹不出来、弹出来点删除没反应、删完之后行号错乱、DELETE 键按下去毫无动静。这些问题不是逻辑难而是 MFC 的消息映射和控件风格设置太分散少配一步就整条链路断掉。这篇笔记聚焦一个完整可跑的闭环在 VS 工程里给 List Control 加上右键弹出菜单菜单里能删除当前选中行同时支持键盘 DELETE 键删除。我会把菜单资源方式和纯代码动态创建菜单两种做法都写清楚再补上LVS_EX_FULLROWSELECT这个关键风格最后用 TaoToken 的统一 Key/API 通道做一个配置骨架方便你把这类小工具后续接上模型能力时不用再折腾鉴权。适合已经会建 MFC 对话框工程、但对消息映射还不太熟的 C 桌面开发者。2. TaoToken 前置统一 Key 与 API 通道准备在写删除逻辑之前先把配置骨架搭好。TaoToken 在这里的角色是统一入口你后面如果想让这个 MFC 工具接上模型对话、代码补全或者批量处理不用每个功能单独配一套鉴权。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址固定为 https://taotoken.net/api 注意 API 地址不带任何查询参数。你需要先拿到一个 Key。进入控制台创建https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 然后在 API Keys 页面生成https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。Key 生成后只显示一次复制到本地配置文件里别硬编码进.cpp。配置文件用config.toml放在工程目录下或者用户目录都行骨架如下# config.toml - TaoToken 统一通道配置骨架 [taotoken] base_url https://taotoken.net/api api_key sk-你的Key粘贴在这里 timeout_ms 30000 [taotoken.defaults] # 默认走对话模型后续接 coding-plan 可换 model claude-sonnet max_tokens 2048 [taotoken.features] # 给 MFC 工具预留的功能开关 enable_chat true enable_code_hint false读取的时候用最朴素的解析就行MFC 工程里没必要上重型库。下面这段是能直接编译的读取函数用std::ifstream逐行扫#include fstream #include string #include map std::mapstd::string, std::string LoadToml(const std::string path) { std::mapstd::string, std::string kv; std::ifstream fin(path); std::string line; while (std::getline(fin, line)) { // 跳过注释和空行 if (line.empty() || line[0] #) continue; auto pos line.find(); if (pos std::string::npos) continue; std::string key line.substr(0, pos); std::string val line.substr(pos 1); // 去掉首尾空格和引号 auto trim [](std::string s) { while (!s.empty() (s.front() || s.front() )) s.erase(s.begin()); while (!s.empty() (s.back() || s.back() )) s.pop_back(); }; trim(key); trim(val); kv[key] val; } return kv; }调用时auto cfg LoadToml(config.toml);然后cfg[api_key]就能拿到 Key。这样你的 MFC 工具和后续脚本共用一份配置换 Key 只改一个文件。3. 可复制配置菜单资源与消息映射完整步骤3.1 方式一菜单资源 右键消息先在资源视图里右键Menu节点插入一个新菜单ID 设为IDR_MENU_POPUP下面加一个菜单项「删除行」ID 设为ID_MENU_DELETE_ROW。然后在对话框类里给 List Control 绑定变量假设变量名是m_List控件 ID 是IDC_LIST1。接着添加右键消息。在类向导里选中对话框类消息列表找到WM_CONTEXTMENU添加处理函数。实现如下void CMyMfcDlg::OnContextMenu(CWnd* pWnd, CPoint point) { // 只处理列表控件区域内的右键 if (pWnd-GetDlgCtrlID() ! IDC_LIST1) { CDialogEx::OnContextMenu(pWnd, point); return; } // 确保有选中行 int nItem m_List.GetSelectionMark(); if (nItem -1) return; CMenu menu; menu.LoadMenu(IDR_MENU_POPUP); CMenu* pSub menu.GetSubMenu(0); if (pSub) { pSub-TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this); } }注意TrackPopupMenu的坐标要用屏幕坐标WM_CONTEXTMENU传进来的point已经是屏幕坐标直接用即可。如果你在OnRButtonDown里自己取坐标记得ClientToScreen转换。菜单命令的响应函数这样写void CMyMfcDlg::OnMenuDeleteRow() { int nItem m_List.GetSelectionMark(); if (nItem -1) return; m_List.DeleteItem(nItem); // 删除后刷新选中状态避免残留高亮 int nCount m_List.GetItemCount(); if (nCount 0) { int nNew (nItem nCount) ? nItem : nCount - 1; m_List.SetItemState(nNew, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); } }3.2 方式二纯代码动态创建菜单不想动资源文件的话直接在OnContextMenu里CreatePopupMenu。这种方式适合菜单项需要根据选中行内容动态变化的场景void CMyMfcDlg::OnContextMenu(CWnd* pWnd, CPoint point) { if (pWnd-GetDlgCtrlID() ! IDC_LIST1) { CDialogEx::OnContextMenu(pWnd, point); return; } int nItem m_List.GetSelectionMark(); if (nItem -1) return; CMenu popup; popup.CreatePopupMenu(); popup.AppendMenu(MF_STRING, ID_MENU_DELETE_ROW, _T(删除行)); popup.AppendMenu(MF_SEPARATOR); popup.AppendMenu(MF_STRING, ID_MENU_REFRESH, _T(刷新列表)); popup.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this); popup.DestroyMenu(); }ID_MENU_DELETE_ROW和ID_MENU_REFRESH需要在resource.h里手动定义或者用#define写在你自己的头文件里值别和已有 ID 冲突#define ID_MENU_DELETE_ROW 32821 #define ID_MENU_REFRESH 32822然后在消息映射里手动加ON_COMMANDBEGIN_MESSAGE_MAP(CMyMfcDlg, CDialogEx) ON_WM_CONTEXTMENU() ON_COMMAND(ID_MENU_DELETE_ROW, CMyMfcDlg::OnMenuDeleteRow) ON_COMMAND(ID_MENU_REFRESH, CMyMfcDlg::OnMenuRefresh) END_MESSAGE_MAP()3.3 关键风格LVS_EX_FULLROWSELECT这一步很多人漏掉导致只能点第一列才选中。在OnInitDialog里加上BOOL CMyMfcDlg::OnInitDialog() { CDialogEx::OnInitDialog(); m_List.SetExtendedStyle(m_List.GetExtendedStyle() | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES); // 初始化列 m_List.InsertColumn(0, _T(名称), LVCFMT_LEFT, 150); m_List.InsertColumn(1, _T(状态), LVCFMT_LEFT, 100); return TRUE; }LVS_EX_FULLROWSELECT让整行可选中LVS_EX_GRIDLINES加网格线视觉上更像表格。3.4 DELETE 键删除LVN_KEYDOWN 消息右键菜单有了键盘删除还得单独接。在类向导里给IDC_LIST1添加LVN_KEYDOWN消息处理void CMyMfcDlg::OnLvnKeydownList1(NMHDR* pNMHDR, LRESULT* pResult) { LPNMLVKEYDOWN pLVKey reinterpret_castLPNMLVKEYDOWN(pNMHDR); if (pLVKey-wVKey VK_DELETE) { int nItem m_List.GetSelectionMark(); if (nItem ! -1) { m_List.DeleteItem(nItem); int nCount m_List.GetItemCount(); if (nCount 0) { int nNew (nItem nCount) ? nItem : nCount - 1; m_List.SetItemState(nNew, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); } } } *pResult 0; }注意LVN_KEYDOWN是 List Control 自己的通知不是对话框的WM_KEYDOWN。如果你在对话框的PreTranslateMessage里拦 DELETE得先判断焦点在不在列表上否则会误删。4. 验证请求与成功结果配置和代码都就位后按这个顺序验证第一步编译运行列表里插入三行测试数据。点击任意一行确认整行高亮LVS_EX_FULLROWSELECT生效。第二步在选中行上点右键菜单弹出位置在鼠标点击处。点「删除行」该行消失且下一行自动选中。第三步选中一行直接按键盘 DELETE 键行同样被删除。第四步验证 TaoToken 通道。在工程里加一个测试按钮调用一次模型对话接口确认 Key 和 base_url 配置正确#include winhttp.h #pragma comment(lib, winhttp.lib) std::string CallTaoToken(const std::string prompt) { auto cfg LoadToml(config.toml); std::string host Ltaotoken.net; std::string path L/api/v1/chat/completions; // 实际请求体 std::string body R({model:) cfg[model] R(,messages:[{role:user,content:) prompt R(}]}); // 这里用 WinHTTP 发 POSTHeader 带 Authorization: Bearer key // 返回 JSON 解析出 content 字段即可 return ok; }如果返回正常说明统一通道打通。模型对话入口在 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 你可以在网页端先试同样的 Key 能不能用排除本地代码问题。长期做编码类工具的话Coding Plan 页面 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 有更细的额度说明。5. 本篇常见错误排查右键菜单不弹检查WM_CONTEXTMENU是否真的映射到了对话框类而不是 List Control 子类。如果 List Control 是CListCtrl直接绑定消息会先到控件需要反射或者子类化。最稳的做法是在对话框的OnContextMenu里判断pWnd-GetDlgCtrlID()。菜单弹了但点删除没反应ON_COMMAND的 ID 和AppendMenu或资源里的 ID 不一致。资源方式检查IDR_MENU_POPUP下菜单项的 ID代码方式检查#define的值有没有和 MFC 内置 ID 冲突比如 0xE100 以上是系统保留区。删除后行号错乱或崩溃DeleteItem之后又用了旧的nItem去SetItemState。必须先GetItemCount判断剩余数量再取min(nItem, nCount-1)。DELETE 键无效LVN_KEYDOWN加在了对话框而不是 List Control 上。在类向导里选IDC_LIST1这个对象 ID再找LVN_KEYDOWN。另外确认列表有焦点点一下列表再按 DELETE。只能点第一列选中漏了LVS_EX_FULLROWSELECT。这个风格必须在插入列之前或之后尽早设置OnInitDialog里最合适。TaoToken 请求 401Key 没读到或者带了多余空格。LoadToml里已经做了去引号和空格检查config.toml里api_key那行有没有被注释掉。另外确认 base_url 是https://taotoken.net/api不要自己拼/v1之外的路径。中文菜单乱码AppendMenu用_T(删除行)工程字符集设成 Unicode。如果是多字节字符集用CString转换或者直接写删除行并确认源文件保存为 GB2312。6. 接入文档与后续扩展删除逻辑跑通后这个 MFC 工具的地基就算打好了。后面想加「右键导出选中行」「批量删除」「根据内容调用模型生成摘要」都只是在这条消息链路上挂新命令。TaoToken 的接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里面有完整的请求格式和错误码说明。如果你用的是 Claude Code 这类命令行工具做辅助开发Anthropic 兼容入口在 https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 配置方式类似把 base_url 和 Key 填进去就行。我自己的习惯是先把config.toml和LoadToml抽成一个独立的小模块所有需要联网的功能都走它这样换环境、换 Key、加超时都不用翻业务代码。List Control 这边删除前加一个MessageBox确认能避免误触批量删除时用for倒序遍历DeleteItem正序删会跳行。这些小细节堆起来工具才用得顺手。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。