Qt之动态换图

Abstract:
1,“动态创建”
2,封装qwt的Plot类
3,拖拽
4,Plot类型转换


一、功能描述
假设有一个信号分析的软件工程,在它的主窗口mainwindow上,左侧是一个测试项列表(QListWidget),右侧是四个图形窗口(Plot or QTableWidget)用于观察测试项的图形结果。现在要实现的功能是:通过拖拽右侧测试项列表中任一项到右侧的任一窗口,即可在该窗口观察对应的测试项图形结果,且这个图形结果有多种形式,如曲线图(PlotCurve)、光栅图(PlotRaster)和表格( QTableWidget )等,具体是哪种形式由具体的测试项决定。
二、功能分解实现
1,UI布局
通过Qt Designer,在左侧布一个 QListWidget,右侧布一个QFrame。然后将 QListWidget提升为CustomListWidget,使它支持拖拽功能(参考《C++ GUI Qt4》第9章“拖拽”的第一个例子),主要是重写函数 mousePressEvent ()和 mouseMoveEvent()。再然后 将右侧 QFrame提升为QuadSplitter(参考我的博文: QuadSplitter 。最后就是用四个QWidget填充QuadSplitter。具体如下:
在mianwindow.h中定义:
    static const int    s_iWidgetsNumber = 4;    // 右侧显示区的图表数量
    QWidget           * m_pShowWidgets[2][2];    // 右侧显示区的四幅图或表
实现:
void MainWindow::CreateCharts()
{
    for (int i = 0; i <<span style=" color:#c0c0c0;"> s_iWidgetsNumber; i++)
    {
        // 创图表
        m_pShowWidgets[i/2][i%2] = new Plot;
        ui->frameDisplay->addWidget(m_pShowWidgets[i/2][i%2], i/2, i%2);

        connect(m_pShowWidgets[i/2][i%2], SIGNAL(FullScreenToggle(QWidget*,bool)), this, SLOT(OnToggleSize(QWidget*,bool)));
        connect(m_pShowWidgets[i/2][i%2], SIGNAL(DragComplete(QWidget*, QString)), this, SLOT(OnDragComplete(QWidget*, QString)));
    }
}
上述代码中,还建立了两对信号与槽的连接,在后续将予以说明。

2,数据观察窗口
上面已经提到,测试项的图形结果有几种呈现形式:曲线图、光栅图和表格。其中图类,我采用QWT库,并封装一个Plot类,然后派生出PlotCurve和PlotRaster;表格的实现,为简单起见也是从Plot类派生出一个PlotTable类,在构造函数中将Plot相关的特性全部隐藏,然后采用PIMP模式( pointer to implementation模式 ),定义一个CustomTableWidget指针成员。事实上,直接使用CustomTableWidget也可以,只是在客户代码中要进行相应修改。下面再列出一些关键代码:
1)Plot的相关信号函数
signals:
    void FullScreenToggle(QWidget* pWidget, bool bFullScreen);
    void DragComplete(QWidget* pWidget, QString strTestItem);
2)Plot需要重写的事件处理函数
void Plot::mouseDoubleClickEvent(QMouseEvent *event)
{
    if (Qt::LeftButton == event->button())
    {
        
        m_bFullScreen = !m_bFullScreen;
        emit FullScreenToggle(this, m_bFullScreen);
    }
    else if (Qt::RightButton == event->button())
    {
        
        if (m_pPlotZoomer != NULL)
            m_pPlotZoomer->zoom(0);
    }
}

void Plot::dragEnterEvent(QDragEnterEvent *event)
{
    DragListWidget *source = dynamic_cast<<span style=" color:#800080;">DragListWidget*>(event->source());
    if (NULL == source)
        return;

    event->setDropAction(Qt::MoveAction);
    event->accept();
}

void Plot::dropEvent(QDropEvent *event)
{
    DragListWidget *source = dynamic_cast<<span style=" color:#800080;">DragListWidget*>(event->source());
    if (NULL == source)
        return;

    event->setDropAction(Qt::MoveAction);
    event->accept();

    emit DragComplete(this, event->mimeData()->text());
}

这个几个函数主要实现两个功能。第一个是实现双击放大指定窗口到填充整个QuadSplitter。其原理是Plot接收鼠标双击事件,然后给mainwindow发信号,mainwindow的槽函数再来调整 QuadSplitter。这就是上面第一个connect()函数的作用。第二个是实现拖拽进入处理和拖拽释放处理。通过拖拽,在本例传递过来的是一个字符串,即CustomListWidget中某项-测试项名。

3,测试项的“动态创建”
对于测试项,我采用的是“动态创建”的设计模式,参考我的博文:  动态创建。不同的是,我的CTestItem是继承自QObject,并添加了Q_OBJECT宏。因为,我在mianwindow中是这样定义的:
QPointer<<span style=" color:#800080;">CTestItem> m_pTestItems[2][2];      // 显示在界面的四个测试项,与上面的四幅图或表一一对应
将CTestItem用QPointer管理起来,不需要手动管理内存。但是,QPointer管理的对象必须是QObject及其子类,且不可复制。
再来看切换函数:
void MainWindow::OnDragComplete(QWidget *pWidget, QString itemName)
{
    for (int i = 0; i <<span style=" color:#c0c0c0;"> s_iWidgetsNumber; ++i)
    {
        if (pWidget == (m_pShowWidgets[i/2][i%2]))
        {
//             if (itemName.toStdWString().c_str() == m_pTestItems[i/2][i%2]->GetClassName())
//                 return;

            m_pTestItems[i/2][i%2] = CRuntimeClass::LoadObject(itemName.toStdWString().c_str(), &m_pShowWidgets[i/2][i%2]);

            // 测试项切换时,如果前后图表的类型不一致,会在CTestItem的构造函数中进行delete和new的操作
            // delete操作会消除m_pShowWidgets之前建立起来的父子对象关系以及信号与槽函数的绑定关系,故需重新建立
            if (m_pShowWidgets[i/2][i%2]->parentWidget() != ui->frameDisplay)
            {
                ui->frameDisplay->addWidget(m_pShowWidgets[i/2][i%2], i/2, i%2);
                connect(m_pShowWidgets[i/2][i%2], SIGNAL(FullScreenToggle(QWidget*,bool)), this, SLOT(OnToggleSize(QWidget*,bool)));
                connect(m_pShowWidgets[i/2][i%2], SIGNAL(DragComplete(QWidget*, QString)), this, SLOT(OnDragComplete(QWidget*, QString)));
            }
            break;
        }
    }
}

仅仅只需将测试项名(字符串)传入即可,其他的由“动态创建”机制来处理。另一出彩的设计是,在具体的CTestItemA的构造函数中,需要对传入的QWidget进行类型判断。如下:
    if (!(m_pShowWidget = qobject_cast<<span style=" color:#800080;">PlotCurve *>(*ppWidget)))
    {
        qDebug("qobject_cast *>(ppWidget) fail");
        delete (*ppWidget);
        *ppWidget = NULL;
        *ppWidget = m_pShowWidget = new PlotCurve;
    }
如果传入的是匹配的类型则直接转换,不匹配则删除之前的再new新的匹配的类型。



你可能感兴趣的:(Qt之动态换图)