集成ScanTask到界面中

我们前面花了好几天来实现后台的处理,却一直看不到程序的界面,接下来我们尝试实现界面相关的功能。首先我们实现单张扫描功能。

我们增加一个slot函数onActSingleScan(),并在ScannerMainWindow的构造函数中将其与QAction actSingleScan关联起来。然后我们给出第一个实现版本:

1
2
3
4
5
void ScannerMainWindow::onActSingleScan()
{
ScanTask task(_impl->_configer, _impl->_scanner);
task.doScan({});
}

运行程序,我们能够从调试窗口看到了程序运行的调试输出信息,说明程序在运行,但是界面一动不动,被阻塞住了。我们需要把它挪到独立的线程中运行。最简单的做法还是和在连接设备中做的一样,使用QtConcurrent::run()启动线程运行。

一种新手很容易犯的错误写法是:

1
2
3
4
5
6
void ScannerMainWindow::onActSingleScan()
{
ScanTask task(_impl->_configer, _impl->_scanner);
QtConcurrent::run(&ScanTask::doScan, &task, QVariantHash());
...
}

除非doScan()启动后不访问ScanTask的任何属性或方法(那样直接就定义成static函数就得了),否则一运行就会崩溃。因为onActSingleScan()启动并运行线程后它自己就立即退出了,从而导致task也被析构了,而在运行的线程再访问task就会导致错误。

我们也可以用各种手段来延长task的生命周期,但是最简单的做法是下面:

1
2
3
4
5
6
7
void ScannerMainWindow::onActSingleScan()
{
QtConcurrent::run([this](){
ScanTask task(_impl->_configer, _impl->_scanner);
task.doScan({});
});
}

现在,我们有一些需求:

  • 当开始扫描后,要禁止掉两个扫描按钮,同时使能停止扫描的按钮
  • 当高倍扫描结束后,要是能扫描按钮,允许马上开始下一次扫描。同时,当前的扫描任务在后台运行直到结束

我们在ScanTask中定义了一些signal,其中有几个和GUI界面通信使用的,我们重新审视一下它们的作用:

  • sigPreviewed():用于传递拍摄得到的预览图和标签图,以及标签文本,会在左边的控制视图中显示预览图和二维码的内容。
  • sigLowScanRanged():传递低倍扫描区域在预览图上面的范围。会在控制视图中的预览图上画出这个范围
  • sigScanStart():用于传递低倍/高倍要扫描的行列数,会在控制视图中的进度图中设置窗格的行列数
  • sigImageCaptured():传递拍摄得到的一张照片,在控制视图中会填充一个网格,在主视图中会显示这张照片
  • sigDeviceFree():释放扫描仪设备。会使能扫描按钮
  • sigTaskCompleted():表示扫描全部结束,会在消息窗口中输出信息(目前不做别的)
  • sigChooseOptions():通知用户确认扫描参数
  • sigChooseLowScanRange():通知用户确认低倍扫描范围

其中,前面几个都是单向消息,而最后两个是双向通信。需要在GUI中关联和处理这些信号。

其中,sigPreviewed()sigLowScanRanged()sigScanStart()ControlPanelForm来处理,而sigScanStart()sigImageCaptured()MainClientForm处理,后面几条信号则由ScannerMainWindow处理。我们分别在对应的类中创建相应的slot函数,并在onActSingleScan()中建立关联关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void ScannerMainWindow::onActSingleScan()
{
auto future = QtConcurrent::run([this](){
ScanTask task(_impl->_configer, _impl->_scanner);

connect(&task, &ScanTask::sigPreviewed, ui->leftPanel, &ControlPanelForm::onSigPreviewCaptured);
connect(&task, &ScanTask::sigLowScanRanged, ui->leftPanel, &ControlPanelForm::onSigLowScanRanged);
connect(&task, &ScanTask::sigScanStart, ui->leftPanel, &ControlPanelForm::onSigScanStart);
connect(&task, &ScanTask::sigScanStart, ui->centerClient, &MainClientForm::onSigScanStart);
connect(&task, &ScanTask::sigImageCaptured, ui->leftPanel, &ControlPanelForm::onSigImageCaptured);
connect(&task, &ScanTask::sigImageCaptured, ui->centerClient, &MainClientForm::onSigImageCaptured);
connect(&task, &ScanTask::sigChooseOptions, this, &ScannerMainWindow::onSigChooseOptions, Qt::BlockingQueuedConnection);
connect(&task, &ScanTask::sigChooseLowScanRange, this, &ScannerMainWindow::onSigChooseLowScanRange, Qt::BlockingQueuedConnection);
connect(&task, &ScanTask::sigDeviceFree, this, &ScannerMainWindow::onSigDeviceFree);
connect(&task, &ScanTask::sigTaskCompleted, this, &ScannerMainWindow::onSigTaskCompleted);

task.doScan({});
});
}

注意上面代码中对sigChooseOptionssigChooseLowScanRangeconnect中,使用了第五个参数,Qt::BlockingQueuedConnection

现在运行程序,可以看到,后台的运行不再阻塞界面的更新了。接下来我们会简单地实现一下界面。