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新的匹配的类型。