This document outlines the standards and best practices for developing desktop and mobile applications using the Qt framework at Bayat.
- Introduction
- Project Structure
- Development Environment
- Architecture
- C++ Conventions
- QML Best Practices
- Performance Optimization
- Internationalization
- Testing
- Deployment
- Resources
Qt is a powerful cross-platform framework for developing applications that can run on desktop (Windows, macOS, Linux), mobile (Android, iOS), embedded systems, and more. This guide focuses on desktop application development with Qt.
Qt is the preferred choice when:
- You need high-performance native applications
- Deep system integration is required
- Cross-platform deployment is necessary
- You need custom UI controls or visualizations
- Real-time data processing or hardware integration is needed
- Qt Widgets: Traditional desktop UI framework
- Qt Quick/QML: Modern declarative UI framework with JavaScript integration
- Qt Core: Non-UI functionality (file I/O, networking, threading)
- Qt Network: Network programming capabilities
- Qt SQL: Database integration
- Qt Test: Unit testing framework
project-root/
├── .github/ # GitHub workflows
├── .vscode/ # VS Code configuration (optional)
├── assets/ # Application assets
│ ├── icons/ # Application icons
│ ├── images/ # Image resources
│ └── fonts/ # Custom fonts
├── docs/ # Documentation
├── src/ # Source code
│ ├── core/ # Core business logic
│ │ ├── models/ # Data models
│ │ ├── services/ # Business services
│ │ └── utils/ # Utility functions
│ ├── ui/ # UI code
│ │ ├── views/ # QML or Widget UI components
│ │ ├── delegates/ # QML delegates
│ │ └── styles/ # Style definitions
│ ├── main.cpp # Application entry point
│ └── application.h/cpp # Main application class
├── resources/ # Resource files (.qrc)
│ ├── qml.qrc # QML resources
│ └── assets.qrc # Asset resources
├── tests/ # Test files
│ ├── unit/ # Unit tests
│ ├── integration/ # Integration tests
│ └── auto/ # Automated UI tests
├── translations/ # Translation files (.ts)
├── CMakeLists.txt # CMake build configuration
├── .gitignore # Git ignore file
└── README.md # Project documentation
- main.cpp: Application entry point
- CMakeLists.txt: Build configuration
- qml.qrc: QML resource file
- application.h/cpp: Main application class
- Use Qt Resource System (.qrc files) for embedding assets
- Structure resource files logically:
<RCC>
<qresource prefix="/images">
<file>assets/images/logo.png</file>
</qresource>
<qresource prefix="/fonts">
<file>assets/fonts/CustomFont.ttf</file>
</qresource>
<qresource prefix="/views">
<file>src/ui/views/MainView.qml</file>
</qresource>
</RCC>
- IDE: Qt Creator or Visual Studio with Qt extension
- Build System: CMake (preferred) or qmake
- Compiler:
- Windows: MSVC or MinGW
- macOS: Clang
- Linux: GCC or Clang
- Qt Version: Latest LTS release (currently Qt 6.5 LTS)
# Install Qt
# Download and run the Qt Online Installer from https://www.qt.io/download
# Create a new project
mkdir MyQtProject
cd MyQtProject
cmake -S . -B build -G "Ninja" -DCMAKE_PREFIX_PATH=/path/to/Qt/6.5.0/gcc_64
# Build the project
cmake --build build
Choose the appropriate UI technology based on project requirements:
-
Qt Quick (QML): Recommended for modern applications
- Fluid animations and modern UIs
- Rapid development
- Touch-friendly interfaces
- Dynamic UI with JavaScript logic
-
Qt Widgets: Better for traditional desktop applications
- Complex forms and data entry
- Windows/macOS/Linux native look and feel
- Legacy application maintenance
- Accessibility requirements
For Qt Widgets applications:
// Model
class TaskModel : public QAbstractListModel {
// Model implementation
};
// View
class TaskView : public QListView {
// View implementation
};
// Controller
class TaskController : public QObject {
// Controller implementation
};
For Qt Quick/QML applications:
// Model
class TaskModel : public QAbstractListModel {
// Model implementation
};
// ViewModel
class TaskViewModel : public QObject {
Q_OBJECT
Q_PROPERTY(TaskModel* tasks READ tasks CONSTANT)
Q_PROPERTY(bool hasActiveTasks READ hasActiveTasks NOTIFY hasActiveTasksChanged)
public:
TaskModel* tasks() const;
bool hasActiveTasks() const;
signals:
void hasActiveTasksChanged();
public slots:
void addTask(const QString& description);
void removeTask(int index);
};
// View (QML)
ListView {
model: viewModel.tasks
delegate: TaskDelegate {
description: model.description
onRemoveClicked: viewModel.removeTask(index)
}
}
Follow the standard \1\2) with these Qt-specific additions:
-
Classes: PascalCase, prefixed with a project-specific identifier for public APIs
class BAProjectNameWidget : public QWidget { ... };
-
Methods: camelCase
void processData(const QByteArray& data);
-
Signals/Slots: camelCase, with specific verbs
// Signals use past tense for events that have occurred signals: void dataProcessed(const QByteArray& result); void errorOccurred(const QString& message); // Slots use imperative for actions public slots: void processData(); void handleError(const QString& message);
-
Q_PROPERTY: camelCase
Q_PROPERTY(QString userName READ userName WRITE setUserName NOTIFY userNameChanged)
- Use Qt's parent-child mechanism for memory management
- Prefer smart pointers for objects without parents
- Be careful with ownership when connecting signals and slots
// Parent-child relationship
QVBoxLayout* layout = new QVBoxLayout(this); // 'this' is the parent
layout->addWidget(new QPushButton("OK", this)); // Parent takes ownership
// Smart pointers for objects without parents
std::unique_ptr<DataProcessor> processor = std::make_unique<DataProcessor>();
- Use the new connection syntax
- Use lambdas for simple slot implementations
- Connect signals and slots in the constructor or init method
// Modern connection syntax
connect(button, &QPushButton::clicked, this, &MyWidget::handleButtonClick);
// Lambda for simple slots
connect(button, &QPushButton::clicked, [this]() {
statusLabel->setText("Button clicked");
});
// Connections with parameters
connect(networkManager, &QNetworkAccessManager::finished,
this, &MyClass::handleNetworkReply);
- Create reusable components
- Use Qt Quick Controls 2 for standard UI elements
- Keep component files small and focused
// CustomButton.qml
Button {
id: root
property color accentColor: "#0066cc"
background: Rectangle {
color: root.down ? Qt.darker(root.accentColor, 1.2) : root.accentColor
radius: 4
}
}
- Use contexts for simple property exposure
- Use registerType for exposing C++ types to QML
- Create dedicated interface classes for complex logic
// In C++
// Setting context properties
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("dataModel", &dataModel);
// Registering types
qmlRegisterType<UserModel>("io.bayat.models", 1, 0, "UserModel");
// In QML
import io.bayat.models 1.0
UserModel {
id: userModel
// Use the registered C++ type
}
- Limit use of JavaScript for performance-critical code
- Avoid binding loops
- Use Component.onCompleted sparingly
- Prefer Loaders for dynamic content
// Efficient use of Loader
Loader {
id: detailLoader
active: tabBar.currentIndex === 1
sourceComponent: DetailView { model: detailModel }
}
- Use the Qt Profiler to identify bottlenecks
- Optimize layout operations (avoid frequent layout recalculations)
- Use image caching and scaling appropriately
- Implement model/view optimizations for large data sets
- Use Item Layers for complex, static components
- Implement incremental rendering for large views
- Use ShaderEffect for custom rendering
- Cache complex components with Loader
// Use layers for caching complex static elements
Rectangle {
id: complexItem
layer.enabled: true
layer.smooth: true
// Complex content
}
- Implement custom models for large datasets
- Use Qt's concurrent framework for background processing
- Optimize painting operations in custom widgets
- Use implicit sharing features
// Concurrent processing
#include <QtConcurrent>
void MyClass::processData(const QByteArray& data) {
QFuture<QByteArray> future = QtConcurrent::run([data]() {
return processDataInBackground(data);
});
QFutureWatcher<QByteArray>* watcher = new QFutureWatcher<QByteArray>(this);
connect(watcher, &QFutureWatcher<QByteArray>::finished, this, [this, watcher]() {
QByteArray result = watcher->result();
emit dataProcessed(result);
watcher->deleteLater();
});
watcher->setFuture(future);
}
- Use tr() and qsTr() for all user-visible strings
- Organize translation files (.ts) in the translations directory
- Implement locale switching at runtime
// C++ string translation
void MyWidget::setupUi() {
label->setText(tr("Welcome to the application"));
submitButton->setText(tr("Submit"));
}
// QML string translation
Text {
text: qsTr("Welcome to the application")
}
-
Update translation sources:
lupdate -recursive ./ -ts translations/app_en.ts translations/app_fr.ts
-
Translate strings using Qt Linguist
-
Release translations:
lrelease translations/app_*.ts
-
Load translations at runtime:
QTranslator translator; if (translator.load(":/translations/app_" + locale + ".qm")) { QApplication::installTranslator(&translator); }
Use Qt Test framework for unit testing:
// testcalculator.cpp
#include <QtTest>
#include "../src/calculator.h"
class TestCalculator : public QObject {
Q_OBJECT
private slots:
void testAddition() {
Calculator calc;
QCOMPARE(calc.add(2, 3), 5);
}
void testSubtraction() {
Calculator calc;
QCOMPARE(calc.subtract(5, 3), 2);
}
};
QTEST_MAIN(TestCalculator)
#include "testcalculator.moc"
Add to CMake:
add_executable(testcalculator testcalculator.cpp)
target_link_libraries(testcalculator PRIVATE Qt6::Test app_core)
add_test(NAME testcalculator COMMAND testcalculator)
Use Qt Test or Squish for UI testing:
// UI test with Qt Test
class TestMainWindow : public QObject {
Q_OBJECT
private slots:
void testUiInteraction() {
MainWindow window;
window.show();
QTest::mouseClick(window.findChild<QPushButton*>("submitButton"), Qt::LeftButton);
QCOMPARE(window.findChild<QLabel*>("statusLabel")->text(), "Submitted");
}
};
Use CMake for build configuration:
cmake_minimum_required(VERSION 3.16)
project(MyQtApp VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Quick)
qt_add_executable(MyQtApp
src/main.cpp
src/application.cpp
src/core/models/datamodel.cpp
)
target_link_libraries(MyQtApp PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Widgets
Qt6::Quick
)
qt_add_qml_module(MyQtApp
URI MyQtApp
VERSION 1.0
QML_FILES
src/ui/views/MainView.qml
src/ui/views/DetailView.qml
)
qt_add_resources(MyQtApp "assets"
PREFIX "/"
FILES
assets/images/logo.png
assets/fonts/CustomFont.ttf
)
- Use Qt's deployment tools (windeployqt, macdeployqt, etc.)
- Create platform-specific installers with appropriate packaging tools
- Sign applications for distribution
# Windows deployment
windeployqt --qmldir src/ui/views build/release/MyQtApp.exe
# macOS deployment
macdeployqt build/MyQtApp.app -qmldir=src/ui/views -dmg
# Linux AppImage
linuxdeployqt build/MyQtApp -qmldir=src/ui/views -appimage