DEV Community

Xiao Ling
Xiao Ling

Posted on • Originally published at dynamsoft.com

How to Build a Qt Barcode Scanner with MSVC and Dynamsoft C++ Barcode SDK on Windows

Qt is a cross-platform C++ framework for developing applications with graphical user interfaces (GUIs). In this tutorial, you'll learn how to create a robust, production-ready barcode scanner application using Qt 6, Microsoft Visual C++ (MSVC), and the Dynamsoft Barcode Reader C++ SDK on Windows. This tutorial covers everything from environment setup to implementing advanced features like real-time camera scanning and visual barcode overlays.

Demo: Qt Barcode Scanner for Windows

Prerequisites

What You'll Build

By the end of this tutorial, you'll create a barcode scanner application with these features:

  • Dual-mode scanning: Image file scanning and real-time camera scanning
  • Visual feedback: Live barcode detection overlays with bounding boxes
  • Multiple format support: Support for 1D and 2D barcodes (QR codes, Code 128, DataMatrix, etc.)
  • Drag-and-drop functionality: Easy image loading via drag-and-drop

Qt barcode scanner for Windows

Step 1: Environment Setup

1.1 Install Qt 6 with MSVC

Download and install Qt 6 from the official Qt installer:

  1. Run the Qt Online Installer.
  2. Select Qt 6.7.2 or later.
  3. Choose MSVC 2019/2022 64-bit component.
  4. Install to C:\Qt\6.7.2\msvc2022_64

    # Set Qt environment variable set Qt6_DIR=C:\Qt\6.7.2\msvc2022_64\lib\cmake\Qt6 

1.2 Install OpenCV

Download OpenCV 4.8.0 from opencv.org:

  1. Download the Windows release
  2. Extract to C:\opencv
  3. Set environment variable:

    set OpenCV_DIR=C:\opencv\build 

Step 2: Project Structure and CMake Configuration

2.1 Create Project Directory

mkdir qt-barcode-scanner cd qt-barcode-scanner 
Enter fullscreen mode Exit fullscreen mode

2.2 CMakeLists.txt Configuration

Create a CMakeLists.txt file that handles all dependencies:

