VC++开发股票软件
VC炒股软件开发
文档说明:
此文档适合VC++的初学者,高手也可参考(希望能提出宝贵意见)。
开发前准备:
这是一个根据股票信息的数据绘的几个制界面,数据来源通信达软件的数据文件,通常在安装了通信达以后并下载数据到本地以后就有了。
1.数据文件的准备:
假如你安装通信达的目录是:D:\jcb_gx。那么对应的数据文件就在D:\jcb_gx\vipdoc\目录下,里面每一个目录下就是一类股票的数据,我们开发这个界面需要用到的是每个目录下的lday目录下的.lday后缀名的文件。每一个文件里面存放的是一支滚票的数据信息。我当时开发用到了两类股票的信息,分别对应的目录是:D:\jcb_gx\vipdoc\sh\lday和D:\jcb_gx\vipdoc\sz\lday。其实每类开发的方法完全一样,唯一不同就是读取不同的目录而已。
2.文件数据结构:
准备好数据以后,还有一点是必须知道的,不然也没有办法进行下去,就是文件里面的数据格式是怎样的。因为我用的是通信达的数据文件,所以只需要在www.g.cn查询通信达的数据格式就可以了,如下:
1 2 3 4 5 6 7 8 9 10 11 | typedef struct { //共32字节 int date; //4字节 如20091229 int open; //开盘价 int high; //最高价 int low; //最低价 int close; //收盘价 float amount; //成交额 int vol; //成交量 int reservation; //保留值 } StockData; |
详细开发过程(包括我的思路和具体实现):
1.实现如上图的界面,需要做如下事情
(1)读一个目录下的所有文件,并从文件名中提取出相应股票的代码
可行性分析:
首先我们打开的是一个目录,然后从这个目录中读出里面所有的文件名,目录存放的内容其实就是此目录下的文件名或目录名。用到两个函数,一个FindFirstFile查找到一个目录下的第一个文件名,另一个FindNextFile查找下一个文件名。这样就可以遍历一个目录下的所有文件名了。
具体实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | BOOLCTongXinDaView::ReadFileData( LPCSTR path) { //path是这种形式的参数:D:\\jcb_gx\\vipdoc\\sh\\lday\\* m_iCount = 0; WIN32_FIND_DATA tFind = {0}; int i = 0; CString strTemp; HANDLE hSearch = ::FindFirstFile(path, &tFind); if (hSearch == INVALID_HANDLE_VALUE) { return FALSE; } //过滤掉.和..文件 ::FindNextFile(hSearch, &tFind); while (::FindNextFile(hSearch, &tFind)) { strTemp.Format( "%s" ,tFind.cFileName); m_File[i].Format( "%s" ,path); //去掉查询用到的*通配符 m_File[i] =m_File[i].Left(m_File[i].GetLength()-1); m_File[i] +=strTemp; //从文件名中提取股票代码 m_FileName[i] =strTemp.Mid(2, 6); i++; } m_iPageCount = i / 31 + 1; //求出需要显示的总页面数 m_iLeave = i % 31; //最后一页显示的数据 m_CurrFile = m_File[m_iCount]; //保存选中的文件名 ::FindClose(hSearch); return TRUE; } |
注意事项:每一个目录下都有这两个目录文件:“.”和“..”。它们分别代表本目录和父目录(就是上层目录),必须过滤掉这两个目录文件。还好每次这两个目录文件总是最先被读出,所以前两次读出来的信息直接不管就可以了。
上面的函数被相应的每一个菜单项事件调用,就是针对不同的股票用一个菜单项打开。
(2)页面的显示:
可行性分析:
先说说我当时需要完成的现实任务,每页显示31行(具体可以变动,但是31 行效果比较好),显示3列,第一列索引号,也就是起个计数的作用,第二列就是刚才我们提取到的股票代码号,第三列随便填充4个汉字。还要求画一条线表示当前选中的股票,鼠标上下滚动和PageDown,PageUp按键实现上下翻页功能,鼠标点击选中点击最近的一支股票,按键上下键也可以移动股票选择。明白了需要实现的功能,我现在就一步一步来完成。这里需要用到文字输出函数DrawText。
具体实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | void CTongXinDaView::DrawText(CDC *pDC, int page) { CRect rt; GetClientRect(&rt); int high = rt.Width() / 55; int y = high; //控制每一行显示数据的增量 CString strLine; int number = 1; pDC->SetBkMode(TRANSPARENT); pDC->SetTextColor(RGB(200, 200, 200)); strLine.Format( " 代码 名称 日期 开牌价 最高价 最低价 收盘价 多多 成交量 " ); pDC->TextOut(0, 0, strLine); //控制最后一页只显示剩余的 if (page == m_iPageCount-1) { for ( int i=page * SCREENHEIGHT; i<(page*SCREENHEIGHT + m_iLeave); i++) { if (i == m_iCount) { CPen pen(PS_SOLID, 1, RGB(255, 255, 0)); CPen *pOldPen = pDC->SelectObject(&pen); pDC->MoveTo(50, y+high-5); pDC->LineTo(rt.right, y+high-5); pDC->SelectObject(pOldPen); } strLine.Format( "%d" , i+1); pDC->DrawText(strLine, CRect(0, y, 40, y+high), DT_RIGHT); pDC->DrawText(m_FileName[i], CRect(50, y, 100, y+high), DT_LEFT); pDC->DrawText( "长城开发" , CRect(110, y, 180, y+high), DT_LEFT); y = y + high; } } else { for ( int i=page * SCREENHEIGHT; i<(page+1)*SCREENHEIGHT; i++) { if (i == m_iCount) { CPen pen(PS_SOLID, 1, RGB(255, 255, 0)); CPen *pOldPen = pDC->SelectObject(&pen); pDC->MoveTo(50, y+high-7); pDC->LineTo(rt.right, y+high-7); pDC->SelectObject(pOldPen); } strLine.Format( "%d" , i+1); pDC->DrawText(strLine, CRect(0, y, 40, y+high), DT_RIGHT); pDC->DrawText(m_FileName[i], CRect(50, y, 100, y+high), DT_LEFT); pDC->DrawText( "长城开发" , CRect(110, y, 180, y+high), DT_LEFT); y = y + high; } } } |
注意事项:
1.最后一页数据条目不够,需要特殊处理。
2.输出函数用的是DawText而不是TextOut,是为了使输出对齐。
3.鼠标和按键的响应只是简单逻辑处理和显示不同的数据。
(3)分页显示和选取当前一支股票的实现思想说明:
可行性分析:
上面已经实现了页面的显示,现在说说怎样控制上下翻页和鼠标键盘实现选中一支股票(我是用一条黄色的线标示)。上下翻页时通过键盘上的PageDown和PageUp,还有鼠标滚轮控制的。其实原理很简单,只需要我们在读目录下每个股票文件时记录一下这个目录下的股票数量,也就是我们需要显示的所有行数。我们一个常量记录每一页显示的数目,用总数除以这个数就是总共需要的页数。然后用一个变量记录当前显示的是第几页,上下翻页就是对这个变量的加减操作了。选中一支股票则是根据我们点击的鼠标的位置来决定,因为每一行所占的页面宽度是一样的,只需要判断点击在哪一行所处的位置就可以了。当然也需要用变量记录选中的是那一只股票,总数刚才我们也记录了,所以很容易记录当前的哪一只股票,只是需要注意翻页后选择的股票相应的加减一页的显示的股票数。最后一点就是注意一些边界条件的处理。
2.实现如下图的界面:
(1)读取选中的股票文件,并保存以为绘图使用这些数据
可行性分析:
按照固定的数据格式把文件中的数据读入到一个结构体中保存,用fread每次读入固定长度的数据格式接可以了。
具体实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | /* * 函数名称: ReadData * 输 入: * 输 出: * 功能描述: 从当前文件中读取数据 * 全局变量: * 作 者: 吴友强 * 日 期: 2009年11月29日 * 修 改: * 日 期: */ void CTongXinDaView::ReadData() { FILE *fp; m_iDataItemCount = -1; //打开当前文件 if ((fp = fopen (m_CurrFile, "rb" )) == NULL) { return ; } while (! feof (fp)) { m_iDataItemCount++; fread (&m_StockData[m_iDataItemCount], sizeof (StockData),1,fp); //求最大的日期 if (m_StockData[m_iDataItemCount].date > m_iMaxDate) { m_iMaxDate = m_StockData[m_iDataItemCount].date; } //求最小的日期 if (m_iDataItemCount == 0) { m_iMinDate = m_StockData[m_iDataItemCount].date; } else if (m_StockData[m_iDataItemCount].date > 0 && m_StockData[m_iDataItemCount].date < m_iMinDate) { m_iMinDate = m_StockData[m_iDataItemCount].date; } } m_iDataItemCount--; //去掉最后一条无用的记录 m_iStartDay = m_iDataItemCount; if (m_iDays > m_iDataItemCount) { m_iDays = m_iDataItemCount+1; } //以前在这里没有关闭文件,所以当打开一定数量的时候(windows限制的)在打开文件就会失败 fclose (fp); } |
(2)提取当前需要显示的数据:
可行性分析:
根据当前需要显示多少天的数据来提取,从上面我们从文件里面读取的数据中提取,以后的所谓放大缩小,左右移动就是提取不同天的数据就是了。
具体实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | /* * 函数名称: GetStockData * 输 入: days * days: 屏幕需要显示的天数 * 输 出: * 功能描述: 得到显示的数据和求取各个最值 * 全局变量: * 作 者: 吴友强 * 日 期: 2009年11月30日 * 修 改: * 日 期: */ void CTongXinDaView::GetStockData( int days) { m_iLowMax = 0; m_iHighMax = 0; m_iVolMax = 0; if (m_sdCurrData != NULL) { delete m_sdCurrData; } m_sdCurrData = new StockData[days]; for ( int i=0; i<days; i++) { m_sdCurrData[days-i-1] = m_StockData[m_iStartDay-i-1]; //求本次显示成交量的最大值 if (m_sdCurrData[days-i-1].vol > m_iVolMax) { m_iVolMax = m_sdCurrData[days-i-1].vol; } //求本次显示最高值的最大值 if (m_sdCurrData[days-i-1].high > m_iHighMax) { m_iHighMax = m_sdCurrData[days-i-1].high; } //求本次显示最低值的最小值 if (i == 0) { m_iLowMax = m_sdCurrData[days-i-1].low; } else if (m_sdCurrData[days-i-1].low < m_iLowMax) { m_iLowMax = m_sdCurrData[days-i-1].low; } } } |
(3)得到需要显示的数据以后,我们就可以开始绘图了
可行性分析:
我们得到需要显示的数据以后,就需要根据当前显示的宽度和高度来划分屏幕了,根据客户的需求大致需要把屏幕分为上中下三部分,第一部分画成交量的平均值线和一天中的最高值到最低值的一条竖线,还有开盘价和收盘价的矩形图;第二部分成交总量;第三部分成交价格除以2,3,4,5刻度控制在-4到4资料的线形图。首先我们必须确定三部分的高度,然后把3个坐标固定下来并绘画出来,至于坐标的刻度我们可以动态的根据每次需要显示的数据的最大值和最小值来计算确定,然后根据刻度的比例来画所有的图形。其中很多需要计算,具体的情看代码。下面是整个系统的黑心部分,具体请看代码注释。
具体实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 | /* * 函数名称: DrawGraphic * 输 入: pDC, days * pDC: 画图的CDC指针 * days: 显示数据的天数 * 输 出: * 功能描述: 画各种坐标以及图形 * 全局变量: * 作 者: 吴友强 * 日 期: 2009年11月29日 * 修 改: 吴友强 * 日 期: 2009年12月4日 */ void CTongXinDaView::DrawGraphic(CDC *pDC, int days) { //设置透明绘图模式 pDC->SetBkMode(TRANSPARENT); pDC->SetTextColor(RGB(200, 0 ,0)); CRect rt; GetClientRect(&rt); float average; float averPri; float ftemp, ftext; CString str; int i = 0; float xAver; pDC->DrawText( "当前代码: " + m_FileName[m_iCount], CRect(0, 0, 200, 20), DT_LEFT); //初始化各个坐标原点 m_ptOrigin[0].x = rt.Width()-100; m_ptOrigin[0].y = rt.Height()/8 * 3; m_ptOrigin[1].x = rt.Width()-100; m_ptOrigin[1].y = rt.Height()/4 * 2; m_ptOrigin[2].x = rt.Width()-100; m_ptOrigin[2].y = rt.Height()/4 * 3; //划分屏幕为三部分 CPen penRedSolid(PS_SOLID, 1, RGB(200, 0, 0)); CPen *pOldPen = pDC->SelectObject(&penRedSolid); pDC->MoveTo(0, m_ptOrigin[0].y); pDC->LineTo(rt.Width(), m_ptOrigin[0].y); pDC->MoveTo(0, m_ptOrigin[1].y); pDC->LineTo(rt.Width(), m_ptOrigin[1].y); pDC->MoveTo(0, m_ptOrigin[2].y); pDC->LineTo(rt.Width()-100, m_ptOrigin[2].y); pDC->MoveTo(rt.Width()-100, 0); pDC->LineTo(rt.Width()-100, rt.Height()); //每一天显示的宽度xAver xAver = (rt.Width()-100) / ( float )days; //平均刻度代表的价格 averPri = (m_iHighMax-m_iLowMax) / 5.0 / 100; //第一条价格起始线 ftemp = m_iLowMax/ 100.0; //刻度线的距离 average = m_ptOrigin[0].y / 6.0; CPen penRedDot(PS_DOT, 1, RGB(200, 0, 0)); //画K线坐标 for (i=0; i<5; i++) { pDC->SelectObject(&penRedDot); pDC->MoveTo(0, average*(i+1)); pDC->LineTo(rt.Width()-100, average*(i+1)); pDC->SelectObject(&penRedSolid); pDC->LineTo(rt.Width()-100+10, average*(i+1)); pDC->MoveTo(rt.Width()-100, average*(i+1) + average/2); pDC->LineTo(rt.Width()-100+5, average*(i+1) + average/2); pDC->MoveTo(rt.Width()-100, average*(i+1) + average/4); pDC->LineTo(rt.Width()-100+3, average*(i+1) + average/4); pDC->MoveTo(rt.Width()-100, average*(i+1) + average/4*3); pDC->LineTo(rt.Width()-100+3, average*(i+1) + average/4*3); str.Format( "%10.2f" , ftemp + averPri * (5-i)); pDC->DrawText(str, CRect(rt.Width()-100, average*(i+1)-7, rt.Width(), average*(i+1)+10), DT_LEFT); } CPen penGreen(PS_SOLID, 1, RGB(0, 200, 0)); CBrush brush(NULL_BRUSH); CBrush brushGreen(RGB(0, 200, 0)); CBrush *pOldBrush = pDC->SelectObject(&brush); //画每天最低到最高的线,开盘和收盘的矩形 for (i=0; i<days; i++) { if (m_sdCurrData[i].open <= m_sdCurrData[i].close) { pDC->SelectObject(&penRedSolid); pDC->SelectObject(&brush); pDC->MoveTo(xAver * i+xAver/3, average*5- average/averPri*(m_sdCurrData[i].high/100.0-ftemp)); pDC->LineTo(xAver * i+xAver/3, average*5- average/averPri*(m_sdCurrData[i].low/100.0-ftemp)); pDC->Rectangle(xAver*i, average*5-average/averPri*(m_sdCurrData[i].open/100.0- ftemp), xAver*i+xAver/3*2, average*5-average/averPri*(m_sdCurrData[i].close/100.0- ftemp)); } else { pDC->SelectObject(&penGreen); pDC->SelectObject(&brushGreen); pDC->MoveTo(xAver * i+xAver/3, average*5- average/averPri*(m_sdCurrData[i].high/100.0-ftemp)); pDC->LineTo(xAver * i+xAver/3, average*5- average/averPri*(m_sdCurrData[i].low/100.0-ftemp)); pDC->Rectangle(xAver*i, average*5-average/averPri*(m_sdCurrData[i].close/100.0- ftemp), xAver*i+xAver/3*2, average*5-average/averPri*(m_sdCurrData[i].open/100.0- ftemp)); } if (!m_bMouseMove) { if (m_iDrawCount == i) { m_ptSavePoint.x = xAver * i+xAver/3; m_ptSavePoint.y = average*5-average/averPri*(m_sdCurrData[i].open/100.0-ftemp); } } } //计算5日平均值和10日平均值 int *fiveAverData = new int [days]; int *tenAverData = new int [days]; for (i=0; i<days; i++) { if (i >= 4) { fiveAverData[i] = (m_sdCurrData[i].close + m_sdCurrData[i-1].close +m_sdCurrData[i-2].close + m_sdCurrData[i-3].close + m_sdCurrData[i-4].close) / 5; } else { fiveAverData[i] = m_sdCurrData[i].close * 5 / 5; } if (i >= 9) { tenAverData[i] = (m_sdCurrData[i].close + m_sdCurrData[i-1].close +m_sdCurrData[i-2].close + m_sdCurrData[i-3].close + m_sdCurrData[i-4].close + m_sdCurrData[i-5].close + m_sdCurrData[i-6].close +m_sdCurrData[i-7].close + m_sdCurrData[i-8].close + m_sdCurrData[i-9].close) / 10; } else { tenAverData[i] = m_sdCurrData[i].close * 10 / 10; } } //画5日均线和10日均线 CPen penWhite(PS_SOLID, 1, RGB(200, 200, 200)); CPen penYellow(PS_SOLID, 1, RGB(200, 200, 0)); for (i=1; i<days; i++) { pDC->SelectObject(&penWhite); pDC->MoveTo(xAver * (i-1)+xAver/3, average*5-average/averPri*(fiveAverData[i-1]/100.0-ftemp)); pDC->LineTo(xAver * i+xAver/3, average*5-average/averPri*(fiveAverData[i]/100.0-ftemp)); pDC->SelectObject(&penYellow); pDC->MoveTo(xAver * (i -1)+xAver/3, average*5-average/averPri*(tenAverData[i-1]/100.0-ftemp)); pDC->LineTo(xAver * i+xAver/3, average*5-average/averPri*(tenAverData[i]/100.0-ftemp)); } //画柱状成交量的坐标和柱状图 average = (m_ptOrigin[1].y - m_ptOrigin[0].y) / 5; float averVol = m_iVolMax / 4.0; int temp = m_iVolMax / 4 / 100000 * 1000; //显示刻度用的临时变量 pDC->SelectObject(&penRedSolid); pDC->SelectObject(&brush); str.Format( "%s" , " X100" ); pDC->Rectangle(rt.Width()-100, m_ptOrigin[1].y-10, rt.Width()-50, m_ptOrigin[1].y+5); pDC->DrawText(str, CRect(rt.Width()-100, m_ptOrigin[1].y-10, rt.Width(), m_ptOrigin[1].y+5), DT_LEFT); for (i=0; i<4; i++) { pDC->SelectObject(&penRedDot); pDC->MoveTo(0, m_ptOrigin[0].y + average*(i+1)); pDC->LineTo(rt.Width()-100, m_ptOrigin[0].y + average*(i+1)); pDC->SelectObject(&penRedSolid); pDC->LineTo(rt.Width()-100+10, m_ptOrigin[0].y + average*(i+1)); pDC->MoveTo(rt.Width()-100, m_ptOrigin[0].y + average*(i+1) + average / 2); pDC->LineTo(rt.Width()-100+5, m_ptOrigin[0].y + average*(i+1) + average / 2); str.Format( "%10d" , temp * (4-i)); pDC->DrawText(str, CRect(rt.Width()-100, m_ptOrigin[0].y + average*(i+1)-7, rt.Width(), m_ptOrigin[0].y + average*(i+1)+10), DT_LEFT); } for (i=0; i<days; i++) { if (m_sdCurrData[i].open <= m_sdCurrData[i].close) { pDC->SelectObject(&penRedSolid); pDC->SelectObject(&brush); } else { pDC->SelectObject(&penGreen); pDC->SelectObject(&brushGreen); } pDC->Rectangle(xAver*i, m_ptOrigin[1].y - m_sdCurrData[i].vol / averVol * average, xAver*i+xAver/3*2, m_ptOrigin[1].y); } //1.画收盘价四色线的刻度值 average = (m_ptOrigin[2].y - m_ptOrigin[1].y) / 5; str.Format( "%8d" , 0); pDC->DrawText(str, CRect(rt.Width()-100, m_ptOrigin[2].y-7, rt.Width(), m_ptOrigin[2].y+10), DT_LEFT); for (i=0; i<4; i++) { pDC->SelectObject(&penRedDot); pDC->MoveTo(0, m_ptOrigin[1].y + average*(i+1)); pDC->LineTo(rt.Width()-100, m_ptOrigin[1].y + average*(i+1)); pDC->SelectObject(&penRedSolid); pDC->LineTo(rt.Width()-100+10, m_ptOrigin[1].y + average*(i+1)); for ( int j=0; j<10; j++) { pDC->MoveTo(rt.Width()-100, m_ptOrigin[1].y + average*(i+1) + average / 10 *(j+1)); pDC->LineTo(rt.Width()-100+5, m_ptOrigin[1].y + average*(i+1) + average / 10*(j+1)); } pDC->SelectObject(&penRedDot); pDC->MoveTo(0, m_ptOrigin[2].y + average*(i+1)); pDC->LineTo(rt.Width()-100, m_ptOrigin[2].y + average*(i+1)); pDC->SelectObject(&penRedSolid); pDC->LineTo(rt.Width()-100+10, m_ptOrigin[2].y + average*(i+1)); for (j=0; j<10; j++) { pDC->MoveTo(rt.Width()-100, m_ptOrigin[2].y + average*i + average / 10 *(j+1)); pDC->LineTo(rt.Width()-100+5, m_ptOrigin[2].y + average*i + average / 10*(j+1)); } str.Format( "%8d" , 4-i); pDC->DrawText(str, CRect(rt.Width()-100, m_ptOrigin[1].y + average*(i+1)-7, rt.Width(), m_ptOrigin[1].y + average*(i+1)+10), DT_LEFT); str.Format( "%8d" , -(i+1)); pDC->DrawText(str, CRect(rt.Width()-100, m_ptOrigin[2].y + average*(i+1)-7, rt.Width(), m_ptOrigin[2].y + average*(i+1)+10), DT_LEFT); } //2.计算四线的点值 float *fPrice1 = new float [days]; float *fPrice2 = new float [days]; float *fPrice3 = new float [days]; float *fPrice4 = new float [days]; for (i=0; i<days; i++) { fPrice1[i] = m_sdCurrData[i].close / 100.0 / 2 - m_sdCurrData[i].close / 100 / 2 + m_sdCurrData[i].close / 100 / 2 % 4; fPrice2[i] = m_sdCurrData[i].close / 100.0 / 3 - m_sdCurrData[i].close / 100 / 3 + m_sdCurrData[i].close / 100 / 3 % 4; fPrice3[i] = m_sdCurrData[i].close / 100.0 / 4 - m_sdCurrData[i].close / 100 / 4 + m_sdCurrData[i].close / 100 / 4 % 4; fPrice4[i] = m_sdCurrData[i].close / 100.0 / 5 - m_sdCurrData[i].close / 100 / 5 + m_sdCurrData[i].close / 100 / 5 % 4; } for (i=0; i<days-1; i++) { pDC->SelectObject(&penWhite); pDC->MoveTo(xAver*i, m_ptOrigin[2].y-average * fPrice1[i]); pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y-average *fPrice1[i+1]); pDC->MoveTo(xAver*i, m_ptOrigin[2].y+average * fPrice1[i]); pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y+average *fPrice1[i+1]); pDC->SelectObject(&penYellow); pDC->MoveTo(xAver*i, m_ptOrigin[2].y-average * fPrice2[i]); pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y-average *fPrice2[i+1]); pDC->MoveTo(xAver*i, m_ptOrigin[2].y+average * fPrice2[i]); pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y+average *fPrice2[i+1]); pDC->SelectObject(&penRedSolid); pDC->MoveTo(xAver*i, m_ptOrigin[2].y-average * fPrice3[i]); pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y-average *fPrice3[i+1]); pDC->MoveTo(xAver*i, m_ptOrigin[2].y+average * fPrice3[i]); pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y+average *fPrice3[i+1]); pDC->SelectObject(&penGreen); pDC->MoveTo(xAver*i, m_ptOrigin[2].y-average * fPrice4[i]); pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y-average *fPrice4[i+1]); pDC->MoveTo(xAver*i, m_ptOrigin[2].y+average * fPrice4[i]); pDC->LineTo(xAver*(i+1), m_ptOrigin[2].y+average *fPrice4[i+1]); } DrawDateText(pDC); //释放动态分配的内存 delete fiveAverData; delete tenAverData; delete fPrice1; delete fPrice2; delete fPrice3; delete fPrice4; //还原绘画环境 pDC->SelectObject(pOldPen); pDC->SelectObject(pOldBrush); } |
说明:上面最麻烦的是数据的处理,如果处理不好就不能把图画到合适的位置,所以计算的时候一定要用float,不然就算是很小的数据差别都会造成绘图不到指定的位置。还有这个函数代码量比较多,是因为绘图都需要用到平均每天的宽度和一些公用的参数,所以就在一个函数里完成了。其实可以写成很多个函数模块,比如每一个部分可以写成一个函数,然后数据计算可以用专门的函数封装。
(3)补充功能,在最下面显示当前鼠标对应的日期
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | /* * 函数名称: DrawDateText * 输 入: pDC * pDC: 绘画用的CDC指针 * 输 出: * 功能描述: 显示日期 * 全局变量: * 作 者: 吴友强 * 日 期: 2009年12月02日 * 修 改: * 日 期: */ void CTongXinDaView::DrawDateText(CDC *pDC) { CRect rt; GetClientRect(&rt); float xAver; CString strDate; xAver = (rt.Width()-100) / ( float )m_iDays; for ( int i=0; i<m_iDays; i++) { if ((m_ptDatePoint.x > xAver * i) && (m_ptDatePoint.x < (xAver * i+xAver / 3 *2))) { m_iDateCount = i; } } CPen pen(PS_SOLID, 1, RGB(200, 0, 0)); CPen *pOldPen = pDC->SelectObject(&pen); CBrush brushBlue(RGB(0, 0, 150)); CBrush *pOldBrush = pDC->SelectObject(&brushBlue); pDC->SetTextColor(RGB(200, 200, 200)); pDC->MoveTo(0, rt.Height()-15); pDC->LineTo(rt.Width()-100, rt.Height()-15); strDate.Format( "%d" , m_sdCurrData[m_iDateCount]); strDate.Insert(4, '/' ); strDate.Insert(7, '/' ); pDC->Rectangle(m_ptDatePoint.x, rt.Height()-15, m_ptDatePoint.x+75, rt.Height()); pDC->DrawText(strDate, CRect(m_ptDatePoint.x, rt.Height()-15, m_ptDatePoint.x+75, rt.Height()), DT_LEFT); pDC->SelectObject(&pOldPen); pDC->SelectObject(&pOldBrush); } |
说明:为了显示出这个日期,需要记录当前屏幕绘画了哪些天的图,然后根据鼠标的坐标位置判断处于哪一天并显示出来。
(4)图形放大缩小以及左右平移的实现思路:
其实原理都是一样的,就是根据需要的天数显示,放大就是显示的天数比较少,缩小就是多显示一些天数,左右移动就是重新提取一些数据。显示的天数可以根据需要按照固定需要按固定的比例放大和缩小。然后就是通过一些按键来控制或是鼠标控制,其中还有一个辅助线的设置比较靠逻辑,需要细心才能弄好。具体的请参看代码,有注释。
3.心得体会:
刚开始的时候感觉什么都不会,但是我还是勇敢地迈出了自己的第一步,因为在这几年的学习过程中我发现自己解决问题的能力提升了很多,所以相信自己能够完成。然后自己就静下心来一行一行代码的写,经过几天努力,完成了大部分功能,自己的信心也是越来越强。现在这个程度还算过的去了吧。
4.存在的不足:
由于对于股票行业术语不是很了解,很多变量的命名不是很合理和规范,代码的组织也不是很好。
5.得到的帮助与指导:
感谢老师提供的思路,特别是对于所谓的图形放大和缩小,左右移动,老师的提醒使我恍然大悟,以后基本上没有什么太大的困难。就是数据的提取问题。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架