MFC绘制插入排序的可视化效果

326 阅读5分钟

我正在参加「掘金·启航计划」

简介

最近在练习使用C++/MFC,练习的内容是要使用MFC实现排序算法的柱状图排序过程的动态可视化效果。使用的工具是Microsoft Visual Studio 2015。

建立项目

1.新建项目

  • 新建项目-选择MFC应用程序-填写名称-确定。

image.png

  • MFC应用程序向导选择下一步-基于对话框-在静态库中使用MFC-完成。

image.png

image.png

  • 在资源视图中找到dialog-删除视图页面中的三个控件。

image.png 右侧属性页可以更新窗口/控件名称等。

2.导入源码包

  1. 下载地址:

例子源码下载:www.codeproject.com/Articles/14…

选择:Download source下载

image.png

  1. 下载成功后,解压,复制文件夹到项目目录下(可以将此文件夹改名为ChartCtrl)

image.png

image.png

  1. 导入项目目录下

项目-添加现有项-选择刚才导入的文件夹-选择所有.h和.cpp文件导入

image.png

image.png

image.png

导入成功

image.png

image.png

3.制作界面

  1. 左侧工具箱中选择 Custom Control-控件拖入窗口,再放大,作为画面显示的地方。设置控件属性Style:0x52010000,设置Class:ChartCtrl(即刚才导入的包中的类)。

image.png

  1. 为此控件添加变量,在代码中使用此变量修改此界面。

右击控件-添加变量-设置变量类型和变量名

image.png

image.png

  1. 工具栏选择 Button-控件拖入窗口下方,设置显示文本“开始排序”,设置按钮ID:IDC_BUTTON_START

image.png

双击按钮,跳转到此按钮的点击事件函数中,此处编写点击按钮后需要响应的代码。

image.png

image.png

  1. 添加定时器

窗口-消息-WM_TIMER设置为OnTimer。

image.png

4.初步实现-随机动态柱状图

  1. button点击事件响应函数
void CStaticBarDlg::OnBnClickedButtonRefresh()
{
    //启动定时器
    SetTimer(0, 1000, NULL);//每隔 1 秒进行刷新
}
  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.思路

  1. 首先将数据排序一遍,计算出此排序需要进行多少步(多少次交换)-排序总次数。

  2. 然后重新进行排序,单步排序,元素发生一次交换则显示一次界面图形。相当于每次更新画面显示当前数据顺序。

  3. 记录当前排序几次,如果大于等于刚才记录的排序总次数,则说明已经完成排序不需再更新画面。

2.代码

(1)在nameDlg.h文件中添加函数和变量:

image.png

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.复制项目代码后不能运行

项目代码复制一份之后,直接打开,运行会报错。根据错误提示在网上搜了好多方法,没有结果。于是决定重新写一次代码。在重新编写代码时,发现项目中新生成的文件导包时,将绝对路径写入了,也就是导入的是复制前的项目路径的包,因此试着将路径修改,然后终于可以成功运行。

image.png

2.柱状图中不能显示数字

在绘画的柱子上方使用画图的textOut函数即可显示画出的文本信息。 例:

CClientDC dcNum(this);
int x, y;
CString str;
dcNum.TextOut(x, y, str);

————THE END————