我接触的绘制有两种:gdi+和qt绘图。可以灵活的绘制任何想要的东西。
先上效果图吧。
如下:基于gdi+的股指和股票的绘制。上面是沪深成分股实时生成的股票指数走势,下面是IF主力走势和开平仓位置。
如下,基于qt绘图的期货数据显示,模仿的博易大师。
现在贴上代码:
1、gdi+
#pragma once
#include <comdef.h>
#include <GdiPlus.h>
#include <Windows.h>
#include <map>
#include <list>
#include <vector>
#include <thread>
#include "my_protocol_struct.h"
using namespace Gdiplus;
using namespace std;
#pragma comment(lib, "gdiplus.lib")
// 其他指标信息容器
int iColorArr[];
struct OtherInfo
{
char pName[MAX_PATH];
Color color;
int penStyle;
double dBound;
bool bShow;
list<double>* listInfo; // 其他辅助信息
OtherInfo()
{
penStyle = 0;
dBound = 1.5f;
pName[0] = 0;
color = iColorArr[0];
bShow = true;
listInfo = NULL;
}
};
// 选中后需要显示的其他信息
struct OtherShowInfo
{
char pName[MAX_PATH];
double dValue;
Color color;
};
class DrawModule
{
public:
DrawModule();
~DrawModule();
private:
RECT m_rc;
unsigned int m_nOffset;
//Gdi初始化变量
GdiplusStartupInput m_Gdistart;
ULONG_PTR m_GdiplusToken;
//透明度
BYTE m_Transparency;
// 画布
Bitmap *bitmap_background;
Bitmap *bitmap_kline_index;
Bitmap *bitmap_user_show_info;
//双缓存绘图
Gdiplus::Graphics *drawObj;
// 当前屏幕上的最大最小值
double m_dMax;
double m_dMin;
int nIntervalTime;
int RIGHT_SPACE;
bool bOtherGreen;
int m_minCycle;
int m_maxCycle;
SIndexData m_pankou;
int m_nSelectKlineDate;
int m_nSelectKlineTime;
HANDLE m_hEvent;
bool m_bThread;
shared_ptr<thread> m_thdDraw;
public:
// 显示十字光标
int m_nShowCursel;
bool m_bSetOther;
private:
list<Skline>* m_pListKline;
list<Skline>* m_pCompareListKline;
vector<OtherInfo> m_vecOtherInfo;
vector<STradeData>* m_vecTradeData;
//vector<OpenDot> m_vecOpenDot;
private:
// 绘制背景色等
bool PreDrawInfo();
// 绘制坐标线
void DrawBackGround();
// 绘制数字
void DrawNumber();
// 绘制K线
void DrawKLine(bool bDrawAll = true);
// 局部重绘
void DoDrawAll();
// 数据转点
Point DataToPoint(double dValue,int nCount,double dCurMin,double dUnit);
void DrawOther(bool bDrawAll = true);
bool IsSameCycle(int nTime, int nKlineTime);
void ThreadDraw();
public:
// 初始化传入控件句柄
void IniDrawModule(HWND hWnd);
//是否半透明
void SetTransparency(BYTE b);
// 塞入想要绘制的信息
// K线 其他信息 NULL结尾
void SetInfoToDraw(list<Skline>* pList);
//
void SetCompareKline(list<Skline>* pList);
// 清空指标容器
void ClearOther();
// 塞入指标
void SetOtherInfo(OtherInfo& pInfo);
// 盘口数据
void SetPankou(SIndexData& data);
// 设置开平仓点
void SetTradeDot(vector<STradeData>* p);
// 设置其他的绿柱颜色
void SetOtherGreenColor();
// 设置纵向周期 秒数
void SetCrossCycle(int min_cycle, int max_cycle);
// 放大缩小重置间距
void DefaultResetInterval(bool bAdd);
// 左移和右移重置偏移量
void DefaultResetOffset(bool bLeft);
// 需要显示的信息
bool InfoToShow(unsigned int nX, unsigned int nY);
// 获取点击的k线
Skline* GetSelectKline(unsigned int nX);
// 左移和右移重置偏移量
void PercentResetOffset(unsigned int nPercent);
// 执行绘制
void DoDraw();
//开始线程
void Start();
// 结束绘制
void Stop();
};
// GDIPLUS4KLINE.cpp : 定义 DLL 应用程序的导出函数。
//
#include "stdafx.h"
#include "GDIPLUS4KLINE.h"
#include <Windows.h>
#define TOP_INFO_SPACE 30
int iColorArr[] = {Color::Yellow,Color::Brown,Color::DarkGoldenrod,Color::Magenta,Color::Blue,Color::Purple,Color::White,
Color::DarkOrange,Color::DarkRed,Color::DarkMagenta,Color::DarkGray,Color::DarkBlue,Color::Gray,
Color::LightPink,Color::LightSalmon,Color::LightYellow,Color::LightGray,Color::LightSkyBlue,Color::LightSeaGreen};
int iInterval[] = {1,1,2,3,4,5,6,7,13,19,25,35};
int iSpace[] = {0,1,1,1,1,1,2,3, 4, 4, 4, 6};
static int to_second(int time)
{
int nHour = time / 10000;
int nMini = time / 100 - nHour * 100;
int nSec = time - nHour * 10000 - nMini * 100;
return nHour * 60 * 60 + nMini * 60 + nSec;
}
DrawModule::DrawModule()
{
nIntervalTime = 4;
m_nOffset = 0;
m_pListKline = NULL;
m_dMax = DBL_MIN;
m_dMin = DBL_MAX;
bitmap_background = NULL;
bitmap_kline_index = NULL;
bitmap_user_show_info = NULL;
drawObj = NULL;
m_nShowCursel = 0;
bOtherGreen = false;
m_Transparency = 255;
RIGHT_SPACE = 80;
m_minCycle = 0;
m_maxCycle = 0;
m_nSelectKlineDate = 0;
m_nSelectKlineTime = 0;
GdiplusStartup(&m_GdiplusToken,& m_Gdistart,NULL);
m_vecTradeData = nullptr;
m_pCompareListKline = nullptr;
m_pankou = { 0 };
m_bThread = true;
m_bSetOther = false;
m_thdDraw = nullptr;
m_hEvent = ::CreateEvent(nullptr, false, false, nullptr);
}
DrawModule::~DrawModule()
{
GdiplusShutdown(m_GdiplusToken);
}
void DrawModule::IniDrawModule(HWND hWnd)
{
if (!drawObj)
{
drawObj = new Graphics(hWnd);
}
else
{
delete drawObj;
drawObj = new Graphics(hWnd);
}
if (bitmap_user_show_info!=NULL) { delete bitmap_user_show_info; }
bitmap_user_show_info = new Bitmap(m_rc.right-m_rc.left,m_rc.bottom-m_rc.top);
GetClientRect(hWnd,&m_rc);
DrawBackGround();
if (RIGHT_SPACE != 0)
RIGHT_SPACE = 65;
}
void DrawModule::SetTransparency(BYTE b)
{
m_Transparency = b;
DrawBackGround();
}
void DrawModule::DrawBackGround()
{
if (bitmap_background!=NULL) { delete bitmap_background; }
bitmap_background = new Bitmap(m_rc.right-m_rc.left, m_rc.bottom-m_rc.top);
Graphics graphics_temp(bitmap_background);
if (m_Transparency != 255)
{
SolidBrush blackBrush_clear(Color(255, 255, 255));
graphics_temp.FillRectangle(&blackBrush_clear, 0, 0, m_rc.right - m_rc.left, m_rc.bottom - m_rc.top);
}
SolidBrush blackBrush(Color(m_Transparency, 10, 10, 10));
graphics_temp.FillRectangle(&blackBrush,0,0,m_rc.right-m_rc.left,m_rc.bottom-m_rc.top);
/*if (m_Transparency != 255)
return;*/
// 2、坐标线
Pen redPen_Solid(Color(180,40,40),0.3f); // 实线画笔
redPen_Solid.SetDashStyle(DashStyleSolid);
Pen redPen_Dash(Color(200,50,50),0.3f); // 虚线画笔
redPen_Dash.SetDashStyle(DashStyleDash);
graphics_temp.DrawLine(&redPen_Solid,
m_rc.right-m_rc.left-RIGHT_SPACE, 0,
m_rc.right-m_rc.left-RIGHT_SPACE, m_rc.bottom-m_rc.top-TOP_INFO_SPACE);//右数据轴 |
// 3、虚线
for (unsigned int i=1;i<=5;i++)
{
graphics_temp.DrawLine(&redPen_Dash,
0, (m_rc.bottom-m_rc.top-TOP_INFO_SPACE)/6*i+TOP_INFO_SPACE,
m_rc.right-m_rc.left-RIGHT_SPACE, (m_rc.bottom-m_rc.top-TOP_INFO_SPACE)/6*i+TOP_INFO_SPACE);
}
}
void DrawModule::SetInfoToDraw(list<Skline>* pList)
{
m_pListKline = pList;
}
void DrawModule::SetCompareKline(list<Skline>* pList)
{
m_pCompareListKline = pList;
}
void DrawModule::ClearOther()
{
m_vecOtherInfo.clear();
}
void DrawModule::SetOtherInfo(OtherInfo& pInfo)
{
m_vecOtherInfo.push_back(pInfo);
}
void DrawModule::SetPankou(SIndexData& data)
{
m_pankou = data;
}
void DrawModule::SetTradeDot(vector<STradeData>* p)
{
m_vecTradeData = p;
}
void DrawModule::SetOtherGreenColor()
{
bOtherGreen = true;
}
void DrawModule::SetCrossCycle(int min_cycle, int max_cycle)
{
m_minCycle = min_cycle;
m_maxCycle = max_cycle;
}
void DrawModule::DefaultResetInterval(bool bAdd)
{
if (nIntervalTime>0 && nIntervalTime<11)
{
if (bAdd) ++nIntervalTime;
else --nIntervalTime;
}else if (nIntervalTime==0 && bAdd)
{
++nIntervalTime;
}else if (nIntervalTime==11 && !bAdd)
{
--nIntervalTime;
}
else
return;
DoDraw();
}
void DrawModule::DefaultResetOffset(bool bLeft)
{
if (m_pListKline==NULL)
{
return;
}
unsigned int oldOffset = m_nOffset;
int nShowCount = (m_rc.right-m_rc.left-RIGHT_SPACE)/(iInterval[nIntervalTime]+2*iSpace[nIntervalTime]);
int nListCount = m_pListKline->size();
if (nListCount <= nShowCount)
{
m_nOffset = 0;
}
else
{
if (bLeft)
{
int nCanOffset = nListCount - m_nOffset - nShowCount;
if (nCanOffset > 0)
{
m_nOffset++;
}
}
else if(m_nOffset >0)
{
m_nOffset--;
}
}
if (m_nOffset!=oldOffset)
{
DoDraw();
}
}
void DrawModule::PercentResetOffset(unsigned int nPercent)
{
if (nPercent>100 || m_pListKline==NULL)
{
return;
}
int nShowCount = (m_rc.right-m_rc.left-RIGHT_SPACE)/(iInterval[nIntervalTime]+2*iSpace[nIntervalTime]);
int nListCount = m_pListKline->size();
int oldOffset = m_nOffset;
int nOffset = (100-nPercent)*nListCount/100;
if ((nOffset+nShowCount)>nListCount)
{
m_nOffset = nListCount - nShowCount;
}
else
{
m_nOffset = nOffset;
}
if (m_nOffset!=nOffset)
{
DoDraw();
}
}
bool DrawModule::PreDrawInfo()
{
if (m_pListKline == NULL)
{
return false;
}
int nKlineCount = m_pListKline->size();
list<Skline>::reverse_iterator rIter = m_pListKline->rbegin();
if (rIter != m_pListKline->rend() && m_nOffset)
{// 指针偏移
int nCount = 0;
while (nCount<m_nOffset)
{
rIter++;
nCount++;
}
}
double dMax = -1.79769313486231570E+308,dMin = DBL_MAX;
int nMaxShow = (m_rc.right-m_rc.left-RIGHT_SPACE)/(iInterval[nIntervalTime]+2*iSpace[nIntervalTime]);
while (rIter != m_pListKline->rend() && nMaxShow)
{// 取频幕上最大最小值
if (dMax<rIter->dHigh)
{
dMax = rIter->dHigh;
}
if (dMin>rIter->dLow)
{
dMin = rIter->dLow;
}
rIter++;
nMaxShow--;
}
for (unsigned int i = 0; i < m_vecOtherInfo.size(); i++)
{
if (m_vecOtherInfo[i].bShow)
{
list<double>::reverse_iterator rOtherIter = m_vecOtherInfo[i].listInfo->rbegin();
if (rOtherIter != m_vecOtherInfo[i].listInfo->rend() && m_nOffset)
{// 指标偏移
int nCount = 0;
while (nCount < m_nOffset && rOtherIter != m_vecOtherInfo[i].listInfo->rend())
{
rOtherIter++;
nCount++;
}
}
nMaxShow = (m_rc.right - m_rc.left - RIGHT_SPACE) / (iInterval[nIntervalTime] + 2 * iSpace[nIntervalTime]);
while (rOtherIter != m_vecOtherInfo[i].listInfo->rend() && nMaxShow)
{
if (dMax < *rOtherIter)
{
dMax = *rOtherIter;
}
if (dMin > *rOtherIter)
{
dMin = *rOtherIter;
}
rOtherIter++;
nMaxShow--;
}
}
}
m_dMax = dMax;
m_dMin = dMin;
return true;
}
void DrawModule::DrawNumber()
{
double dUnit = (m_dMax - m_dMin)/(m_rc.bottom-m_rc.top-TOP_INFO_SPACE)/0.9; // 数据轴单位
double dCurMin = m_dMin - 0.05*(m_rc.bottom-m_rc.top-TOP_INFO_SPACE)*dUnit; // 数据轴最小值
//if (m_Transparency != 255)
// return;
FontFamily fontFamily(L"楷体");
Gdiplus::Font font(&fontFamily, 15, FontStyleRegular, UnitPixel);
StringFormat stringformat;
stringformat.SetAlignment(StringAlignmentNear);
stringformat.SetLineAlignment(StringAlignmentCenter);
if (bitmap_kline_index!=NULL) { delete bitmap_kline_index; }
bitmap_kline_index = new Bitmap(m_rc.right-m_rc.left, m_rc.bottom-m_rc.top);
Graphics graphics_kline_index(bitmap_kline_index);
graphics_kline_index.SetTextRenderingHint(TextRenderingHintAntiAlias);
char szValue[50];
wchar_t wcstring[100];
SolidBrush red_brush(Color(240,10,10));
for (unsigned int i=1;i<=5;i++)
{
int nHorien = (m_rc.bottom-m_rc.top-TOP_INFO_SPACE)/6*(6-i)+TOP_INFO_SPACE;
_snprintf_s(szValue,50,"%.2f",dCurMin+dUnit*(m_rc.bottom-m_rc.top-TOP_INFO_SPACE)/6*i);
MultiByteToWideChar(CP_ACP,0,szValue,50,wcstring,100);
graphics_kline_index.DrawString(wcstring,wcslen(wcstring),&font,
RectF(m_rc.right-m_rc.left-RIGHT_SPACE+2,nHorien-8,RIGHT_SPACE-2,16),&stringformat,&red_brush);
}
if (m_pankou.ask_volume != 0 && m_pankou.bid_volume != 0) {
SolidBrush white_brush(Color(255, 255, 255));
Point pt_new = DataToPoint(m_pankou.index_tick, 1, dCurMin, dUnit);
SolidBrush RedBrush(Color(240, 10, 10));
graphics_kline_index.FillRectangle(&RedBrush, m_rc.right - m_rc.left - RIGHT_SPACE, pt_new.Y - 14, 70, 28);
_snprintf_s(szValue, 100, "%.2f", m_pankou.ask_price);
MultiByteToWideChar(CP_ACP, 0, szValue, 100, wcstring, 200);
graphics_kline_index.DrawString(wcstring, wcslen(wcstring), &font,
RectF(m_rc.right - m_rc.left - RIGHT_SPACE, pt_new.Y - 14, strlen(szValue) * 8 + 6, TOP_INFO_SPACE / 2), &stringformat, &white_brush);
_snprintf_s(szValue, 100, "%.2f", m_pankou.bid_price);
MultiByteToWideChar(CP_ACP, 0, szValue, 100, wcstring, 200);
graphics_kline_index.DrawString(wcstring, wcslen(wcstring), &font,
RectF(m_rc.right - m_rc.left - RIGHT_SPACE, pt_new.Y , strlen(szValue) * 8 + 6, TOP_INFO_SPACE / 2), &stringformat, &white_brush);
}
}
bool DrawModule::InfoToShow(unsigned int nX, unsigned int nY)
{
if (nX>=(m_rc.right-m_rc.left-RIGHT_SPACE) || m_pListKline==NULL)
{
return false;
}
int nScreenOffset = (m_rc.right-m_rc.left - nX - RIGHT_SPACE)/(iInterval[nIntervalTime]+2*iSpace[nIntervalTime]); // 屏幕偏移
int nScreenCount = nScreenOffset;
nScreenOffset += m_nOffset; // 容器偏移
list<Skline>::reverse_iterator rIter = m_pListKline->rbegin();
while(rIter != m_pListKline->rend() && nScreenOffset>0)
{
nScreenOffset--;
rIter++;
}
if (rIter == m_pListKline->rend())
{
return false;
}
if (nY<TOP_INFO_SPACE)
{
return false;
}
vector<OtherShowInfo> vecOther;
vector<OtherInfo>::iterator iterOther = m_vecOtherInfo.begin();
while (iterOther != m_vecOtherInfo.end())
{
nScreenOffset = (m_rc.right - m_rc.left - nX - RIGHT_SPACE) / (iInterval[nIntervalTime] + 2 * iSpace[nIntervalTime]) + m_nOffset;
list<double>::reverse_iterator subrIterOther = iterOther->listInfo->rbegin();
while (subrIterOther != iterOther->listInfo->rend() && nScreenOffset)
{
nScreenOffset--;
subrIterOther++;
}
if (subrIterOther != iterOther->listInfo->rend())
{
OtherShowInfo osi;
strcpy_s(osi.pName, iterOther->pName);
osi.dValue = *subrIterOther;
osi.color = iterOther->color;
vecOther.push_back(osi);
}
iterOther++;
}
Skline& m_lastKline = *rIter;
if (bitmap_user_show_info!=NULL) { delete bitmap_user_show_info; }
bitmap_user_show_info = new Bitmap(m_rc.right-m_rc.left,m_rc.bottom-m_rc.top);
Graphics graphics_user_show_info(bitmap_user_show_info);
graphics_user_show_info.DrawImage(bitmap_background,0,0,bitmap_background->GetWidth(),bitmap_background->GetHeight());
graphics_user_show_info.DrawImage(bitmap_kline_index,0,0,bitmap_kline_index->GetWidth(),bitmap_kline_index->GetHeight());
int y = 2;
FontFamily fontFamily(L"楷体");
Gdiplus::Font font(&fontFamily, 15, FontStyleRegular, UnitPixel);
StringFormat stringformat;
stringformat.SetAlignment(StringAlignmentNear);
stringformat.SetLineAlignment(StringAlignmentCenter);
graphics_user_show_info.SetTextRenderingHint(TextRenderingHintAntiAlias);
char szValue[1000];
wchar_t wcstring[2000];
_snprintf_s(szValue,1000,"%d,%d,开:%.2f,高:%.2f,低:%.2f,收:%.2f,成交量:%d,持仓量:%d;",
m_lastKline.nDate,m_lastKline.nTime,
m_lastKline.dOpen,m_lastKline.dHigh,m_lastKline.dLow,m_lastKline.dClose,
m_lastKline.nVolume,m_lastKline.dInterest);
MultiByteToWideChar(CP_ACP,0,szValue,1000,wcstring,2000);
SolidBrush white_brush(Color(255,255,255));
graphics_user_show_info.DrawString(wcstring,wcslen(wcstring),&font,
RectF(2,y,strlen(szValue)*8,TOP_INFO_SPACE/2-2),&stringformat,&white_brush);
y = TOP_INFO_SPACE / 2 + 2;
int x = 2;
for (int i = 0; i < vecOther.size(); i++)
{
SolidBrush the_brush(vecOther[i].color);
_snprintf_s(szValue, 100, "%s:%.2f;", vecOther[i].pName, vecOther[i].dValue);
MultiByteToWideChar(CP_ACP, 0, szValue, 100, wcstring, 200);
graphics_user_show_info.DrawString(wcstring, wcslen(wcstring), &font,
RectF(x, y, strlen(szValue) * 8, TOP_INFO_SPACE / 2 - 2), &stringformat, &the_brush);
x += strlen(szValue) * 8;
}
if (m_nShowCursel > 0)
{
Pen WhitePen(Color(240,240,240),1.0f);
// 横、纵、数字
if (m_nShowCursel > 1)
{
double dUnit = (m_dMax - m_dMin) / (m_rc.bottom - m_rc.top - TOP_INFO_SPACE) / 0.9;
double dCurMin = m_dMin - 0.05*(m_rc.bottom - m_rc.top - TOP_INFO_SPACE)*dUnit; // 数据轴最小值
Point ptLeft; ptLeft.X = 0; ptLeft.Y = nY;
Point ptRight; ptRight.X = m_rc.right - m_rc.left - RIGHT_SPACE; ptRight.Y = nY;
graphics_user_show_info.DrawLine(&WhitePen, ptLeft, ptRight);
SolidBrush RedBrush(Color(240, 10, 10));
graphics_user_show_info.FillRectangle(&RedBrush, m_rc.right - m_rc.left - RIGHT_SPACE, nY - 7, 70, 14);
_snprintf_s(szValue, 100, "%.2f", (m_rc.bottom - m_rc.top - nY)*dUnit + dCurMin);
MultiByteToWideChar(CP_ACP, 0, szValue, 100, wcstring, 200);
graphics_user_show_info.DrawString(wcstring, wcslen(wcstring), &font,
RectF(m_rc.right - m_rc.left - RIGHT_SPACE, nY - 7, strlen(szValue) * 8 + 6, TOP_INFO_SPACE / 2), &stringformat, &white_brush);
}
int nKlineX = m_rc.right-m_rc.left-RIGHT_SPACE - nScreenCount*(iInterval[nIntervalTime]+iSpace[nIntervalTime]*2) - iInterval[nIntervalTime]/2 - iSpace[nIntervalTime];
Point ptTop; ptTop.X = nKlineX;ptTop.Y = TOP_INFO_SPACE;
Point ptBottom; ptBottom.X = nKlineX; ptBottom.Y = m_rc.bottom-m_rc.top;
graphics_user_show_info.DrawLine(&WhitePen, ptTop, ptBottom);
}
drawObj->DrawImage(bitmap_user_show_info,0,0,bitmap_user_show_info->GetWidth(),bitmap_user_show_info->GetHeight());
return true;
}
Skline* DrawModule::GetSelectKline(unsigned int nX)
{
if (nX >= (m_rc.right - m_rc.left - RIGHT_SPACE) || m_pListKline == NULL)
{
return nullptr;
}
int nScreenOffset = (m_rc.right - m_rc.left - nX - RIGHT_SPACE) / (iInterval[nIntervalTime] + 2 * iSpace[nIntervalTime]); // 屏幕偏移
int nScreenCount = nScreenOffset;
nScreenOffset += m_nOffset; // 容器偏移
list<Skline>::reverse_iterator rIter = m_pListKline->rbegin();
while (rIter != m_pListKline->rend() && nScreenOffset > 0)
{
nScreenOffset--;
rIter++;
}
if (rIter == m_pListKline->rend())
{
return nullptr;
}
vector<OtherShowInfo> vecOther;
vector<OtherInfo>::iterator iterOther = m_vecOtherInfo.begin();
while (iterOther != m_vecOtherInfo.end())
{
nScreenOffset = (m_rc.right - m_rc.left - nX - RIGHT_SPACE) / (iInterval[nIntervalTime] + 2 * iSpace[nIntervalTime]) + m_nOffset;
list<double>::reverse_iterator subrIterOther = iterOther->listInfo->rbegin();
while (subrIterOther != iterOther->listInfo->rend() && nScreenOffset)
{
nScreenOffset--;
subrIterOther++;
}
if (subrIterOther != iterOther->listInfo->rend())
{
OtherShowInfo osi;
strcpy_s(osi.pName, iterOther->pName);
osi.dValue = *subrIterOther;
osi.color = iterOther->color;
vecOther.push_back(osi);
}
iterOther++;
}
Skline& m_lastKline = *rIter;
m_nSelectKlineDate = m_lastKline.nDate;
m_nSelectKlineTime = m_lastKline.nTime;
return &m_lastKline;
}
void DrawModule::DrawKLine(bool bDrawAll)
{
if (m_pListKline==NULL) return;
list<Skline>::reverse_iterator rIter = m_pListKline->rbegin();
int nCountOffset = 0;
if (rIter != m_pListKline->rend() && m_nOffset)
{// K线偏移
while (nCountOffset<m_nOffset)
{
rIter++;
nCountOffset++;
}
}
list<Skline>::reverse_iterator rIterCompare;
if (m_pCompareListKline)
{
rIterCompare = m_pCompareListKline->rbegin();
advance(rIterCompare, nCountOffset);
}
double dUnit = (m_dMax - m_dMin)/(m_rc.bottom-m_rc.top-TOP_INFO_SPACE)/0.9; // 数据轴单位
double dCurMin = m_dMin - 0.05*(m_rc.bottom-m_rc.top-TOP_INFO_SPACE)*dUnit; // 数据轴最小值
// 画笔
Pen RedPen(Color(240,10,10),1.5f);
Pen LightRedPen(Color(120, 10, 10), 0.5f);
LightRedPen.SetDashStyle(DashStyleDash);
Pen GreenPen(Color(50,210,50),1.5f);
Pen OtherGreenPen(Color(141, 238, 238), 1.5f);
Pen WhitePen(Color(240,240,240),1.5f);
Pen yellowPen(Color(230, 236, 29), 1.5f);
SolidBrush GreenPenTrade(Color(100, 200, 100));
SolidBrush RedPenTrade(Color(210, 50, 50));
// 画刷
SolidBrush GreenBrush(Color(50,210,50));
SolidBrush RedBrush(Color(240, 10, 10));
SolidBrush OtherGreenBrush(Color(141, 238, 238));
SolidBrush yellowBrush(Color(230, 236, 29));
Graphics graphics_kline_index(bitmap_kline_index);
int nMaxCount = (m_rc.right-m_rc.left-RIGHT_SPACE)/(iInterval[nIntervalTime]+2*iSpace[nIntervalTime]);
int nCount = 1;// 画图上第多少个
double dMaxScreen=DBL_MIN,dMinScreen=DBL_MAX;
Point ptMax,ptMin;
if (iInterval[nIntervalTime]<2)
{// 间距小于4只画线
while (rIter != m_pListKline->rend() && nMaxCount)
{
double dMulti = rIter->dClose - rIter->dOpen;
Point ptHigh = DataToPoint(rIter->dHigh,nCount,dCurMin,dUnit);
Point ptLow = DataToPoint(rIter->dLow,nCount,dCurMin,dUnit);
if (m_maxCycle != 0 && m_minCycle != 0) {
int hX = ptLow.X + iInterval[nIntervalTime] / 2 + iSpace[nIntervalTime]; // 纵向网格
int sec = to_second(rIter->nTime);
if (sec % m_maxCycle == 0) {
graphics_kline_index.DrawLine(&RedPen, hX, TOP_INFO_SPACE, hX, m_rc.bottom - m_rc.top);
}
else if (sec % m_minCycle == 0) {
graphics_kline_index.DrawLine(&LightRedPen, hX, TOP_INFO_SPACE, hX, m_rc.bottom - m_rc.top);
}
}
if (dMulti< -0.01)
{// 绿线
graphics_kline_index.DrawLine(bOtherGreen ? &OtherGreenPen : &GreenPen,ptLow,ptHigh);
}
else
{// 红线
graphics_kline_index.DrawLine(&RedPen,ptLow,ptHigh);
}
if (rIter->dHigh > dMaxScreen)
{
dMaxScreen = rIter->dHigh;
ptMax = ptHigh;
}
if (dMinScreen > rIter->dLow)
{
dMinScreen = rIter->dLow;
ptMin = ptLow;
}
nCount++;
rIter++;
if (m_pCompareListKline && rIterCompare!= m_pCompareListKline->rend()) rIterCompare++;
nMaxCount--;
}
}
else
{
while (rIter != m_pListKline->rend() && nMaxCount)
{
double dMulti = rIter->dClose - rIter->dOpen;
Point ptOpen = DataToPoint(rIter->dOpen,nCount,dCurMin,dUnit);
Point ptHigh = DataToPoint(rIter->dHigh,nCount,dCurMin,dUnit);
Point ptLow = DataToPoint(rIter->dLow,nCount,dCurMin,dUnit);
Point ptClose = DataToPoint(rIter->dClose,nCount,dCurMin,dUnit);
if (m_maxCycle != 0 && m_minCycle != 0) {
int hX = ptLow.X + iInterval[nIntervalTime] / 2 + iSpace[nIntervalTime]; // 纵向网格
int sec = to_second(rIter->nTime);
if (sec % m_maxCycle == 0) {
graphics_kline_index.DrawLine(&RedPen, hX, TOP_INFO_SPACE, hX, m_rc.bottom - m_rc.top);
}
else if (sec % m_minCycle == 0) {
graphics_kline_index.DrawLine(&LightRedPen, hX, TOP_INFO_SPACE, hX, m_rc.bottom - m_rc.top);
}
}
if (dMulti< -0.01)
{// 绿柱
if (m_pCompareListKline && rIterCompare != m_pCompareListKline->rend() && rIterCompare->dOpen <= rIterCompare->dClose)
{
graphics_kline_index.DrawRectangle((rIter->nDate == m_nSelectKlineDate && rIter->nTime == m_nSelectKlineTime) ? &yellowPen : &GreenPen,
ptOpen.X - iInterval[nIntervalTime] / 2, ptOpen.Y,
iInterval[nIntervalTime], ptClose.Y - ptOpen.Y);
graphics_kline_index.DrawLine((rIter->nDate == m_nSelectKlineDate && rIter->nTime == m_nSelectKlineTime) ? &yellowPen : &GreenPen, ptHigh, ptOpen);
graphics_kline_index.DrawLine((rIter->nDate == m_nSelectKlineDate && rIter->nTime == m_nSelectKlineTime) ? &yellowPen : &GreenPen, ptLow, ptClose);
}
else
{
graphics_kline_index.FillRectangle((rIter->nDate == m_nSelectKlineDate && rIter->nTime == m_nSelectKlineTime) ? &yellowBrush : &GreenBrush,
ptOpen.X - iInterval[nIntervalTime] / 2, ptOpen.Y,
iInterval[nIntervalTime], ptClose.Y - ptOpen.Y);
graphics_kline_index.DrawLine((rIter->nDate == m_nSelectKlineDate && rIter->nTime == m_nSelectKlineTime) ? &yellowPen : &GreenPen, ptHigh, ptLow);
}
}
else if (dMulti < 0.01)
{// 白十字
graphics_kline_index.DrawLine((rIter->nDate == m_nSelectKlineDate && rIter->nTime == m_nSelectKlineTime) ? &yellowPen : &WhitePen,ptHigh,ptLow);
graphics_kline_index.DrawLine((rIter->nDate == m_nSelectKlineDate && rIter->nTime == m_nSelectKlineTime) ? &yellowPen : &WhitePen,
ptOpen.X-iInterval[nIntervalTime]/2,ptOpen.Y,
ptOpen.X+iInterval[nIntervalTime]/2,ptOpen.Y);
}
else
{// 红柱
if (m_pCompareListKline && rIterCompare != m_pCompareListKline->rend() && rIterCompare->dOpen <= rIterCompare->dClose)
{
graphics_kline_index.FillRectangle((rIter->nDate == m_nSelectKlineDate && rIter->nTime == m_nSelectKlineTime) ? &yellowBrush : &RedBrush, ptClose.X - iInterval[nIntervalTime] / 2, ptClose.Y,
iInterval[nIntervalTime], ptOpen.Y - ptClose.Y);
graphics_kline_index.DrawLine((rIter->nDate == m_nSelectKlineDate && rIter->nTime == m_nSelectKlineTime) ? &yellowPen : &RedPen, ptHigh, ptLow);
}
else
{
graphics_kline_index.DrawRectangle((rIter->nDate == m_nSelectKlineDate && rIter->nTime == m_nSelectKlineTime) ? &yellowPen : &RedPen,
ptClose.X - iInterval[nIntervalTime] / 2, ptClose.Y,
iInterval[nIntervalTime], ptOpen.Y - ptClose.Y);
graphics_kline_index.DrawLine((rIter->nDate == m_nSelectKlineDate && rIter->nTime == m_nSelectKlineTime) ? &yellowPen : &RedPen, ptHigh, ptClose);
graphics_kline_index.DrawLine((rIter->nDate == m_nSelectKlineDate && rIter->nTime == m_nSelectKlineTime) ? &yellowPen : &RedPen, ptLow, ptOpen);
}
}
if (m_vecTradeData) {
for (auto iter_trade = m_vecTradeData->begin(); iter_trade != m_vecTradeData->end(); iter_trade++)
{
if (iter_trade->nDate == rIter->nDate && IsSameCycle(iter_trade->nTime, rIter->nTime))
{▲▼▼▲↑
Point ptPrice = DataToPoint(iter_trade->dPrice, nCount, dCurMin, dUnit);
SolidBrush KongBrush(Color(89, 66, 237));
SolidBrush DuoBrush(Color(244, 186, 247));
SolidBrush checkBrush(Color(100, 100, 140));
FontFamily fontFamily(L"楷体");
Gdiplus::Font font(&fontFamily, 14, FontStyleRegular, UnitPixel);
StringFormat stringformat;
stringformat.SetAlignment(StringAlignmentCenter);
stringformat.SetLineAlignment(StringAlignmentCenter);
graphics_kline_index.SetTextRenderingHint(TextRenderingHintAntiAlias);
if (iter_trade->chDir == '0')
{
char szValue[50]; wchar_t wcstring[100];
_snprintf_s(szValue, 50, "▲\n%d|%d", iter_trade->nVolume, iter_trade->nNetPos);
MultiByteToWideChar(CP_ACP, 0, szValue, 50, wcstring, 100);
//graphics_kline_index.FillRectangle(&checkBrush, RectF(ptPrice.X - 28, ptPrice.Y, 56, 28));
graphics_kline_index.DrawString(wcstring, wcslen(wcstring), &font,
RectF(ptPrice.X - 28, ptPrice.Y, 56, 28), &stringformat, &DuoBrush);
}
else
{
char szValue[50]; wchar_t wcstring[100];
_snprintf_s(szValue, 50, "%d|%d\n▼", iter_trade->nVolume, iter_trade->nNetPos);
MultiByteToWideChar(CP_ACP, 0, szValue, 50, wcstring, 100);
//graphics_kline_index.FillRectangle(&checkBrush, RectF(ptPrice.X - 28, ptPrice.Y - 28, 56, 28));
graphics_kline_index.DrawString(wcstring, wcslen(wcstring), &font,
RectF(ptPrice.X - 28, ptPrice.Y-24, 56, 28), &stringformat, &KongBrush);
}
//_snprintf_s(szValue, 50, "%d", iter_trade->nNetPos);
//MultiByteToWideChar(CP_ACP, 0, szValue, 50, wcstring, 100);
//graphics_kline_index.DrawString(wcstring, wcslen(wcstring), &font,
// RectF(ptPrice.X + 1, iter_trade->chDir == '1' ? ptPrice.Y - 7 : ptPrice.Y, 14, 28), &stringformat, &WhiteBrush);
}
}
}
if (rIter->dHigh > dMaxScreen)
{
dMaxScreen = rIter->dHigh;
ptMax = ptHigh;
}
if (dMinScreen > rIter->dLow)
{
dMinScreen = rIter->dLow;
ptMin = ptLow;
}
nCount++;
rIter++;
if (m_pCompareListKline && rIterCompare != m_pCompareListKline->rend()) rIterCompare++;
nMaxCount--;
}
}
if (dMinScreen!=DBL_MAX && dMaxScreen!=DBL_MIN)
{
SolidBrush WhiteBrush(Color(255,255,240));
FontFamily fontFamily(L"楷体");
Gdiplus::Font font(&fontFamily, 14, FontStyleRegular, UnitPixel);
StringFormat stringformat;
stringformat.SetAlignment(StringAlignmentNear);
stringformat.SetLineAlignment(StringAlignmentCenter);
graphics_kline_index.SetTextRenderingHint(TextRenderingHintAntiAlias);
char szValue[50];wchar_t wcstring[100];
_snprintf_s(szValue,50,"←%.2f",dMinScreen);
MultiByteToWideChar(CP_ACP,0,szValue,50,wcstring,100);
graphics_kline_index.DrawString(wcstring,wcslen(wcstring),&font,
RectF(ptMin.X,ptMin.Y-7,RIGHT_SPACE,14),&stringformat,&WhiteBrush);
_snprintf_s(szValue,50,"←%.2f",dMaxScreen);
MultiByteToWideChar(CP_ACP,0,szValue,50,wcstring,100);
graphics_kline_index.DrawString(wcstring,wcslen(wcstring),&font,
RectF(ptMax.X,ptMax.Y-7,RIGHT_SPACE,14),&stringformat,&WhiteBrush);
}
}
Point DrawModule::DataToPoint(double dValue, int nCount, double dCurMin, double dUnit)
{
Point pt;
pt.X = m_rc.right-m_rc.left-RIGHT_SPACE-(iInterval[nIntervalTime]+iSpace[nIntervalTime]*2)*nCount+iSpace[nIntervalTime]+iInterval[nIntervalTime]/2;
pt.Y = m_rc.bottom-m_rc.top-(dValue-dCurMin)/dUnit;
return pt;
}
void DrawModule::DrawOther(bool bDrawAll)
{
double dUnit = (m_dMax - m_dMin) / (m_rc.bottom - m_rc.top - TOP_INFO_SPACE) / 0.9; // 数据轴单位
double dCurMin = m_dMin - 0.05*(m_rc.bottom - m_rc.top - TOP_INFO_SPACE)*dUnit; // 数据轴最小值
Graphics graphics_kline_index(bitmap_kline_index);
for (unsigned int i = 0; i < m_vecOtherInfo.size(); i++)
{
Pen randomPen(m_vecOtherInfo[i].color, m_vecOtherInfo[i].dBound);
randomPen.SetDashStyle(DashStyle(m_vecOtherInfo[i].penStyle));
if (m_vecOtherInfo[i].bShow)
{
list<double>::reverse_iterator rOtherIter = m_vecOtherInfo[i].listInfo->rbegin();
if (rOtherIter != m_vecOtherInfo[i].listInfo->rend() && m_nOffset)
{
int nCount = 0;
while (nCount < m_nOffset && rOtherIter != m_vecOtherInfo[i].listInfo->rend())
{
rOtherIter++;
nCount++;
}
}
int nMaxCount = (m_rc.right - m_rc.left - RIGHT_SPACE) / (iInterval[nIntervalTime] + 2 * iSpace[nIntervalTime]);
int nCount = 1;
Point ptLast;
while (rOtherIter != m_vecOtherInfo[i].listInfo->rend() && nMaxCount)
{
Point ptThis = DataToPoint(*rOtherIter, nCount, dCurMin, dUnit);
if (nCount > 1)
{
graphics_kline_index.DrawLine(&randomPen, ptLast, ptThis);
}
rOtherIter++;
nCount++;
nMaxCount--;
ptLast = ptThis;
}
}
}
}
bool DrawModule::IsSameCycle(int nTime, int nKlineTime)
{
int n1 = nTime / 100;
int n2 = nKlineTime / 100;
if (n1 % 100 == 59)
{
n1 += 41;
}
else
{
n1 += 1;
}
if (n1 == n2)
return true;
return false;
}
void DrawModule::ThreadDraw()
{
while (m_bThread)
{
if (WaitForSingleObject(m_hEvent, 100) == WAIT_TIMEOUT)
continue;
if (PreDrawInfo())
{
DoDrawAll();
}
}
}
void DrawModule::DoDraw()
{
::SetEvent(m_hEvent);
//if(PreDrawInfo())
//{
// DoDrawAll();
//}
}
void DrawModule::Start()
{
if (m_thdDraw) return;
m_thdDraw = make_shared<thread>(&DrawModule::ThreadDraw, this);
}
void DrawModule::Stop()
{
m_bThread = false;
if (m_thdDraw && m_thdDraw->joinable())
m_thdDraw->join();
}
void DrawModule::DoDrawAll()
{
// 在画布上绘制
DrawNumber();
DrawKLine();
DrawOther();
// 显示到控件上
if (!bitmap_user_show_info) return;
Gdiplus::Graphics graphics_user_show_info(bitmap_user_show_info);
graphics_user_show_info.DrawImage(bitmap_background,0,0,bitmap_background->GetWidth(),bitmap_background->GetHeight());
graphics_user_show_info.DrawImage(bitmap_kline_index,0,0,bitmap_kline_index->GetWidth(),bitmap_kline_index->GetHeight());
drawObj->DrawImage(bitmap_user_show_info,0,0,bitmap_user_show_info->GetWidth(),bitmap_user_show_info->GetHeight());
}
2、qt绘图
#pragma once
#include <QVBoxLayout>
#include <QFrame>
#include <QPixmap>
#include <QLabel>
#include <QList>
#include <QPen>
#include <QBrush>
#include <list>
#include "user_define_struct.h"
#include "TradeStruct.h"
//关联外部定义结构体到绘图模块,实现必要的函数
typedef Skline QDrawKline;
typedef SIG_TYPE QDrawSignalType;
typedef STRUCT_RSP_SIGNAL QDrawSignal;
typedef PositionDetail QDrawPosition;
class QKlineInfo : public QWidget
{
public:
QKlineInfo(QWidget *parent) : QWidget(parent), m_precision(0)
{
QVBoxLayout* layout = new QVBoxLayout(this);
layout->setContentsMargins(1, 1, 1, 1);
layout->addWidget(new QLabel(u8"<span style='color:white;'>日期</span>"));
m_lb_date = new QLabel(this);
layout->addWidget(m_lb_date);
layout->addWidget(new QLabel(u8"<span style='color:white;'>时间</span>"));
m_lb_time = new QLabel(this);
layout->addWidget(m_lb_time);
layout->addWidget(new QLabel(u8"<span style='color:white;'>开盘</span>"));
m_lb_open = new QLabel(this);
layout->addWidget(m_lb_open);
layout->addWidget(new QLabel(u8"<span style='color:white;'>最高</span>"));
m_lb_high = new QLabel(this);
layout->addWidget(m_lb_high);
layout->addWidget(new QLabel(u8"<span style='color:white;'>最低</span>"));
m_lb_low = new QLabel(this);
layout->addWidget(m_lb_low);
layout->addWidget(new QLabel(u8"<span style='color:white;'>收盘</span>"));
m_lb_close = new QLabel(this);
layout->addWidget(m_lb_close);
layout->addWidget(new QLabel(u8"<span style='color:white;'>成交</span>"));
m_lb_volume = new QLabel(this);
layout->addWidget(m_lb_volume);
layout->addWidget(new QLabel(u8"<span style='color:white;'>持仓</span>"));
m_lb_interest = new QLabel(this);
layout->addWidget(m_lb_interest);
}
void SetPrecision(int precision)
{
m_precision = precision;
}
void UpdateKline(QDrawKline& k, double& last_close)
{
m_lb_date->setText(QString("<span style='color:white;'>%1</span>").arg(k.Date()));
m_lb_time->setText(QString("<span style='color:white;'>%1</span>").arg(k.Time()));
m_lb_open->setText(QString("<span style='color:%1;'>%2</span>").arg(GetColor(k.Open(), last_close)).arg(QString::number(k.Open(), 'f', m_precision)));
m_lb_high->setText(QString("<span style='color:%1;'>%2</span>").arg(GetColor(k.High(), last_close)).arg(QString::number(k.High(), 'f', m_precision)));
m_lb_low->setText(QString("<span style='color:%1;'>%2</span>").arg(GetColor(k.Low(), last_close)).arg(QString::number(k.Low(), 'f', m_precision)));
m_lb_close->setText(QString("<span style='color:%1;'>%2</span>").arg(GetColor(k.Close(), last_close)).arg(QString::number(k.Close(), 'f', m_precision)));
m_lb_volume->setText(QString("<span style='color:yellow;'>%1</span>").arg(k.Volume()));
m_lb_interest->setText(QString("<span style='color:yellow;'>%1</span>").arg(QString::number(k.Interest(), 'f', 0)));
}
protected:
QString GetColor(const double& this_value,const double& last_value)
{
if (this_value > last_value)
return "#DB3320";
if (this_value < last_value)
return "#00E700";
return "#E7E7E7";
}
protected:
QLabel* m_lb_date;
QLabel* m_lb_time;
QLabel* m_lb_open;
QLabel* m_lb_high;
QLabel* m_lb_low;
QLabel* m_lb_close;
QLabel* m_lb_volume;
QLabel* m_lb_interest;
int m_precision;
};
class QDrawModule : public QFrame
{
Q_OBJECT
using kline_riter = std::list<QDrawKline>::reverse_iterator;
using curve_riter = std::list<double>::reverse_iterator;
struct DataCoverage
{
std::list<QDrawKline>* m_pListKline;
std::list<double>* m_pListCurve;
kline_riter m_offset_kline_iter;
curve_riter m_offset_curve_iter;
QPixmap m_pixCoverage;
QString name;
QColor clr;
DataCoverage()
{
m_pListKline = nullptr;
m_pListCurve = nullptr;
}
bool operator ==(const DataCoverage& other)
{
bool bRet = m_pListKline == other.m_pListKline;
bRet &= m_pListCurve == other.m_pListCurve;
return bRet;
}
void ResetIter()
{
if (m_pListKline) m_offset_kline_iter = m_pListKline->rbegin();
if (m_pListCurve) m_offset_curve_iter = m_pListCurve->rbegin();
}
};
struct UserPosition
{
int user_key;
QDrawPosition* m_position;
UserPosition()
{
user_key = 0;
m_position = nullptr;
}
};
#define SHOW_ALL_POSITION INT_MAX
int m_showUserPosition;
QList<UserPosition> m_userPosition;//用户仓位
struct UserSignal
{
int user_key;
std::list<QDrawSignal>* m_signal;
};
QList<UserSignal> m_userSignal;
public:
QDrawModule(QWidget *parent);
~QDrawModule();
static int PushOneKey() { return ++user_key_index; }
public:
void SetListKline(std::list<QDrawKline>* p);//设置K线容器
void SetCurve(std::list<double>* p, QString name, QColor clr);//设置曲线容器
void RemoveCoverage(void* p);//移除容器对应的图层
void SetInterval(int interval);//间距
void SetSpace(int top, int bottom, int left, int right);//上下空挡
void SetDataCount(int count);//总数
void SetOffset(int offset);//与最后一根的距离
void SetPrecision(int precision);//设置精度,小数点后保留位数,类似期权数据大于0小于1的情况
void SetUserPosition(int user_key, QDrawPosition* p);
void SetShowUserPosition(int user_key);
public slots:
void DoDraw(bool draw_all);
void PageUpDown(bool up_down);
protected:
void DrawCoordinate();//坐标线
void DrawProcess();//绘图过程
//更新最大最小值
void UpdateMaxMin();
void UpdateMaxMin(kline_riter riter_start, kline_riter riter_end, kline_riter& offset_riter);
void UpdateMaxMin(curve_riter riter_start, curve_riter riter_end, curve_riter& offset_riter);
void DrawKline(DataCoverage& dc);//K线
void DrawCurve(DataCoverage& dc);//曲线
void DrawSignal();//信号位置
void DrawPosition();//仓位线
void ErasePixmap(QPixmap& pix, QRect& rc);//部分擦除
int DataToPointY(const double& value, const double& min_data, const double& unit_data);//数值转为Y坐标
double PointYToData(int& y, const double& min_data, const double& unit_data);
void CenterPointCount();//中心点个数
void UnitData();//Y轴单位
void DrawOneKline(QPixmap& pix, int x, QDrawKline& k, QColor& clr);//画一个K线
protected:
void resizeEvent(QResizeEvent *event);
void focusInEvent(QFocusEvent *event);
void focusOutEvent(QFocusEvent *event);
void leaveEvent(QEvent *event);
void mouseMoveEvent(QMouseEvent *event);//数值提示
void mouseDoubleClickEvent(QMouseEvent *event);//十字线
void keyReleaseEvent(QKeyEvent *event);//上下左右
void paintEvent(QPaintEvent *event);
protected:
QList<DataCoverage> m_dataCoverage;//数据&图层
static int user_key_index;
private:
bool m_draw_all;//是否全部重绘
//数据结构参数
double m_try_min_data, m_try_max_data;//更新之后的最大最小
double m_min_data, m_max_data;//实际最大最小
int m_min_data_x, m_max_data_x;//实际最大最小对应的位置
double m_unit_data;//一个像素点对应的高度数值
int m_max_center_point;//中心点的最大数
int m_widget_width, m_widget_height;
int m_interval;
bool m_cross;
QRect m_space;
int m_precision;
int m_total_count;
int m_offset;
int m_last_offset;
//绘画工具
QPixmap m_pixCoor;
QPixmap m_pixCross;
QPixmap m_pixPosition;
QPen m_pen;
QBrush m_brush;
QColor m_clrRed;
QColor m_clrGreen;
QKlineInfo* m_widget_info;
};
#include "QDrawModule.h"
#include <QPainter>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QFocusEvent>
#include "qcoreevent.h"
int QDrawModule::user_key_index = 0;
QDrawModule::QDrawModule(QWidget *parent)
: QFrame(parent)
{
m_clrGreen = QColor(0, 255, 255);
m_clrRed = QColor(255, 60, 57);
m_offset = 0;
m_last_offset = m_offset;
m_interval = 11;
m_precision = 0;
m_cross = false;
m_showUserPosition = SHOW_ALL_POSITION;
m_try_min_data = m_min_data = 1.79769313486231570E+308;
m_try_max_data = m_max_data = -1.79769313486231570E+308;
m_widget_info = new QKlineInfo(this);
m_widget_info->setObjectName("m_widget_info");
m_widget_info->setStyleSheet("#m_widget_info{border:1px solid white}");
if (!m_cross) m_widget_info->hide();
setMouseTracking(true);
setFocusPolicy(Qt::FocusPolicy::ClickFocus);
}
QDrawModule::~QDrawModule()
{
}
void QDrawModule::SetListKline(std::list<QDrawKline>* p)
{
DataCoverage dc;
dc.m_pListKline = p;
dc.ResetIter();
m_dataCoverage.push_back(dc);
}
void QDrawModule::SetCurve(std::list<double>* p, QString name, QColor clr)
{
DataCoverage dc;
dc.m_pListCurve = p;
dc.name = name;
dc.clr = clr;
dc.ResetIter();
m_dataCoverage.push_back(dc);
}
void QDrawModule::RemoveCoverage(void * p)
{
for (auto& dc : m_dataCoverage)
{
if (dc.m_pListKline == p || dc.m_pListCurve == p) {
m_dataCoverage.removeOne(dc);
break;
}
}
}
void QDrawModule::SetInterval(int interval)
{
m_interval = interval;
}
void QDrawModule::SetSpace(int top, int bottom, int left, int right)
{
m_space.setTop(top);
m_space.setBottom(bottom);
m_space.setLeft(left);
m_space.setRight(right);
m_widget_info->setFixedSize(left - 1, 340);
}
void QDrawModule::SetDataCount(int count)
{
m_total_count = count;
}
void QDrawModule::SetOffset(int offset)
{
m_offset = offset;
}
void QDrawModule::SetPrecision(int precision)
{
m_precision = precision;
m_widget_info->SetPrecision(precision);
}
void QDrawModule::SetUserPosition(int user_key, QDrawPosition* p)
{
UserPosition up;
up.user_key = user_key;
up.m_position = p;
m_userPosition.push_back(up);
}
void QDrawModule::SetShowUserPosition(int user_key)
{
m_showUserPosition = user_key;
}
void QDrawModule::DoDraw(bool draw_all)
{
m_draw_all = draw_all;
CenterPointCount();
UpdateMaxMin();
if (m_try_max_data != m_max_data || m_try_min_data != m_min_data)
{
m_max_data = m_try_max_data;
m_min_data = m_try_min_data;
m_draw_all = true;
}
UnitData();
DrawProcess();
if (m_cross)
{
m_pixCross = QPixmap(m_widget_width, m_widget_height);
m_pixCross.fill(Qt::transparent);
}
update();
}
void QDrawModule::PageUpDown(bool up_down)
{
if (up_down)
{
m_offset -= m_max_center_point;
if (m_offset < 0)
m_offset = 0;
}
else
{
int max_offset = m_max_center_point;
for (auto& dc : m_dataCoverage)
{
if (dc.m_pListKline)
{
int offset_count = 0;
auto riter = dc.m_offset_kline_iter;
while (riter != dc.m_pListKline->rend() && offset_count < m_max_center_point)
{
riter++;
offset_count++;
}
if (max_offset > offset_count)
max_offset = offset_count;
}
if (dc.m_pListCurve)
{
int offset_count = 0;
auto riter = dc.m_offset_curve_iter;
while (riter != dc.m_pListCurve->rend() && offset_count < m_max_center_point)
{
riter++;
offset_count++;
}
if (max_offset > offset_count)
max_offset = offset_count;
}
}
m_offset += max_offset;
}
}
void QDrawModule::DrawCoordinate()
{
int work_height = m_widget_height - m_space.top() - m_space.bottom();
int line_numb = work_height / 50;//需要绘制的坐标线个数
double line_interval = (m_max_data - m_min_data) / (line_numb + 1);
double fundation = 1;
if (line_interval > 1)
{
int interval = line_interval;
while (interval / 10 > 0)
{
interval /= 10;
fundation *= 10;
}
fundation *= interval + 1;
}
else if (line_interval > 0)
{
while (line_interval < 1)
{
line_interval *= 10;
fundation *= 10;
}
line_interval = (int)line_interval;
fundation = line_interval / fundation;
}
else
{
return;
}
double start_line = ((int)(m_min_data / fundation) + 1) * fundation;
m_pixCoor = QPixmap(m_widget_width, m_widget_height);
m_pixCoor.fill(Qt::transparent);
QPainter painter(&m_pixCoor);
m_pen.setColor(QColor(132, 0, 0));
m_pen.setWidth(1);
m_pen.setStyle(Qt::PenStyle::SolidLine);
painter.setPen(m_pen);
painter.drawLine(m_space.left() - 1, 0, m_space.left() - 1, m_widget_height);
m_pen.setStyle(Qt::PenStyle::DotLine);
QPen white_pen = m_pen;
white_pen.setColor(QColor(192, 192, 192));
white_pen.setWidth(1);
white_pen.setStyle(Qt::PenStyle::SolidLine);
do
{
int point_y = DataToPointY(start_line, m_min_data, m_unit_data);
painter.setPen(m_pen);
painter.drawLine(m_space.left(), point_y, m_widget_width - m_space.right(), point_y);
painter.setPen(white_pen);
painter.drawText(0, point_y - 15, m_space.left() - 2, 30, Qt::AlignRight | Qt::AlignCenter, QString::number(start_line, 'f', m_precision));
start_line += fundation;
} while (start_line <= m_max_data);
}
void QDrawModule::UpdateMaxMin()
{
m_try_min_data = 1.79769313486231570E+308;
m_try_max_data = -1.79769313486231570E+308;
for (auto& dc : m_dataCoverage)
{
if (dc.m_pListKline) {
UpdateMaxMin(dc.m_pListKline->rbegin(), dc.m_pListKline->rend(), dc.m_offset_kline_iter);
}
if (dc.m_pListCurve) {
UpdateMaxMin(dc.m_pListCurve->rbegin(), dc.m_pListCurve->rend(), dc.m_offset_curve_iter);
}
}
}
void QDrawModule::DrawProcess()
{
if (m_draw_all)
DrawCoordinate();
for (auto& dc : m_dataCoverage)
{
if (dc.m_pListKline)
DrawKline(dc);
if (dc.m_pListCurve)
DrawCurve(dc);
}
DrawSignal();
DrawPosition();
}
#define UPDATE_OFFSET_RITER()\
int bak_offset = m_offset;\
if (bak_offset > m_last_offset)\
{\
while (bak_offset != m_last_offset)\
{\
if (offset_riter != riter_end)\
{\
offset_riter++;\
}\
bak_offset--;\
}\
}\
else if (bak_offset < m_last_offset)\
{\
while (bak_offset != m_last_offset)\
{\
if (offset_riter != riter_start)\
{\
offset_riter--;\
}\
bak_offset++;\
}\
}
void QDrawModule::UpdateMaxMin(kline_riter riter_start, kline_riter riter_end, kline_riter& offset_riter)
{
UPDATE_OFFSET_RITER()
riter_start = offset_riter;
int start_x = m_max_center_point;
while (riter_start != riter_end && start_x > 0)
{
if (riter_start->High() > m_try_max_data) {
m_try_max_data = riter_start->High();
m_max_data_x = start_x;
}
if (riter_start->Low() < m_try_min_data) {
m_try_min_data = riter_start->Low();
m_min_data_x = start_x;
}
riter_start++;
start_x--;
}
}
void QDrawModule::UpdateMaxMin(curve_riter riter_start, curve_riter riter_end, curve_riter& offset_riter)
{
UPDATE_OFFSET_RITER()
riter_start = offset_riter;
int start_x = m_max_center_point;
while (riter_start != riter_end && start_x > 0)
{
if (*riter_start > m_try_max_data)
m_try_max_data = *riter_start;
if (*riter_start < m_try_min_data)
m_try_min_data = *riter_start;
start_x--;
riter_start++;
}
}
void QDrawModule::DrawKline(DataCoverage& dc)
{
QPixmap& myPix = dc.m_pixCoverage;
kline_riter riter_start = dc.m_offset_kline_iter == dc.m_pListKline->rend() ?
dc.m_pListKline->rbegin() : dc.m_offset_kline_iter;
kline_riter riter_end = dc.m_pListKline->rend();
int start_x = m_max_center_point;
if (m_draw_all)
{
myPix = QPixmap(m_widget_width - m_space.left(), m_widget_height);
myPix.fill(Qt::transparent);
while (riter_start != riter_end && start_x > 0)
{
DrawOneKline(myPix, start_x, *riter_start, dc.clr);
riter_start++;
start_x--;
}
}
else
{
QRect rc;
rc.setLeft((m_max_center_point - 1) * (m_interval + m_interval / 5));
rc.setTop(0);
rc.setHeight(m_widget_height);
rc.setWidth(m_interval);
ErasePixmap(myPix, rc);
DrawOneKline(myPix, start_x, *riter_start, dc.clr);
}
}
void QDrawModule::DrawCurve(DataCoverage& dc)
{
QPixmap& myPix = dc.m_pixCoverage;
curve_riter riter_start = dc.m_offset_curve_iter == dc.m_pListCurve->rend() ?
dc.m_pListCurve->rbegin() : dc.m_offset_curve_iter;
curve_riter riter_end = dc.m_pListCurve->rend();
if (m_draw_all) {
myPix = QPixmap(m_widget_width - m_space.left(), m_widget_height);
myPix.fill(Qt::transparent);
}
else
{
QRect rc;
int cent_x2 = (m_max_center_point - 2) * (m_interval + m_interval / 5) + m_interval / 2;
rc.setLeft(cent_x2);
rc.setTop(0);
rc.setHeight(m_widget_height);
rc.setWidth(m_interval);
ErasePixmap(myPix, rc);
}
QPainter painter(&myPix);
m_pen.setColor(dc.clr);
m_pen.setWidth(1);
m_pen.setStyle(Qt::SolidLine);
painter.setPen(m_pen);
int start_x = m_max_center_point;
bool bFirstPoint = true;
QPoint last_point;
if (m_draw_all)
{
while (riter_start != riter_end && start_x > 0)
{
int cent_x = (start_x - 1) * (m_interval + m_interval / 5) + m_interval / 2;
QPoint this_point(cent_x, DataToPointY(*riter_start, m_min_data, m_unit_data));
if (!bFirstPoint)
{
painter.drawLine(this_point, last_point);
}
last_point = this_point;
bFirstPoint = false;
riter_start++;
start_x--;
}
}
else
{
if (riter_start != riter_end && start_x > 0)
{
int cent_x = (start_x - 1) * (m_interval + m_interval / 5) + m_interval / 2;
last_point = QPoint(cent_x, DataToPointY(*riter_start, m_min_data, m_unit_data));
riter_start++;
start_x--;
}
if (riter_start != riter_end && start_x > 0)
{
int cent_x = (start_x - 1) * (m_interval + m_interval / 5) + m_interval / 2;
QPoint this_point(cent_x, DataToPointY(*riter_start, m_min_data, m_unit_data));
painter.drawLine(this_point, last_point);
}
}
}
void QDrawModule::DrawSignal()
{
}
void QDrawModule::DrawPosition()
{
if (m_userPosition.size() == 0)
return;
m_pixPosition = QPixmap(m_widget_width - m_space.left() - m_space.right(), m_widget_height);
m_pixPosition.fill(Qt::transparent);
QPainter painter(&m_pixPosition);
auto lambda_func = [&](int user_key, QColor clr, int& count, double& avg_price)
{
m_pen.setColor(clr);
m_pen.setStyle(Qt::PenStyle::DashLine);
painter.setPen(m_pen);
int y = DataToPointY(avg_price, m_min_data, m_unit_data);
QString strInfo = QString(u8"[%1]%2手,均价:%3").arg(user_key).
arg(count).arg(QString::number(avg_price, 'f', m_precision + 1));
int txt_length = strInfo.size() * 8;
if (y < m_space.top())
{
painter.drawText(1, m_space.top(), txt_length, 20, Qt::AlignLeft | Qt::AlignCenter, strInfo);
painter.drawLine(txt_length, m_space.top() + 10, m_widget_width - m_space.right(), m_space.top() + 10);
}
else if (y > m_widget_height - m_space.bottom())
{
painter.drawText(1, m_widget_height - m_space.bottom() - 20, txt_length, 20, Qt::AlignLeft | Qt::AlignCenter, strInfo);
painter.drawLine(txt_length, m_widget_height - m_space.bottom() - 10, m_widget_width - m_space.right(), m_widget_height - m_space.bottom() - 10);
}
else
{
painter.drawText(1, y - 10, txt_length, 20, Qt::AlignLeft | Qt::AlignCenter, strInfo);
painter.drawLine(txt_length, y, m_widget_width - m_space.right(), y);
}
};
for (auto& up : m_userPosition)
{
if (!up.m_position)
continue;
if (m_showUserPosition == SHOW_ALL_POSITION || m_showUserPosition == up.user_key)
{
if (up.m_position->GetLongHolding() > 0)
{
lambda_func(up.user_key, "#FF5C5C", up.m_position->GetLongHolding(), up.m_position->GetLongAvgPrice());
}
else
{
lambda_func(up.user_key, "#00FF00", up.m_position->GetShortHolding(), up.m_position->GetShortAvgPrice());
}
}
}
}
void QDrawModule::ErasePixmap(QPixmap& pix, QRect& rc)
{
QPainter painter(&pix);
painter.setCompositionMode(QPainter::CompositionMode_Source);
painter.fillRect(rc, Qt::transparent);
}
int QDrawModule::DataToPointY(const double& value, const double& min_data, const double& unit_data)
{
return m_widget_height - m_space.bottom() - (value - min_data) / unit_data;
}
double QDrawModule::PointYToData(int& y, const double& min_data, const double& unit_data)
{
return (m_widget_height - m_space.bottom() - y) * unit_data + min_data;
}
void QDrawModule::CenterPointCount()
{
int work_width = m_widget_width - m_space.left() - m_space.right();
m_max_center_point = work_width / (m_interval + m_interval / 5);
}
void QDrawModule::UnitData()
{
int work_height = m_widget_height - m_space.top() - m_space.bottom();
m_unit_data = (m_max_data - m_min_data) / work_height;
}
void QDrawModule::DrawOneKline(QPixmap & pix, int x, QDrawKline & k, QColor& clr)
{
QPainter painter(&pix);
int clr_idx;
if (k.Open() > k.Close())
{
m_pen.setColor(m_clrGreen);
clr_idx = 1;
}
else if (k.Open() < k.Close())
{
m_pen.setColor(m_clrRed);
clr_idx = 0;
}
else
{
m_pen.setColor(Qt::white);
clr_idx = 2;
}
m_pen.setWidth(1);
m_pen.setStyle(Qt::PenStyle::SolidLine);
painter.setPen(m_pen);
painter.setBrush(m_brush);
int left_x = (x - 1) * (m_interval + m_interval / 5);
int cent_x = left_x + m_interval / 2;
int right_x = left_x + m_interval - 1;
int top_y = DataToPointY(k.High(), m_min_data, m_unit_data);
int open_y = DataToPointY(k.Open(), m_min_data, m_unit_data);
int close_y = DataToPointY(k.Close(), m_min_data, m_unit_data);
int low_y = DataToPointY(k.Low(), m_min_data, m_unit_data);
if (clr_idx == 0)
{//红色空心
painter.drawRect(QRect(left_x, close_y, m_interval - 1, open_y - close_y));
painter.drawLine(cent_x, top_y, cent_x, close_y);
painter.drawLine(cent_x, low_y, cent_x, open_y);
}
else if (clr_idx == 1)
{//绿色实心
painter.fillRect(QRect(left_x, open_y, m_interval, close_y - open_y), m_clrGreen);
painter.drawLine(cent_x, top_y, cent_x, low_y);
}
else
{//白色十字
painter.drawLine(left_x, open_y, right_x, open_y);
painter.drawLine(cent_x, top_y, cent_x, low_y);
}
if (x == m_min_data_x)
{//最小值
m_pen.setColor(Qt::white); painter.setPen(m_pen);
if (cent_x > 60)
painter.drawText(cent_x - 60, low_y - 15, 60, 30, Qt::AlignRight | Qt::AlignCenter, QString(u8"%1→").arg(QString::number(k.Low(), 'f', m_precision)));
else
painter.drawText(cent_x - 4, low_y - 15, 60, 30, Qt::AlignLeft | Qt::AlignCenter, QString(u8"←%1").arg(QString::number(k.Low(), 'f', m_precision)));
}
if (x == m_max_data_x)
{//最大值
m_pen.setColor(Qt::white); painter.setPen(m_pen);
if (cent_x > 60)
painter.drawText(cent_x - 60, top_y - 15, 60, 30, Qt::AlignRight | Qt::AlignCenter, QString(u8"%1→").arg(QString::number(k.High(), 'f', m_precision)));
else
painter.drawText(cent_x - 4, top_y - 15, 60, 30, Qt::AlignLeft | Qt::AlignCenter, QString(u8"←%1").arg(QString::number(k.High(), 'f', m_precision)));
}
}
void QDrawModule::resizeEvent(QResizeEvent * event)
{
m_widget_width = this->width();
m_widget_height = this->height();
DoDraw(true);
m_widget_info->move(0, m_space.top());
}
void QDrawModule::focusInEvent(QFocusEvent *event)
{
grabKeyboard();
}
void QDrawModule::focusOutEvent(QFocusEvent *event)
{
releaseKeyboard();
}
void QDrawModule::leaveEvent(QEvent *event)
{
m_pixCross = QPixmap(m_widget_width, m_widget_height);
m_pixCross.fill(Qt::transparent);
update();
}
void QDrawModule::mouseMoveEvent(QMouseEvent *event)
{
if (event->pos().x() <= m_space.left() || event->pos().x() >= (m_widget_width - m_space.right()))
{
leaveEvent(nullptr);
return;
}
m_pixCross = QPixmap(m_widget_width, m_widget_height);
m_pixCross.fill(Qt::transparent);
int y = event->pos().y();
m_pen.setStyle(Qt::PenStyle::SolidLine);
m_pen.setColor(Qt::white);
QPainter painter(&m_pixCross);
painter.setPen(m_pen);
painter.fillRect(0, y - 10, m_space.left() - 2, 20, QColor(0, 97, 255));
painter.drawText(0, y - 10, m_space.left() - 2, 20, Qt::AlignRight | Qt::AlignCenter, QString::number(PointYToData(y, m_min_data, m_unit_data), 'f', m_precision));
if (m_cross)
{
for (auto& dc : m_dataCoverage)
{
painter.drawPixmap(m_space.left(), 0, dc.m_pixCoverage);
}
painter.setCompositionMode(QPainter::CompositionMode_Difference);
painter.setPen(m_pen);
painter.drawLine(m_space.left(), y, m_widget_width - m_space.right(), y);
int draw_x = 0;
int str_x = m_space.left() + 1;
double last_close;
for (auto& dc : m_dataCoverage)
{
if (dc.m_pListKline) {
if (dc.m_offset_kline_iter == dc.m_pListKline->rend())
continue;
auto riter = dc.m_offset_kline_iter;
int x = m_max_center_point;
int main_x = event->pos().x() - m_space.left();
int last_left = m_widget_width - m_space.left();
while (x > 0 && riter != dc.m_pListKline->rend())
{
int left_x = (x - 1) * (m_interval + m_interval / 5);
if (main_x >= left_x && main_x <= last_left)
{
draw_x = left_x + m_interval / 2;
QDrawKline k = *riter;
last_close = k.Open();
riter++;
if (riter != dc.m_pListKline->rend())
last_close = riter->dClose;
if (m_widget_info->isVisible())
m_widget_info->UpdateKline(k, last_close);
break;
}
riter++;
x--;
last_left = left_x;
}
}
if (dc.m_pListCurve) {
if (dc.m_offset_curve_iter == dc.m_pListCurve->rend())
continue;
auto riter = dc.m_offset_curve_iter;
int x = m_max_center_point;
int main_x = event->pos().x() - m_space.left();
int last_left = m_widget_width - m_space.left();
while (x > 0 && riter != dc.m_pListCurve->rend())
{
int left_x = (x - 1) * (m_interval + m_interval / 5);
if (main_x >= left_x && main_x <= last_left)
{
draw_x = left_x + m_interval / 2;
m_pen.setColor(dc.clr);
painter.setPen(m_pen);
QString strIndex = dc.name + ":";
strIndex.append(QString::number(*riter, 'f', m_precision));
painter.drawText(str_x, 0, strIndex.length() * 8, 20, Qt::AlignLeft | Qt::AlignCenter, strIndex);
str_x += strIndex.length() * 8 + 5;
break;
}
riter++;
x--;
last_left = left_x;
}
}
}
m_pen.setColor(Qt::white);
painter.setPen(m_pen);
if (draw_x != 0)
painter.drawLine(draw_x + m_space.left(), 0, draw_x + m_space.left(), m_widget_height);
}
update();
}
void QDrawModule::mouseDoubleClickEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
m_cross = !m_cross;
if (m_cross)
setCursor(Qt::BlankCursor);
else
setCursor(Qt::ArrowCursor);
for (auto& dc : m_dataCoverage)
{
if (dc.m_pListKline)
{
if (!m_cross)
{
m_widget_info->hide();
}
else
{
m_widget_info->show();
}
break;
}
}
mouseMoveEvent(event);
}
}
void QDrawModule::keyReleaseEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Up)
{
if (m_interval < 61)
{
m_interval += 2;
}
else
return;
}
else if (event->key() == Qt::Key_Down)
{
if (m_interval > 1)
{
m_interval -= 2;
}
else
return;
}
else if (event->key() == Qt::Key_Left)
{
QPoint point = mapFromGlobal(QCursor::pos());
if (!m_cross || point.x() <= (m_space.left() + m_interval + m_interval / 5))
{
bool enable_offset = true;
for (auto& dc : m_dataCoverage)
{
if (dc.m_pListKline)
{
auto riter = dc.m_offset_kline_iter;
enable_offset &= riter != dc.m_pListKline->rend();
}
if (dc.m_pListCurve)
{
auto riter = dc.m_offset_curve_iter;
enable_offset &= riter != dc.m_pListCurve->rend();
}
}
if (enable_offset)
m_offset++;
}
if (m_cross)
{
if (point.x() > (m_space.left() + m_interval + m_interval / 5) && point.x() <= (m_widget_width - m_space.right()))
{
QCursor::setPos(mapToGlobal(QPoint(point.x() - m_interval - m_interval / 5, point.y())));
}
}
}
else if (event->key() == Qt::Key_Right)
{
QPoint point = mapFromGlobal(QCursor::pos());
if (!m_cross || point.x() > (m_space.left() + (m_max_center_point - 1) * (m_interval + m_interval / 5)))
{
if (m_offset > 0)
{
m_offset--;
}
}
if (m_cross)
{
QPoint point = mapFromGlobal(QCursor::pos());
if (point.x() > m_space.left() && point.x() <= (m_space.left() + (m_max_center_point - 1) * (m_interval + m_interval / 5)))
{
QCursor::setPos(mapToGlobal(QPoint(point.x() + m_interval + m_interval / 5, point.y())));
}
}
}
else if (event->key() == Qt::Key_PageUp)
{
PageUpDown(true);
}
else if (event->key() == Qt::Key_PageDown)
{
PageUpDown(false);
}
else
{
return;
}
DoDraw(true);
m_last_offset = m_offset;
}
void QDrawModule::paintEvent(QPaintEvent * event)
{
QPainter painter(this);
painter.drawPixmap(0, 0, m_pixCoor);
for (auto& dc : m_dataCoverage)
{
painter.drawPixmap(m_space.left(), 0, dc.m_pixCoverage);
}
painter.drawPixmap(m_space.left(), 0, m_pixPosition);
painter.drawPixmap(0, 0, m_pixCross);
}
今天的文章k线、指标绘制分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/26045.html