我正在参加「掘金·启航计划」
简介
最近在练习使用C++/MFC,练习的内容是要使用MFC实现排序算法的柱状图排序过程的动态可视化效果。使用的工具是Microsoft Visual Studio 2015。
建立项目
1.新建项目
- 新建项目-选择MFC应用程序-填写名称-确定。
- MFC应用程序向导选择下一步-基于对话框-在静态库中使用MFC-完成。
- 在资源视图中找到dialog-删除视图页面中的三个控件。
右侧属性页可以更新窗口/控件名称等。
2.导入源码包
- 下载地址:
例子源码下载:www.codeproject.com/Articles/14…
选择:Download source下载
- 下载成功后,解压,复制文件夹到项目目录下(可以将此文件夹改名为ChartCtrl)
- 导入项目目录下
项目-添加现有项-选择刚才导入的文件夹-选择所有.h和.cpp文件导入
导入成功
3.制作界面
- 左侧工具箱中选择 Custom Control-控件拖入窗口,再放大,作为画面显示的地方。设置控件属性Style:0x52010000,设置Class:ChartCtrl(即刚才导入的包中的类)。
- 为此控件添加变量,在代码中使用此变量修改此界面。
右击控件-添加变量-设置变量类型和变量名
- 工具栏选择 Button-控件拖入窗口下方,设置显示文本“开始排序”,设置按钮ID:IDC_BUTTON_START
双击按钮,跳转到此按钮的点击事件函数中,此处编写点击按钮后需要响应的代码。
- 添加定时器
窗口-消息-WM_TIMER设置为OnTimer。
4.初步实现-随机动态柱状图
- button点击事件响应函数
void CStaticBarDlg::OnBnClickedButtonRefresh()
{
//启动定时器
SetTimer(0, 1000, NULL);//每隔 1 秒进行刷新
}
- 定时器函数
//定时器
void CStaticBarDlg::OnTimer(UINT_PTR nIDEvent)
{
if (nIDEvent == 0) {
//更新界面:-------------
//清除操作
m_chart.EnableRefresh(false); //在设置时,设置不能刷新
m_chart.GetTitle()->RemoveAll(); //删除原来的标题,否则会累加
m_chart.RemoveAllSeries(); //删除所有曲线
//设置X轴
CChartDateTimeAxis *pBottomAxis = m_chart.CreateDateTimeAxis(CChartCtrl::BottomAxis);
pBottomAxis->SetMinMax(0.5, 10);
pBottomAxis->SetDiscrete(true); //柱状图需要设置该属性
//创建Y轴
CChartStandardAxis *pLeftAxis = m_chart.CreateStandardAxis(CChartCtrl::LeftAxis);
pLeftAxis->SetMinMax(0, 1000);
pLeftAxis->GetLabel()->SetText(_T("Number"));
//图例
m_chart.GetLegend()->SetVisible(true); //设置显示图例
m_chart.GetLegend()->SetHorizontalMode(true);
m_chart.GetLegend()->UndockLegend(80, 50); //设置图例的位置
//添加标题
m_chart.GetTitle()->AddString(_T("test graph2动态柱状图"));
CChartFont titleFont;
//设置字体样式、大小、是否斜体,是否加粗,是否有下划线
titleFont.SetFont(_T("Arial Black"), 120, true, false, true);
m_chart.GetTitle()->SetFont(titleFont);
m_chart.GetTitle()->SetColor(RGB(0, 0, 0));
//设置背景色-渐变
m_chart.SetBackGradient(RGB(255, 255, 255), RGB(26, 161, 226), gtVertical);
//创建柱状图
CChartBarSerie* pBarSerie1 = m_chart.CreateBarSerie();
//X轴,Y轴绑定数据
for (int i = 0; i < 20; i++) {
//填充X轴数据
double x = (i + 1)*0.5;
//填充Y轴数据
int partical = 20 + rand() % (1000 - 30); //随机生成变量,范围20-90
pBarSerie1->AddPoint(x, partical);
}
pBarSerie1->SetColor(RGB(253, 118, 3));
pBarSerie1->SetName(_T("粒径"));
m_chart.EnableRefresh(true); //设置可以刷新
}
CDialogEx::OnTimer(nIDEvent);
}
三、实现排序算法可视化
1.思路
-
首先将数据排序一遍,计算出此排序需要进行多少步(多少次交换)-排序总次数。
-
然后重新进行排序,单步排序,元素发生一次交换则显示一次界面图形。相当于每次更新画面显示当前数据顺序。
-
记录当前排序几次,如果大于等于刚才记录的排序总次数,则说明已经完成排序不需再更新画面。
2.代码
(1)在nameDlg.h文件中添加函数和变量:
vector<int> m_numVec; //要排序的数组
int numMax = INT_MIN; //最大数据记录,用于画Y轴时的高度
int numMin = INT_MAX;
void sortArray(); //数据读取+调用排序函数
//插入排序
void insertSort(vector<int> _vec);//用于统计排序多少次
bool isCount = false; //是否已经计算过总共需要排序多少次
void insertSort(); //单步排序
int sortCount = 0; //一共需要排序的次数
int sortStateCount = 0;
afx_msg void refreshView();
(2)nameDlg.cpp文件中的各个函数
- void OnBnClickedButtonRefresh() 点击按钮的响应事件
void CStaticBarDlg::OnBnClickedButtonRefresh()
{
//开始排序
if (isCount) {
//已排序完成则不用再刷新
if (sortStateCount >= sortCount)return;
insertSort();
refreshView();
}
else { //还未count则先读取数据+count
sortArray();
refreshView();
SetTimer(0, 1000, NULL);
}
}
- void OnTimer(UINT_PTR nIDEvent) 定时器
void CStaticBarDlg::OnTimer(UINT_PTR nIDEvent)
{
if (nIDEvent == 0) {
if (sortStateCount < sortCount) {
OnBnClickedButtonRefresh();
}
}
CDialogEx::OnTimer(nIDEvent);//启动定时器
}
- void sortArray() 初始化数组+计数排序次数
void CStaticBarDlg::sortArray()
{
FILE *fp = fopen("E:/lisiyuan/VSproject/01SortData.txt", "r");
if (!fp) {
printf("文件打开失败\n");
return;
}
int num;
while (!feof(fp)) {
fscanf(fp, "%d", &num);
if (numMax < num) numMax = num;
if (numMin > num) numMin = num;
m_numVec.push_back(num);
}
//关闭file
fclose(fp);
vector<int> _vec;
_vec.assign(m_numVec.begin(), m_numVec.end());
insertSort(_vec);
}
- void insertSort(vector< int> _vec) 统计总共排序次数
void CStaticBarDlg::insertSort(vector<int> _vec)
{
isCount = true;
for (int i = 0; i < _vec.size(); i++) {
int num = _vec[i];
int j = i;
while (j > 0 && _vec[j - 1] > _vec[j]) {
swap(_vec[j - 1], _vec[j]);
j--;
sortCount++;
}
}
return;
}
- void insertSort() 单步排序函数
void CStaticBarDlg::insertSort()
{
for (int i = 0; i < m_numVec.size(); i++) {
bool sortOnce = false;
int num = m_numVec[i];
int j = i;
while (j > 0 && m_numVec[j - 1] > m_numVec[j]) {
swap(m_numVec[j - 1], m_numVec[j]);
j--;
sortOnce = true;
sortStateCount++;
break;
}
if (sortOnce)break;
}
return;
}
- void refreshView() 刷新页面
void CStaticBarDlg::refreshView()
{
//清除操作
m_chart.EnableRefresh(false); //在设置时,设置不能刷新
m_chart.GetTitle()->RemoveAll(); //删除原来的标题,否则会累加
m_chart.RemoveAllSeries(); //删除所有曲线
//设置X轴
CChartStandardAxis *pBottomAxis = m_chart.CreateStandardAxis(CChartCtrl::BottomAxis);
pBottomAxis->SetMinMax(0, m_numVec.size() + 1);
pBottomAxis->SetDiscrete(true); //柱状图需要设置该属性
pBottomAxis->SetVisible(false);
//创建Y轴
CChartStandardAxis *pLeftAxis = m_chart.CreateStandardAxis(CChartCtrl::LeftAxis);
pLeftAxis->SetMinMax(0, numMax + 20);
pLeftAxis->GetLabel()->SetText(_T("Number"));
//添加标题
m_chart.GetTitle()->AddString(_T("插入排序可视化"));
CChartFont titleFont;
//设置字体样式、大小、是否斜体,是否加粗,是否有下划线
titleFont.SetFont(_T("黑体"), 120, true, false, true);
m_chart.GetTitle()->SetFont(titleFont);
m_chart.GetTitle()->SetColor(RGB(0, 0, 0));
//设置背景色-渐变
m_chart.SetBackGradient(RGB(255, 255, 255), RGB(26, 161, 226), gtVertical);
//创建柱状图
CChartBarSerie* pBarSerie1 = m_chart.CreateBarSerie();
//X轴,Y轴绑定数据
for (int i = 0; i < m_numVec.size(); i++) {
//填充X轴数据
int x = i + 1;
//填充Y轴数据
int _num = m_numVec[i]; //数组的数据值
pBarSerie1->AddPoint(x, _num);
}
pBarSerie1->SetColor(RGB(253, 118, 3));
m_chart.EnableRefresh(true); //设置可以刷新
}
四、遇到的问题
1.复制项目代码后不能运行
项目代码复制一份之后,直接打开,运行会报错。根据错误提示在网上搜了好多方法,没有结果。于是决定重新写一次代码。在重新编写代码时,发现项目中新生成的文件导包时,将绝对路径写入了,也就是导入的是复制前的项目路径的包,因此试着将路径修改,然后终于可以成功运行。
2.柱状图中不能显示数字
在绘画的柱子上方使用画图的textOut函数即可显示画出的文本信息。 例:
CClientDC dcNum(this);
int x, y;
CString str;
dcNum.TextOut(x, y, str);
————THE END————