cmake_minimum_required(VERSION 3.16) project(QtBarcodeScanner VERSION 1.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Find Qt6 components find_package(Qt6 REQUIRED COMPONENTS Core Widgets Multimedia MultimediaWidgets) # Enable Qt MOC, UIC, and RCC set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) # OpenCV for camera support - always enabled find_package(OpenCV REQUIRED) if(OpenCV_FOUND) message(STATUS "OpenCV found: ${OpenCV_VERSION}") endif() # Source files set(SOURCES main.cpp mainwindow.cpp mainwindow.h mainwindow.ui barcodeworker.cpp barcodeworker.h opencvcamera.cpp opencvcamera.h ) # Create executable add_executable(QtBarcodeScanner ${SOURCES}) # Link Qt libraries target_link_libraries(QtBarcodeScanner Qt6::Core Qt6::Widgets Qt6::Multimedia Qt6::MultimediaWidgets ) # Link OpenCV if(OpenCV_FOUND) target_link_libraries(QtBarcodeScanner ${OpenCV_LIBS}) target_include_directories(QtBarcodeScanner PRIVATE ${OpenCV_INCLUDE_DIRS}) endif() # Dynamsoft SDK configuration set(DCV_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../dcv") target_include_directories(QtBarcodeScanner PRIVATE "${DCV_ROOT}/include") if(WIN32) target_link_libraries(QtBarcodeScanner "${DCV_ROOT}/lib/win/DynamsoftCaptureVisionRouter.lib" "${DCV_ROOT}/lib/win/DynamsoftCore.lib" "${DCV_ROOT}/lib/win/DynamsoftUtility.lib" "${DCV_ROOT}/lib/win/DynamsoftLicense.lib" ) endif() # Post-build DLL and resource copying if(WIN32) # Copy Qt platform plugins add_custom_command(TARGET QtBarcodeScanner POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:QtBarcodeScanner>/platforms" COMMAND ${CMAKE_COMMAND} -E copy_directory "${Qt6_DIR}/../../../plugins/platforms" "$<TARGET_FILE_DIR:QtBarcodeScanner>/platforms" ) # Copy complete DLL directory add_custom_command(TARGET QtBarcodeScanner POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${DCV_ROOT}/lib/win" "$<TARGET_FILE_DIR:QtBarcodeScanner>" ) endif() 
Enter fullscreen mode Exit fullscreen mode

Key Configuration Points:

  • Automatic MOC/UIC/RCC: Essential for Qt meta-object compilation
  • Complete DLL copying: Ensures all Dynamsoft libraries are available
  • Plugin directory copying: Required for Qt platform plugins
  • Conditional compilation: Enables camera features based on available components

Step 3: Core Application Structure

3.1 Main Window Header (mainwindow.h)

#ifndef MAINWINDOW_H #define MAINWINDOW_H  #include <QtWidgets/QMainWindow> #include <QtWidgets/QLabel> #include <QtCore/QThread> #include <QtCore/QTimer> #include <QtGui/QDragEnterEvent> #include <QtGui/QDropEvent> #include <QtGui/QPainter>  #include <QtMultimedia/QCamera> #include <QtMultimedia/QMediaCaptureSession> #include <QtMultimedia/QMediaDevices> #include <QtMultimediaWidgets/QVideoWidget>  #include "opencvcamera.h" #include <QtWidgets/QLabel>  #include "barcodeworker.h"  QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); protected: void dragEnterEvent(QDragEnterEvent *event) override; void dropEvent(QDropEvent *event) override; void resizeEvent(QResizeEvent *event) override; void changeEvent(QEvent *event) override; private slots: void openImageFile(); void startCamera(); void stopCamera(); void clearResults(); void about(); void setLicense(); void loadTemplate(); void onBarcodeResults(const QList<BarcodeResult> &results); void onImageTabSelected(); void onCameraTabSelected(); void onLicenseStatusChanged(const QString &status, bool isValid); private: void setupConnections(); void loadImageFile(const QString &filePath); void updateImageDisplay(const QPixmap &pixmap, const QList<BarcodeResult> &results = QList<BarcodeResult>()); void updateResultsDisplay(const QList<BarcodeResult> &results); void updateCameraDisplay(); void updateLicenseStatus(const QString &status, bool isValid); bool tryStartOpenCVCamera(); Ui::MainWindow *ui; std::unique_ptr<QCamera> camera; std::unique_ptr<QMediaCaptureSession> captureSession; std::unique_ptr<QVideoWidget> videoWidget; OpenCVCamera *openCVCamera; QLabel *cameraLabel; bool useOpenCVCamera; QTimer *resizeTimer; bool cameraUpdatesPaused; #endif  QThread *workerThread; BarcodeWorker *barcodeWorker; QPixmap currentPixmap; QList<BarcodeResult> currentImageResults; QPixmap currentCameraFrame; QList<BarcodeResult> currentCameraResults; QString licenseKey; QString templateContent; QString lastImageDirectory; QLabel *licenseStatusLabel; }; #endif // MAINWINDOW_H 
Enter fullscreen mode Exit fullscreen mode

3.2 Barcode Worker Implementation

The BarcodeWorker class handles barcode detection in a separate thread to prevent UI blocking:

// barcodeworker.h #ifndef BARCODEWORKER_H #define BARCODEWORKER_H  #include <QtCore/QObject> #include <QtCore/QList> #include <QtCore/QPoint> #include <QtGui/QImage>  #include "DynamsoftCaptureVisionRouter.h" #include "DynamsoftUtility.h"  using namespace dynamsoft::license; using namespace dynamsoft::cvr; using namespace dynamsoft::dbr; using namespace dynamsoft::utility; using namespace dynamsoft::basic_structures; struct BarcodeResult { QString format; QString text; QList<QPoint> points; }; class BarcodeWorker : public QObject, public CCapturedResultReceiver { Q_OBJECT public: explicit BarcodeWorker(QObject *parent = nullptr); ~BarcodeWorker(); virtual void OnDecodedBarcodesReceived(CDecodedBarcodesResult *pResult) override; public slots: void initialize(); void processImage(const QImage &image); void processFrame(const QImage &frame); void setLicense(const QString &license); signals: void resultsReady(const QList<BarcodeResult> &results); void licenseStatusChanged(const QString &status, bool isValid); private: QList<BarcodeResult> convertResults(CDecodedBarcodesResult *pResult); CCaptureVisionRouter *m_router; QString m_licenseKey; bool m_initialized; }; #endif // BARCODEWORKER_H 
Enter fullscreen mode Exit fullscreen mode

Key Implementation Details:

  • Thread Safety: All barcode processing happens in a worker thread
  • Dynamsoft Integration: Implements CCapturedResultReceiver for result callbacks
  • Signal-Slot Communication: Uses Qt signals for thread-safe communication
  • License Management: Handles Dynamsoft license initialization and validation

Step 4: Implementing the Barcode Worker

4.1 Worker Initialization and License Setup

// barcodeworker.cpp void BarcodeWorker::initialize() { try { char errorMsgBuffer[512]; int ret = CLicenseManager::InitLicense(m_licenseKey.toUtf8().constData(), errorMsgBuffer, 512); if (ret != EC_OK) { QString errorMsg = QString::fromUtf8(errorMsgBuffer); emit licenseStatusChanged(QString("License: Failed (%1)").arg(errorMsg), false); } else { emit licenseStatusChanged("License: Valid", true); } m_router = new CCaptureVisionRouter(); if (!m_router) { return; } m_router->AddResultReceiver(this); m_initialized = true; } catch (const std::exception &e) { m_initialized = false; } } 
Enter fullscreen mode Exit fullscreen mode

4.2 Image Processing Implementation

void BarcodeWorker::processImage(const QImage &image) { if (!m_initialized || !m_router || image.isNull()) { if (!m_initialized) { initialize(); if (!m_initialized) { emit resultsReady(QList<BarcodeResult>()); return; } } if (image.isNull()) { emit resultsReady(QList<BarcodeResult>()); return; } } try { // Convert QImage to Dynamsoft-compatible format QImage rgbImage = image.convertToFormat(QImage::Format_RGB888); CImageData imageData( rgbImage.sizeInBytes(), rgbImage.bits(), rgbImage.width(), rgbImage.height(), rgbImage.bytesPerLine(), IPF_RGB_888 ); CCapturedResult *result = m_router->Capture(&imageData, CPresetTemplate::PT_READ_BARCODES); if (result) { if (result->GetErrorCode() != EC_OK) { emit resultsReady(QList<BarcodeResult>()); } else { CDecodedBarcodesResult *barcodeResult = result->GetDecodedBarcodesResult(); if (barcodeResult) { QList<BarcodeResult> results = convertResults(barcodeResult); emit resultsReady(results); } else { emit resultsReady(QList<BarcodeResult>()); } } result->Release(); } else { emit resultsReady(QList<BarcodeResult>()); } } catch (const std::exception &e) { emit resultsReady(QList<BarcodeResult>()); } } 
Enter fullscreen mode Exit fullscreen mode

Critical Points in Image Processing:

  • Format Conversion: Dynamsoft requires RGB888 format for optimal performance
  • Preset Templates: Using PT_READ_BARCODES for optimal detection settings

4.3 Result Conversion and Coordinate Extraction

QList<BarcodeResult> BarcodeWorker::convertResults(CDecodedBarcodesResult *pResult) { QList<BarcodeResult> results; if (!pResult || pResult->GetErrorCode() != EC_OK) { return results; } int count = pResult->GetItemsCount(); for (int i = 0; i < count; i++) { const CBarcodeResultItem *barcodeItem = pResult->GetItem(i); if (!barcodeItem) continue; BarcodeResult result; result.format = QString::fromUtf8(barcodeItem->GetFormatString()); result.text = QString::fromUtf8(barcodeItem->GetText()); CQuadrilateral location = barcodeItem->GetLocation(); CPoint points[4]; location.points[0] = points[0]; location.points[1] = points[1]; location.points[2] = points[2]; location.points[3] = points[3]; for (int j = 0; j < 4; j++) { result.points.append(QPoint(points[j].coordinate[0], points[j].coordinate[1])); } results.append(result); } return results; } 
Enter fullscreen mode Exit fullscreen mode

Step 5: Implementing Visual Overlay System

5.1 Image Display with Barcode Overlay

void MainWindow::updateImageDisplay(const QPixmap &pixmap, const QList<BarcodeResult> &results) { QPixmap displayPixmap = pixmap; if (!results.isEmpty()) { QPainter painter(&displayPixmap); painter.setPen(QPen(Qt::green, 3)); painter.setFont(QFont("Arial", 12, QFont::Bold)); for (const auto &result : results) { if (result.points.size() >= 4) { // Draw bounding polygon QPolygon polygon; for (const auto &point : result.points) { polygon << point; } painter.drawPolygon(polygon); if (!result.points.isEmpty()) { int textWidth = painter.fontMetrics().horizontalAdvance(result.text); QRect textRect(result.points[0].x(), result.points[0].y() - 20, textWidth + 10, 20); painter.fillRect(textRect, QColor(0, 255, 0, 180)); painter.setPen(Qt::black); painter.drawText(result.points[0].x() + 5, result.points[0].y() - 5, result.text); painter.setPen(QPen(Qt::green, 3)); } } } } ui->imageLabel->setPixmap(displayPixmap.scaled(ui->imageLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } 
Enter fullscreen mode Exit fullscreen mode

5.2 Real-time Camera Overlay

connect(openCVCamera, QOverload<const QPixmap &>::of(&OpenCVCamera::frameReady), this, [this](const QPixmap &pixmap) { if (useOpenCVCamera && cameraLabel && cameraLabel->isVisible() && !cameraUpdatesPaused) { currentCameraFrame = pixmap; QSize labelSize = cameraLabel->size(); if (labelSize.width() > 10 && labelSize.height() > 10) { if (!currentCameraResults.isEmpty()) { // Create overlay on current frame QPixmap overlayPixmap = pixmap; QPainter painter(&overlayPixmap); painter.setRenderHint(QPainter::Antialiasing); // Draw real-time barcode detection boxes for (const auto &result : currentCameraResults) { if (!result.points.isEmpty() && result.points.size() >= 4) { QPen pen(Qt::green, 3); painter.setPen(pen); // Draw detection polygon QPolygonF polygon; for (const auto &point : result.points) { polygon << point; } painter.drawPolygon(polygon); // Draw format label if (!result.format.isEmpty()) { painter.setPen(QPen(Qt::yellow, 2)); QFont font = painter.font(); font.setPointSize(12); font.setBold(true); painter.setFont(font); QPointF textPos = result.points.first(); textPos.setY(textPos.y() - 10); painter.drawText(textPos, result.format); } } } painter.end(); QPixmap scaledPixmap = overlayPixmap.scaled(labelSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); cameraLabel->setPixmap(scaledPixmap); } else { // No overlay needed QPixmap scaledPixmap = pixmap.scaled(labelSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); cameraLabel->setPixmap(scaledPixmap); } } // Process frame for barcode detection if (barcodeWorker) { QImage image = pixmap.toImage(); barcodeWorker->processImage(image); } } }); 
Enter fullscreen mode Exit fullscreen mode

Step 6: OpenCV Camera Implementation

The application uses OpenCV exclusively for camera access, providing better reliability and cross-platform compatibility than Qt6 Multimedia.

6.1 Camera Initialization and Setup

void MainWindow::startCamera() { if (openCVCamera && openCVCamera->start(0)) { // Show OpenCV camera display if (cameraLabel) { cameraLabel->show(); } ui->startCameraButton->setEnabled(false); ui->stopCameraButton->setEnabled(true); ui->resultsTextEdit->append("OpenCV camera started successfully!"); } else { QStringList availableCameras = openCVCamera ? openCVCamera->availableCameras() : QStringList(); if (availableCameras.isEmpty()) { ui->resultsTextEdit->append("No cameras detected by OpenCV"); } else { ui->resultsTextEdit->append(QString("Available cameras: %1, but failed to start") .arg(availableCameras.join(", "))); } } } 
Enter fullscreen mode Exit fullscreen mode

6.2 OpenCV Camera Class Implementation

// opencvcamera.h class OpenCVCamera : public QObject { Q_OBJECT public: explicit OpenCVCamera(QObject *parent = nullptr); ~OpenCVCamera(); bool start(int cameraIndex = 0); void stop(); bool isActive() const { return active; } bool isAvailable() const; QStringList availableCameras() const; signals: void frameReady(const QPixmap &frame); private slots: void captureFrame(); private: cv::VideoCapture capture; QTimer *captureTimer; bool active; int currentCameraIndex; }; 
Enter fullscreen mode Exit fullscreen mode

6.3 Real-time Frame Processing

void OpenCVCamera::captureFrame() { if (!active || !capture.isOpened()) return; cv::Mat frame; if (capture.read(frame) && !frame.empty()) { // Convert BGR to RGB for Qt cv::Mat rgbFrame; cv::cvtColor(frame, rgbFrame, cv::COLOR_BGR2RGB); // Convert to QImage then QPixmap QImage qImage(rgbFrame.data, rgbFrame.cols, rgbFrame.rows, rgbFrame.step, QImage::Format_RGB888); QPixmap pixmap = QPixmap::fromImage(qImage); emit frameReady(pixmap); } } 
Enter fullscreen mode Exit fullscreen mode

Step 7: Building and Deployment

Create build.bat for easy building:

@echo off echo Building Qt Barcode Scanner in Release mode... REM Check for Qt6_DIR environment variable if not defined Qt6_DIR ( echo Qt6_DIR environment variable not set. Searching for Qt installations... REM Search common Qt installation paths for %%d in ( "C:\Qt\6.7.2\msvc2022_64\lib\cmake\Qt6" "C:\Qt\6.7.2\msvc2019_64\lib\cmake\Qt6" "C:\Qt\6.6.0\msvc2022_64\lib\cmake\Qt6" ) do ( if exist %%d ( set "Qt6_DIR=%%~d" echo Found Qt at: %%~d goto :found_qt ) ) echo Qt6 installation not found. Please install Qt6 or set Qt6_DIR manually. pause exit /b 1 ) :found_qt echo Using Qt6_DIR: %Qt6_DIR% set "CMAKE_PREFIX_PATH=%Qt6_DIR%\..\..\.." echo Configuring CMake... if not exist build mkdir build cd build cmake .. -DQt6_DIR="%Qt6_DIR%" -DOpenCV_DIR="%OpenCV_DIR%" -G "Visual Studio 17 2022" if errorlevel 1 ( echo CMake configuration failed pause exit /b 1 ) echo Building project... cmake --build . --config Release if errorlevel 1 ( echo Build failed pause exit /b 1 ) echo Build completed successfully echo Executable location: %CD%\bin\QtBarcodeScanner.exe echo To run the application: echo cd %CD%\bin echo QtBarcodeScanner.exe pause 
Enter fullscreen mode Exit fullscreen mode

Source Code

https://github.com/yushulx/cmake-cpp-barcode-qrcode-mrz/tree/main/examples/qt_barcode_scanner

Top comments (0)