ControlPanelForm
用于显示界面左侧的信息栏,它有两部分组成:上面主要是一个QLabel
,用于显示样本的预览图,下方是一个QTableWidget
,用于显示扫描进度。是一个很粗糙的界面设计,但是用于演示也够了。
它包含四个slot
函数,我们会依次简单看一下:
显示预览图信息
函数onSigPreviewCaptured()
用于在收到sigPreviewed
后显示样本信息。它将收到的cv::Mat
转换成QPixmap
,并按照这个QLabel
的实际大小做缩放,并设置到QLabel
上面。
1 2 3 4 5 6 7 8 9 10
| void ControlPanelForm::onSigPreviewCaptured(cv::Mat prev_mat, cv::Mat label_mat, const QString &qrcode) { _impl->_prevMat = prev_mat.clone(); _impl->_qrcode = qrcode;
auto pixmap = ImageTool::MatToQPixmap(prev_mat).scaled(ui->previewImage->size(), Qt::KeepAspectRatio); ui->previewImage->setPixmap(pixmap); ui->previewImage->setEnabled(true); ui->previewLabel->setText(qrcode); }
|
绘制扫描范围
因为在ScanTask
中是将预览图和扫描范围分两次发送的,所以这里也需要提供两个slot
实现。它也很简单,也就是将传过来的矩形范围画到预览图上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void ControlPanelForm::onSigLowScanRanged(QRect rect) { void ControlPanelForm::onSigLowScanRanged(QRect rect) { RETURN_LOG_IF(_impl->_prevMat.empty(), "保存的预览图无效!"); RETURN_LOG_IF(!rect.isValid(), "无效的扫描区域!");
QPixmap thumbnail = ImageTool::MatToQPixmap(_impl->_prevMat).scaled(ui->previewImage->size(), Qt::KeepAspectRatio); QPainter painter(&thumbnail); painter.setPen(QPen((QColor(255,0,0, 180)), 2)); qreal ratio = thumbnail.width() / qreal(_impl->_prevMat.cols); painter.drawRect(QRect(rect.topLeft()*ratio, rect.size()*ratio)); ui->previewImage->setPixmap(thumbnail); } }
|
绘制扫描进度
ScanTask
通过两条信号来通知扫描进度信息:sigScanStart()
在启动扫描后发出,通知要扫描的照片的数量,而sigImageCaptured()
在采集到每张照片后发出,会携带这张照片的位置以及图像内容。我们会根据sigScanStart()
传过来的扫描的行列号计算表格的行列尺寸,并在收到sigImageCaptured()
后将代表这个视野的图片涂成绿色。我们只是处理了了连续扫描的情况,对于非连续扫描,传过来的columns
会是扫描视野数,rows
会是1,如何调整显示就不在这里讨论了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| void ControlPanelForm::onSigScanStart(int columns, int rows, int method) { auto container = ui->tableWidget->parentWidget(); if(method!=EScanMethod::eMatrixScan) { } int width = container->width(); int height = qRound((qreal)width * rows / columns); if(height + ui->tableWidget->y() > container->height()) { height = container->height() - ui->tableWidget->y(); width = qRound(height * columns / qreal(rows)); container->layout()->setAlignment(ui->tableWidget, Qt::AlignHCenter); }
ui->tableWidget->setRowCount(rows); ui->tableWidget->setColumnCount(columns); ui->tableWidget->setFixedSize(width, height); container->layout()->update(); }
void ControlPanelForm::onSigImageCaptured(const QPoint &pos, cv::Mat image) { Q_UNUSED(image) RETURN_LOG_IF(pos.x()<0||pos.y()<0, QString("pos值(%1,%2)非法!").arg(pos.x()).arg(pos.y()));
auto item = new QTableWidgetItem; item->setBackground(Qt::green); ui->tableWidget->setItem(pos.y(), pos.x(), item); }
|
实现MainClientForm
MainClientForm
是软件的主客户区域,它的主体是一个QGraphicsView
,用于显示拍摄的照片。
在Qt中显示图像,最常用的是QLabel
,就如我们在显示预览图时所作的。还有一种做法是使用QGraphicsView
框架,利用QGraphicsPixmapItem
来显示图片。
QGraphicsView
框架,据说最初的开发初衷是用于QtQuick里面的,但是后面却不知什么原因没有出现,据说是被废弃了。但是,我认为,这个框架是Qt的图像机制中最有价值的东西,这么说,QtWidget
,QQuick
,去掉了GraphicsView
之后剩下的东西,和WPF比起来,说垃圾有点过分,但也好不到哪里去,最多只是一个弱化版的WPF。但是有了GraphicsView
框架,就不一样了,在很多场景下,这是不可替代的。不过,现在客户端都在退化,我们看到,微软在WPF之后推出的一代代GUI框架,在功能上相对于WPF也是在不断弱化中。因为移动端不需要也做不了太复杂的功能。在Qt中,利用GraphicsView
框架,可以将整个程序界面建立在上面。有兴趣的人可以去看一下Qt的文档和例子。
在真实的产品中,我们需要对显示的图像做一些处理和计算,显示一些信息,在这里,我们只是简单显示一下图像就可以了。我们不会对View
和Scene
有什么特别的要求,所以简单地使用Qt提供的原生类就可以了,我们也仅仅需要显示一个QGraphicsPixmapItem
,所以连Scene
的实例都不需要保存,直接在构造函数中创建并设置给View
就行了。在后面,我们会使用更复杂的Scene/View
,这次权做热身使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| struct MainClientForm::Implementation { QGraphicsPixmapItem *_photoItem; };
MainClientForm::MainClientForm(QWidget *parent) : QWidget(parent) , ui(new Ui::MainClientForm) , _impl{new Implementation} { ui->setupUi(this); ui->photoView->setScene(new QGraphicsScene(0,0,2448,2048, this)); _impl->_photoItem = ui->photoView->scene()->addPixmap(QPixmap()); }
void MainClientForm::resizeEvent(QResizeEvent *event) { ui->photoView->fitInView(ui->photoView->sceneRect(), Qt::KeepAspectRatio); QWidget::resizeEvent(event); }
void MainClientForm::onSigImageCaptured(const QPoint &pos, cv::Mat image) { _impl->_photoItem->setPixmap(ImageTool::MatToQPixmap(image) ); }
|
我们重载了resizeEvent()
,在里面更新了View的大小。这大概是最简单的让显示图像自动缩放适应窗口大小的做法。
接下来实现用户交互。
实现onSigChooseOptions()
函数
我们在前面开发ScanTask::verifyOptions()
的时候介绍过,通过发送信号sigChooseOptions
,通知界面显示参数确认对话框:
1 2 3 4 5 6 7 8
| QVariantHash params{ ... }; bool ok = true; emit sigChooseOptions(params, ok); if(ok) { ...
|
在ScannerMainWindow::onActSingleScan()
中,使用Qt::BlockingQueuedConnection
方式将sigChooseOptions
和ScannerMainWindow::onSigChooseOptions
关联。
我们先创建对话框,并指定类名为SingleScanOptionDialog
,我们使用它来显示单张扫描时的扫描参数。

对话框的实现本身没有什么值得说的,唯一要注意的是实现的时候要注意和ScanTask
相对照,以避免QVariantMap
出现key的错误。
ScannerMainWindow::onSigChooseOptions()
的实现则很简单:显示对话框,并将结果保存在options
和ok
中返回给调用者:
1 2 3 4 5 6 7 8 9 10 11 12 13
| void ScannerMainWindow::onSigChooseOptions(QVariantHash &options, bool &ok) { SingleOptionDialog dlg; dlg.setParam(options); if(dlg.exec() != QDialog::Accepted) { ok = false; return ; } ok = true; options = dlg.params(); return; }
|