ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

VC6股票软件源码实战:3秒实时行情刷新与列表更新

VC6股票软件源码实战:3秒实时行情刷新与列表更新 简介这是一套面向股票行情软件开发者与量化爱好者的VC6.0工程源代码核心解决股票列表实时行情自动刷新的问题实现每3秒刷新一次并以中远海控为例演示脱机使用场景数据接口对接腾讯股票实时行情数据。压缩包共2034个文件约64.12MB其中h与cpp头文件和源文件合计七百余个构成完整工程主体obj、sbr、pdb等为编译中间产物dsp、dsw、vcproj、sln等为VC6.0与VS工程配置bmp、gif、psd、ico等为界面资源dll、lib、exe为可执行与依赖库另有day、cel等行情数据文件目录结构完整便于直接编译与二次开发。目前已有1347人学习下载适合希望研究行情刷新机制、网络数据接口封装与MFC界面实现的读者参考可从中获取实时行情拉取、列表刷新调度及脱机数据回放等关键实现思路。1. 从一份 VC6 股票软件源码说起3 秒刷新一次行情到底难在哪很多人第一次拿到「策略为王股票软件源代码」这类 VC6 工程时第一反应是双击 .dsw 打开、编译、运行然后盯着那个股票列表看它到底会不会动。结果往往是界面出来了列表是空的或者数据永远停在某一刻不动。问题不在代码写得多烂而在于「实时行情刷新」这件事本身是一条完整的链路——数据从哪来、怎么解析、怎么塞进列表控件、怎么定时触发、怎么保证不卡界面。这份源码标题里写的「成功加入股票列表实时行情刷新3 秒刷新一次」本质上就是把这条链路跑通了而且把刷新周期定死在 3 秒。VC6.0 这个环境今天看很老但大量券商营业部、私募内部工具、老一代行情客户端都是它编译出来的读懂它比追新框架更有实战价值。这篇笔记面向两类人手里有类似 VC6 行情工程、想把它跑起来并改成自己节奏的开发者以及想搞清楚「股票列表 定时刷新」这套模式该怎么落地的人。下面按「先搞懂结构、再动手改、最后避坑」的顺序讲透。2. 拆开 VC6 工程股票列表和行情刷新是怎么接上的2.1 先认清 MFC 里那几个关键类VC6 时代的股票软件绝大多数是 MFC 单文档或多文档结构。你打开工程后先别急着看业务代码按下面顺序定位CxxxApp程序入口InitInstance里通常有主窗口创建和初始化。CxxxView或主对话框类股票列表控件CListCtrl一般挂在这里。一个行情数据类名字常见是CStockData、CQuote、CMarketData之类负责存每只股票的最新价、涨跌幅、成交量。一个定时器或线程负责周期性拉数据、刷新界面。判断刷新机制是定时器还是线程最快的办法是全局搜SetTimer、OnTimer、AfxBeginThread、CreateThread这几个关键字。标题说「3 秒刷新一次」那大概率能看到SetTimer(1, 3000, NULL)这样的调用或者线程里Sleep(3000)。2.2 股票列表的数据结构长什么样列表控件本身不存业务数据它只存显示文本。真正的最新价、昨收、涨跌通常放在一个struct或class数组里。典型结构是这样// 单只股票的行情结构字段名按你工程实际改 struct StockQuote { CString code; // 股票代码如 600000 CString name; // 股票名称 double lastPrice; // 最新价 double preClose; // 昨收价 double change; // 涨跌额 double changePct; // 涨跌幅 % long volume; // 成交量手 CString updateTime;// 最近刷新时间 };逻辑说明列表控件只负责「显示」StockQuote数组负责「数据」。刷新时先更新数组再把数组里的值写回CListCtrl的对应行。这样做的原因是行情更新频率高直接操作控件既慢又容易闪烁先改内存再统一刷界面是标准做法。参数说明lastPrice用double而不是float因为价格计算涉及涨跌幅float精度不够容易在 3 位小数上出现误差volume用long是因为单日成交量可能超过int在某些平台的范围虽然实际很少触顶但养成习惯。2.3 3 秒刷新是怎么触发的如果工程用的是SetTimer典型代码是这样// 在主窗口初始化里启动定时器 BOOL CMainFrame::PreCreateWindow(CREATESTRUCT cs) { if( !CFrameWnd::PreCreateWindow(cs) ) return FALSE; // 定时器 ID 为 1周期 3000 毫秒 SetTimer(1, 3000, NULL); return TRUE; } // 定时器消息处理 void CMainFrame::OnTimer(UINT nIDEvent) { if (nIDEvent 1) { // 1. 拉取最新行情可能是本地模拟或网络请求 theApp.m_quoteMgr.RefreshAll(); // 2. 把新数据刷到列表控件 theApp.m_pMainView-UpdateStockList(); } CFrameWnd::OnTimer(nIDEvent); }逻辑说明SetTimer的第三个参数是回调函数传NULL表示用WM_TIMER消息走OnTimer。3 秒是标题里明确写的周期改这个数字就能改刷新频率。RefreshAll负责取数据UpdateStockList负责刷界面两者分开是为了将来把取数据换成网络线程时界面部分不用大改。参数说明定时器 ID 用 1 是习惯多个定时器时别重复周期单位是毫秒3000 就是 3 秒。注意SetTimer的周期不是精确的系统忙的时候可能延迟到 3.1 秒甚至更多对行情展示够用但别拿它做交易触发。2.4 把刷新周期从 3 秒改成你要的值改周期是最常见的需求。如果代码里是硬编码的3000直接改数字即可。但更稳的做法是抽成常量或配置// 在 stdafx.h 或公共头文件里定义 #define QUOTE_REFRESH_INTERVAL_MS 3000 // 行情刷新周期单位毫秒 // 使用时 SetTimer(1, QUOTE_REFRESH_INTERVAL_MS, NULL);逻辑说明把周期抽成宏将来要改成 1 秒或 5 秒只改一处。如果工程里有多个地方用到这个周期比如状态栏也显示倒计时统一引用同一个宏能避免不一致。参数说明周期不建议低于 1000 毫秒。原因有两个一是多数免费行情接口本身有频率限制3 秒是常见下限二是 VC6 的 MFC 界面在 1 秒以下刷新时CListCtrl重绘会明显闪烁用户体验反而差。如果确实要更快得换双缓冲或虚拟列表那是另一个话题。3. 让列表真正动起来数据填充与界面刷新的完整步骤3.1 初始化列表控件列和行都要先建好CListCtrl默认是空的必须先插入列再插入行。典型初始化代码void CStockListView::InitStockList() { // 1. 设置扩展风格整行选中 网格线 m_listCtrl.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES); // 2. 插入列参数列序号、标题、对齐方式、宽度 m_listCtrl.InsertColumn(0, _T(代码), LVCFMT_LEFT, 80); m_listCtrl.InsertColumn(1, _T(名称), LVCFMT_LEFT, 100); m_listCtrl.InsertColumn(2, _T(最新价), LVCFMT_RIGHT, 80); m_listCtrl.InsertColumn(3, _T(涨跌幅), LVCFMT_RIGHT, 80); m_listCtrl.InsertColumn(4, _T(成交量), LVCFMT_RIGHT, 100); m_listCtrl.InsertColumn(5, _T(更新时间), LVCFMT_LEFT, 120); // 3. 按股票数量插入行先占位 for (int i 0; i m_stockArray.GetSize(); i) { m_listCtrl.InsertItem(i, m_stockArray[i].code); } }逻辑说明先插列再插行是CListCtrl的固定顺序。InsertItem只设置第一列文本后面列用SetItemText单独设。这里先占位是为了后面刷新时只改文本、不增删行避免列表跳动。参数说明LVS_EX_FULLROWSELECT让整行可选中LVS_EX_GRIDLINES加网格线这两个是行情列表的标配。列宽按实际调整LVCFMT_RIGHT用于数字列右对齐更符合看盘习惯。3.2 刷新函数只改文本不重建行刷新是每 3 秒执行一次的核心写法直接决定卡不卡void CStockListView::UpdateStockList() { // 禁止重绘减少闪烁 m_listCtrl.SetRedraw(FALSE); for (int i 0; i m_stockArray.GetSize(); i) { StockQuote q m_stockArray[i]; // 只更新第 2 列及之后的文本第 1 列代码不变 CString strPrice; strPrice.Format(_T(%.2f), q.lastPrice); m_listCtrl.SetItemText(i, 2, strPrice); CString strPct; strPct.Format(_T(%.2f%%), q.changePct); m_listCtrl.SetItemText(i, 3, strPct); CString strVol; strVol.Format(_T(%ld), q.volume); m_listCtrl.SetItemText(i, 4, strVol); m_listCtrl.SetItemText(i, 5, q.updateTime); } // 恢复重绘一次性刷新 m_listCtrl.SetRedraw(TRUE); m_listCtrl.Invalidate(); }逻辑说明SetRedraw(FALSE)和SetRedraw(TRUE)成对使用中间所有SetItemText都不会立即触发重绘最后统一刷一次这是 VC6 里抑制闪烁最有效的手段。循环里只改文本不调用InsertItem或DeleteItem保证行数稳定。参数说明Format里的%.2f控制价格两位小数%.2f%%里的%%是输出一个百分号。成交量用%ld对应long。如果股票数量上千这个循环每 3 秒跑一次会有压力后面第 5 章会讲优化。3.3 数据来源本地模拟还是网络拉取标题里没写数据从哪来但「实时行情刷新」通常有两种落地方式本地模拟用一个随机数或定时改动的数组验证刷新链路是否通。适合刚拿到源码、还没接数据源时。网络拉取通过 HTTP 请求行情接口解析返回的文本或 JSON更新m_stockArray。先跑通本地模拟再换网络是稳妥顺序。本地模拟的刷新函数可以这样写void CQuoteManager::RefreshAll() { for (int i 0; i m_stockArray.GetSize(); i) { StockQuote q m_stockArray[i]; // 用昨收价上下浮动模拟最新价 double delta (rand() % 200 - 100) / 100.0; // -1.00 ~ 1.00 q.lastPrice q.preClose delta; q.change q.lastPrice - q.preClose; q.changePct q.preClose 0 ? (q.change / q.preClose) * 100.0 : 0.0; q.volume rand() % 1000; // 记录刷新时间 CTime now CTime::GetCurrentTime(); q.updateTime now.Format(_T(%H:%M:%S)); } }逻辑说明rand()生成模拟波动preClose是基准保证价格不会乱飘。changePct做除零保护避免昨收为 0 时崩溃。时间用CTime格式化显示在列表最后一列方便肉眼确认 3 秒确实刷了一次。参数说明rand() % 200 - 100得到 -100 到 99 的整数除以 100.0 变成 -1.00 到 0.99 的浮点波动模拟日内小幅震荡。真实场景里这个波动范围要按股票实际振幅调整。3.4 把刷新链路串起来验证到这一步完整链路是OnTimer每 3 秒触发 →RefreshAll更新内存数组 →UpdateStockList把数组刷到列表。验证方法很直接运行程序看最后一列「更新时间」是不是每 3 秒跳一次同时最新价和涨跌幅在变。如果时间在跳但价格不动说明RefreshAll没被调用或数组没更新如果价格在变但界面不动说明UpdateStockList里的SetItemText没生效或控件句柄不对。4. 避坑与排查VC6 行情刷新最容易翻车的 5 个地方4.1 列表刷新时界面闪烁严重现象每次 3 秒刷新整个列表闪一下股票多了更明显。原因SetItemText每次调用都触发重绘循环里几十上百次重绘叠加。解决在刷新循环外包SetRedraw(FALSE)/SetRedraw(TRUE)最后Invalidate一次。如果还闪给列表控件加LVS_EX_DOUBLEBUFFER扩展风格部分 VC6 环境需手动声明常量。4.2 定时器不触发或只触发一次现象程序运行后列表一直不动OnTimer里的断点只命中一次。原因SetTimer的宿主窗口不对或者OnTimer没有正确映射消息。解决确认SetTimer是在一个存活的主窗口里调用且该窗口的消息映射里有ON_WM_TIMER()。如果定时器建在对话框上对话框关闭后定时器自然失效。4.3 网络拉取数据时界面卡死现象改成网络请求后每到刷新点界面就卡住一两秒鼠标转圈。原因网络请求是同步的跑在 UI 线程里请求期间消息循环被阻塞。解决把网络请求放到工作线程取到数据后用PostMessage通知 UI 线程刷新。VC6 里用AfxBeginThread起线程注意线程函数里不要直接操作控件。4.4 股票数量多时刷新越来越慢现象几十只股票时流畅几百只时每次刷新明显卡顿。原因SetItemText是逐个单元格操作行数一多总调用次数成倍增长。解决只刷新可见区域或者用虚拟列表LVS_OWNERDATA。简单做法是记录当前滚动位置只更新视口内的行。更彻底的是换CListCtrl为自绘控件但改动量大。4.5 编译报错找不到 MFC 头文件现象换机器打开工程编译提示afxwin.h找不到或MFC42D.DLL缺失。原因VC6 的 MFC 库没装全或者工程设置里的包含路径不对。解决确认安装 VC6 时勾选了 MFC在「工程设置 → C/C → 预处理器」里检查_AFXDLL是否按需定义运行时报 DLL 缺失把 MFC 的 Debug/Release DLL 放到可执行文件同目录。5. 进阶把 3 秒刷新做成可配置、可扩展的行情模块5.1 用配置项替代硬编码周期前面用宏解决了单点修改但更实用的做法是读配置文件。VC6 里读写 ini 最省事// 读取刷新周期默认 3000 毫秒 UINT GetRefreshInterval() { UINT nInterval GetPrivateProfileInt( _T(Quote), // 节名 _T(Interval), // 键名 3000, // 默认值 _T(.\\config.ini)); // 文件路径 // 兜底低于 1000 毫秒按 1000 处理 if (nInterval 1000) nInterval 1000; return nInterval; }逻辑说明GetPrivateProfileInt是 Win32 APIVC6 直接可用。配置文件里写[Quote]节和Interval3000改刷新频率不用重新编译。兜底判断防止有人填 0 或负数导致定时器异常。参数说明默认值 3000 保证配置文件缺失时仍能跑下限 1000 是经验值再低界面和接口都吃不消。5.2 刷新频率与数据源能力的匹配3 秒不是随便定的。免费行情接口通常限制每分钟请求次数3 秒一次等于每分钟 20 次多数接口能承受。如果改成 1 秒每分钟 60 次容易被限流甚至封 IP。判断方法连续跑 10 分钟看有没有请求失败或返回空数据。如果有把周期调回 3 秒或更长。刷新周期每分钟请求数适用场景1 秒60本地数据源或付费高频接口3 秒20免费接口、常规看盘5 秒12接口不稳定时的保守值10 秒6只关注趋势、不在意秒级变化5.3 一个我踩过的坑刷新和排序打架列表支持点击列头排序后刷新会打乱顺序。原因是排序改的是控件行序而m_stockArray没跟着变刷新时按数组下标写文本就写到了错误的行。解决办法是排序时同步重排数组或者刷新时按股票代码查找行号再写。我现在的习惯是只要列表允许排序刷新函数里一定用代码到行号的映射不直接用循环下标。这个坑当年调了一下午血泪经验。5.4 验证刷新是否真的每 3 秒一次别只靠肉眼看时间列。在OnTimer里加一个计数器每 100 次输出一次平均间隔void CMainFrame::OnTimer(UINT nIDEvent) { if (nIDEvent 1) { static int nCount 0; static DWORD dwLast GetTickCount(); nCount; if (nCount % 100 0) { DWORD dwNow GetTickCount(); DWORD dwAvg (dwNow - dwLast) / 100; TRACE(_T(平均刷新间隔: %lu ms\n), dwAvg); dwLast dwNow; } // ... 原有刷新逻辑 } CFrameWnd::OnTimer(nIDEvent); }逻辑说明GetTickCount返回系统运行毫秒数统计 100 次的总耗时再平均能看出实际间隔是否接近 3000。TRACE输出到 VC6 的输出窗口不影响界面。参数说明%lu对应DWORD。如果平均值明显大于 3000说明 UI 线程被其他操作拖慢需要检查刷新函数里有没有耗时操作。这套东西跑通之后你会发现「3 秒刷新」本身不难难的是让它在股票数量增长、数据源变化、用户操作干扰下依然稳定。我自己的习惯是任何定时刷新模块先写死周期跑通再抽配置最后加统计验证三步走完才敢往生产环境放。希望帮到你。本文还有配套的精品资源点击获取
返回列表