1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/feiyangqingyun-qtkaifajingyan

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

0 Введение

  1. Проект: qtchina.blog.csdn.net/article/details/97565652
  2. Видео на главной странице: space.bilibili.com/687803542
  3. Интернет-магазин: shop244026315.taobao.com
  4. Контакты: QQ (517216493), WeChat (feiyangqingyun). Рекомендуется добавить в WeChat.
  5. Публичный аккаунт: Qt практика / Qt для начинающих и продвинутых / Учебник по Qt / Программное обеспечение Qt
  6. Поддержка версий: все проекты полностью поддерживают версии Qt4/5/6, а также последующие версии.
  7. Мониторинг работы проекта: pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g Код извлечения: 01jf
  8. Другие впечатления от работы: pan.baidu.com/s/1ZxG-oyUKe286LPMPxOrO2A Код извлечения: o05q
  9. Онлайн-документ системы мониторинга: www.qtcdev.com/video_system/
  10. Онлайн-документ системы большого экрана: www.qtcdev.com/bigscreen/
  11. Онлайн-документ системы Интернета вещей: www.qtcdev.com/iotsystem/

1 Опыт разработки

01:001-010

  1. Когда при компиляции обнаруживается большое количество ошибок, нужно начинать с первой и решать их одну за другой, не переходя сразу к следующей ошибке. Часто последующие ошибки возникают из-за предыдущих. После исправления первой ошибки, скорее всего, остальные тоже будут решены. Например, возможно, вы допустили ошибку в одной строке кода, и компилятор выдал несколько сотен ошибок. Достаточно исправить эту строку, и остальные ошибки исчезнут.
  2. Таймеры — полезная вещь. Научившись правильно их использовать, можно решить множество неожиданных проблем. Например, при инициализации окна может потребоваться загрузить длительную операцию, что может привести к зависанию отображения главного окна до завершения загрузки. В этом случае можно отложить или выполнить загрузку асинхронно, чтобы она происходила после отображения интерфейса. Это позволит избежать зависания основного интерфейса.
// Асинхронное выполнение функции load
QMetaObject::invokeMethod(this, "load", Qt::QueuedConnection);
// Выполнение функции load через 10 миллисекунд
QTimer::singleShot(10, this, SLOT(load()));

// Использование таймера с лямбда-выражением
QTimer::singleShot(10, [&]() {
  load();
});

QTimer *timer = new QTimer(this);
timer->setSingleShot(true);
connect(timer, &QTimer::timeout, this, [timer, this] {

});
timer->start(5000);
  1. По умолчанию Qt Creator компилируется в однопоточном режиме, чтобы минимизировать использование системных ресурсов. Однако современные компьютеры имеют многоядерные процессоры, и msvc компилятор по умолчанию использует многопоточную компиляцию без необходимости ручной настройки. Для других компиляторов требуется ручная настройка.
  • Метод 1: в настройках сборки каждого проекта (можно выбрать страницу shadow build) в разделе build, добавьте строку -j16 в аргументы make. Эта настройка сохранится в файле pro.user. Если файл удалить, настройку придётся повторить, поэтому этот метод не рекомендуется.
  • Метод 2: в среде сборки комплекта инструментов перейдите в «Инструменты» -> «Опции» -> «Комплекты сборки» (kits), выберите нужный комплект сборки, в поле environment справа нажмите кнопку change и введите в открывшемся окне MAKEFLAGS=-j4. Теперь каждый проект, использующий этот комплект сборки, будет автоматически добавлять этот параметр компиляции.
  • Примечание: число после -j соответствует количеству ядер вашего компьютера. Указывать слишком большое число не имеет смысла, лучше узнать параметры своего компьютера или просто указать -j4, так как большинство компьютеров сейчас имеют 4 ядра.
  • Начиная примерно с 2019 года, новые версии Qt Creator автоматически настраивают многопоточную компиляцию в зависимости от количества ядер вашего компьютера, например, если у вас 16 ядер, компиляция будет выполняться с параметром -j16.
  • В версии Qt Creator 8 меню настроек было перемещено из раздела «Инструменты» в раздел «Редактировать». Многие пользователи могут быть не знакомы с этим изменением, но его легко найти, немного поискав.
  1. Если вы хотите использовать Qt Creator для развёртывания приложений на Android, сначала необходимо настроить Android Studio таким образом, чтобы скомпилированное приложение могло работать на телефоне или эмуляторе.

  2. После того как вы найдёте соответствующий метод в Qt, не забудьте изучить перегрузки этой функции. Вы можете обнаружить множество параметров, которые предоставляют различные возможности. Иногда это может привести к внезапному осознанию того, что Qt уже предоставляет вам готовые решения. Например, перегрузки функций QString и QColor имеют множество параметров, предоставляя вам множество функций, о которых вы могли только мечтать.

  3. В файле pro можно указать информацию о версии программы, иконке, названии продукта, авторских правах и описании файла (поддерживается только в Qt5). На Windows при использовании qmake эта информация автоматически преобразуется в rc файл. Для более ранних версий Qt4 можно создать rc файл вручную.

# Версия программы
VERSION  = 2025.10.01
# Иконка программы
RC_ICONS = main.ico
# Название продукта
QMAKE_TARGET_PRODUCT = quc
# Авторские права
QMAKE_TARGET_COPYRIGHT = feiyangqingyun
# Описание файла
QMAKE_TARGET_DESCRIPTION = QQ: 517216493  WX: feiyangqingyun
  1. Чтобы запустить программу от имени администратора в MSVC компиляторе, добавьте следующий код в файл pro проекта.
QMAKE_LFLAGS += /MANIFESTUAC:"level='requireAdministrator' uiAccess='false'" # Запуск от имени администратора
QMAKE_LFLAGS += /SUBSYSTEM:WINDOWS,"5.01" # Запуск в Windows XP в VS2013
  1. Чтобы запускать файлы с окном вывода отладки, это очень полезно. Часто при выпуске программы мы сталкиваемся с тем, что программа не запускается при двойном клике или не выдаёт ошибок (на компьютере разработчика всё работает нормально). Мы не знаем, что происходит, даже диспетчер задач показывает, что программа запущена, но окно не появляется. В таких случаях добавьте строку CONFIG += console в файл pro вашего проекта. Программы с графическим интерфейсом также будут автоматически открывать окно вывода отладки для печати информации, что облегчает поиск проблем. Обычно программы, которые не запускаются нормально, печатают подсказки о том, чего им не хватает.
TEMPLATE    = app
MOC_DIR     = temp/moc
RCC_DIR     = temp/rcc
UI_DIR      = temp/ui
OBJECTS_DIR = temp/obj
# Добавление строки для запуска файла с окном вывода отладки
CONFIG      += console
  1. Используйте QPainter::drawTiledPixmap для рисования плоского фона и QPainter::drawRoundedRect() для рисования прямоугольников с закруглёнными углами, вместо QPainter::drawRoundRect(). Эти две функции легко перепутать.

  2. Укажите удаление старого стиля управления.

// Удаление старого стиля
style()->unpolish(ui->btn);
// Обязательно выполните следующую строку, иначе стиль не удалится
ui->btn->setStyleSheet("");
// Установите новый стиль для элемента управления
style()->polish(ui->btn);

02:011-020 ```

widget->property(name); qDebug() << name << type << value; }

// Количество всех методов int methodCount = metaObject->methodCount(); // methodOffset — это начало пользовательских методов int methodOffset = metaObject->methodOffset(); // Цикл для получения пользовательских методов компонента, int i = 0 означает все методы for (int i = methodOffset; i < methodCount; ++i) { QMetaMethod metaMethod = metaObject->method(i); const char *name = metaMethod.name(); const char *type = metaMethod.typeName(); qDebug() << name << type; }


12. В Qt встроенные значки упакованы в QStyle, их около семидесяти, и их можно использовать напрямую.
```cpp
SP_TitleBarMenuButton,
SP_TitleBarMinButton,
SP_TitleBarMaxButton,
SP_TitleBarCloseButton,
SP_MessageBoxInformation,
SP_MessageBoxWarning,
SP_MessageBoxCritical,
SP_MessageBoxQuestion,
...
// Получение и использование следующим образом
QPixmap pixmap = this->style()->standardPixmap(QStyle::SP_TitleBarMenuButton);
ui->label->setPixmap(pixmap);
  1. Загрузка в зависимости от разрядности операционной системы
win32 {
    contains(DEFINES, WIN64) {
        DESTDIR = $$PWD/../bin64
    } else { 
        DESTDIR = $$PWD/../bin32
    }
}
  1. Qt5 усилил множество проверок безопасности, если появляется сообщение setGeometry: Unable to set geometry, переместите видимость этого компонента после добавления его в макет.

  2. Можно добавить компонент A в макет, затем установить этот макет для компонента B, что повышает гибкость комбинирования компонентов, например, можно добавить кнопку поиска слева или справа от текстового поля, установив для кнопки значок.

QPushButton *btn = new QPushButton;
btn->resize(30, ui->lineEdit->height());
QHBoxLayout *layout = new QHBoxLayout(ui->lineEdit);
layout->setMargin(0);
layout->addStretch();
layout->addWidget(btn);
  1. Чтобы установить стиль для QLCDNumber, необходимо установить segmentstyle в flat, иначе эффекта не будет.

  2. Умное использование findChildren позволяет найти все дочерние компоненты данного компонента. findChild используется для поиска одного.

// Поиск компонента с указанным именем класса objectName
QList<QWidget *> widgets = fatherWidget.findChildren<QWidget *>("widgetname");
// Поиск всех QPushButton
QList<QPushButton *> allPButtons = fatherWidget.findChildren<QPushButton *>();
// Поиск дочерних компонентов первого уровня, иначе будут просмотрены все дочерние элементы
QList<QPushButton *> childButtons = fatherWidget.findChildren<QPushButton *>(QString(), Qt::FindDirectChildrenOnly);
  1. Умное использование inherits позволяет определить, принадлежит ли компонент к определённому классу.
QTimer *timer = new QTimer;         // QTimer наследует QObject
timer->inherits("QTimer");          // возвращает true
timer->inherits("QObject");         // возвращает true
timer->inherits("QAbstractButton"); // возвращает false
  1. Используя механизм слабых свойств, можно хранить временные значения для передачи в суждениях. Можно перечислить все имена слабых свойств с помощью widget->dynamicPropertyNames(), а затем получить значение соответствующего слабого свойства с помощью widget->property("name").

  2. Во время разработки, будь то из соображений удобства обслуживания или экономии памяти, следует иметь один qss-файл для хранения всех таблиц стилей, а не использовать setStyleSheet повсеместно. Если вы находитесь на стадии обучения или тестирования, вы можете напрямую установить таблицу стилей в пользовательском интерфейсе, щёлкнув правой кнопкой мыши, но для официальных проектов рекомендуется объединить их в один файл таблицы стилей qss для единообразного управления.

03: 21–30

  1. Если появляется ошибка Z-order assignment: is not a valid widget, откройте соответствующий ui-файл в Блокноте и удалите пустые места в теге .

  2. Умение эффективно использовать второй параметр addItem в QComboBox для установки пользовательских данных может привести к множеству эффектов, которые можно извлечь с помощью itemData. Важно отметить, что вторым параметром является тип QVariant, поэтому он должен быть достаточно гибким, чтобы содержать универсальные данные, такие как структуры, позволяя передавать множество данных вместо одного. Например, в выпадающем списке для выбора номера студента можно связать имя студента, класс и оценки. Многие люди думают, что могут быть связаны только данные типа QString или int, потому что обычно используются именно эти два типа.

QStringList listVideoOpenInterval, listVideoOpenIntervalx;
listVideoOpenInterval << "0.0 秒" << "0.1 秒" << "0.3 秒" << "0.5 秒" << "1.0 秒" << "2.0 秒";
listVideoOpenIntervalx << "0" << "100" << "300" << "500" << "1000" << "2000";
for (int i = 0; i < listVideoOpenInterval.count(); ++i) {
    ui->cboxVideoOpenInterval->addItem(listVideoOpenInterval.at(i), listVideoOpenIntervalx.at(i));
}
// Извлечение соответствующего значения
int indexVideoOpenInterval = ui->cboxVideoOpenInterval->currentIndex();
indexVideoOpenInterval = ui->cboxVideoOpenInterval->itemData(indexVideoOpenInterval).toInt();
  1. При использовании модуля webengine необходимо включить QtWebEngineProcess.exe, папку translations и resources при публикации программы, иначе она не запустится должным образом.

  2. В программах MFC, VB/C# и других оконных приложениях каждый компонент имеет свой дескриптор, который автоматически распознаётся при перемещении с помощью инструментов дескриптора, но в приложениях Qt по умолчанию каждому окну соответствует один дескриптор. Чтобы каждый компонент имел отдельный дескриптор, выполните следующие настройки в функции main.

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    a.setAttribute(Qt::AA_NativeWindows);
}
  1. Написание Android-программы на Qt для предотвращения закрытия программы.
#if defined(Q_OS_ANDROID)
QAndroidService a(argc, argv);
return a.exec()
#else
QApplication a(argc, argv);
return a.exec();
#endif
  1. Можно установить стиль для всех индикаторов сразу, без необходимости устанавливать стиль для каждого отдельного индикатора компонента.
*::down-arrow{}
*::menu-indicator{}
*::up-arrow:disabled{}
*::up-arrow:off{}
  1. Можно задать расположение фонового изображения.
QMainWindow > .QWidget {
    background-color: gainsboro;
    background-image: url(:/images/xxoo.png);
    background-position: top right;
    background-repeat: no-repeat
}
  1. Запуск программы Qt на встроенном Linux
// Написание для Qt4
./HelloQt -qws &
``` ```
//Qt5写法 xcb можно заменить на linuxfb eglfs vnc wayland и т. д., какой есть, тот и использовать для поочерёдного тестирования
./HelloQt --platform xcb
./HelloQt --platform linuxfb
./HelloQt --platform wayland
  1. Если обнаружится, что комплект сборки в QtCreator работает неправильно (например, не может правильно распознать qmake или компилятор в среде, или при открытии проекта не получается создать каталог теневой сборки), необходимо найти два каталога (C:\Users\Administrator\AppData\Local\QtProject, C:\Users\Administrator\AppData\Roaming\QtProject) и удалить их. После удаления заново открыть QtCreator и настроить комплект сборки.

  2. QMediaPlayer — это оболочка (также называемая фреймворком), которая зависит от локального декодера. По умолчанию он в основном воспроизводит MP4, но даже MP4 не может быть воспроизведён. Для поддержки других форматов необходимо загрузить и установить k-lite или LAV Filters (k-lite или LAV Filters относятся к Windows, для других систем ищите самостоятельно; похоже, что во встраиваемом Linux используется GStreamer (sudo apt-get install gstreamer1.0-libav ubuntu-restricted-extras), но полной проверки не было, и при ошибке выводится сообщение «Your GStreamer installation is missing a plug-in», требуется командная установка sudo apt-get install ubuntu-restricted-extras). Если вам нужен мощный проигрыватель, новичкам рекомендуется использовать vlc или mpv, а для универсального решения — ffmpeg (видео после декодирования можно использовать с QOpenGLWidget для рендеринга на GPU или преобразовать в QImage для отрисовки, аудиоданные можно воспроизвести с помощью QAudioOutput).

04: 031–040

  1. Определение типа компилятора, версии компилятора и операционной системы.
//pro中判断编译器版本
greaterThan(MSC_VER, 1900) {
}

//GCC编译器
#ifdef __GNUC__
#if __GNUC__ >= 3   // GCC3.0  以上

//MSVC编译器
#ifdef _MSC_VER
#if _MSC_VER >=1000 // VC++4.0 以上
#if _MSC_VER >=1100 // VC++5.0 以上
#if _MSC_VER >=1200 // VC++6.0 以上
#if _MSC_VER >=1300 // VC2003  以上
#if _MSC_VER >=1400 // VC2005  以上
#if _MSC_VER >=1500 // VC2008  以上
#if _MSC_VER >=1600 // VC2010  以上
#if _MSC_VER >=1700 // VC2012  以上
#if _MSC_VER >=1800 // VC2013  以上
#if _MSC_VER >=1900 // VC2015  以上

//Visual Studio版本与MSVC版本号的对应关系
MSC    1.0   _MSC_VER == 100
MSC    2.0   _MSC_VER == 200
MSC    3.0   _MSC_VER == 300
MSC    4.0   _MSC_VER == 400
MSC    5.0   _MSC_VER == 500
MSC    6.0   _MSC_VER == 600
MSC    7.0   _MSC_VER == 700
MSVC++ 1.0   _MSC_VER == 800
MSVC++ 2.0   _MSC_VER == 900
MSVC++ 4.0   _MSC_VER == 1000 (Developer Studio 4.0)
MSVC++ 4.2   _MSC_VER == 1020 (Developer Studio 4.2)
MSVC++ 5.0   _MSC_VER == 1100 (Visual Studio 97 version 5.0)
MSVC++ 6.0   _MSC_VER == 1200 (Visual Studio 6.0 version 6.0)
MSVC++ 7.0   _MSC_VER == 1300 (Visual Studio .NET 2002 version 7.0)
MSVC++ 7.1   _MSC_VER == 1310 (Visual Studio .NET 2003 version 7.1)
MSVC++ 8.0   _MSC_VER == 1400 (Visual Studio 2005 version 8.0)
MSVC++ 9.0   _MSC_VER == 1500 (Visual Studio 2008 version 9.0)
MSVC++ 10.0  _MSC_VER == 1600 (Visual Studio 2010 version 10.0)
MSVC++ 11.0  _MSC_VER == 1700 (Visual Studio 2012 version 11.0)
MSVC++ 12.0  _MSC_VER == 1800 (Visual Studio 2013 version 12.0)
MSVC++ 14.0  _MSC_VER == 1900 (Visual Studio 2015 version 14.0)
MSVC++ 14.1  _MSC_VER == 1910 (Visual Studio 2017 version 15.0)
MSVC++ 14.11 _MSC_VER == 1911 (Visual Studio 2017 version 15.3)
MSVC++ 14.12 _MSC_VER == 1912 (Visual Studio 2017 version 15.5)
MSVC++ 14.13 _MSC_VER == 1913 (Visual Studio 2017 version 15.6)
MSVC++ 14.14 _MSC_VER == 1914 (Visual Studio 2017 version 15.7)
MSVC++ 14.15 _MSC_VER == 1915 (Visual Studio 2017 version 15.8)
MSVC++ 14.16 _MSC_VER == 1916 (Visual Studio 2017 version 15.9)
MSVC++ 14.2  _MSC_VER == 1920 (Visual Studio 2019 Version 16.0)
MSVC++ 14.21 _MSC_VER == 1921 (Visual Studio 2019 Version 16.1)
MSVC++ 14.22 _MSC_VER == 1922 (Visual Studio 2019 Version 16.2)
MSVC++ 14.30 _MSC_VER == 1930 (Visual Studio 2022 Version 17.0)
MSVC++ 14.31 _MSC_VER == 1931 (Visual Studio 2022 Version 17.1)
MSVC++ 14.32 _MSC_VER == 1932 (Visual Studio 2022 Version 17.2)

//Borland C++
#ifdef __BORLANDC__

//Cygwin
#ifdef __CYGWIN__
#ifdef __CYGWIN32__

//mingw
#ifdef __MINGW32__

//windows
#ifdef _WIN32           //32bit
#ifdef _WIN64           //64bit
#ifdef _WINDOWS         //图形界面程序
#ifdef _CONSOLE         //控制台程序

//Windows(95/98/Me/NT/2000/XP/Vista)和Windows CE都定义了
#if (WINVER >= 0x030a)  // Windows 3.1以上
#if (WINVER >= 0x0400)  // Windows 95/NT4.0以上
#if (WINVER >= 0x0410)  // Windows 98以上
#if (WINVER >= 0x0500)  // Windows Me/2000以上
#if (WINVER >= 0x0501)  // Windows XP以上
#if (WINVER >= 0x0600)  // Windows Vista以上

//_WIN32_WINNT 内核版本
#if (_WIN32_WINNT >= 0x0500) // Windows 2000以上
#if (_WIN32_WINNT >= 0x0501) // Windows XP以上
#if (_WIN32_WINNT >= 0x0600) // Windows Vista以上
  1. В pro файле определение версии Qt и разрядности комплекта сборки
#打印版本信息
message(qt version: $$QT_VERSION)
#判断当前qt版本号
QT_VERSION = $$[QT_VERSION]
QT_VERSION = $$split(QT_VERSION, ".")
QT_VER_MAJ =
``` ```
while (model->canFetchMore()) {
    model->fetchMore();
}
  1. Если нужно задать окно без рамки, но при этом сохранить свойства рамки операционной системы, например, возможность растягивать рамку, можно использовать setWindowFlags(Qt::CustomizeWindowHint), это сохранит системную рамку.

06: 051–060

  1. При отправке некоторых данных через HTTP POST, если данные соединены с помощью символа &, и происходит искажение китайских символов при разборе, необходимо выполнить URL-кодирование китайских символов.
QString content = "测试中文";
QString note = content.toUtf8().toPercentEncoding();
  1. Qt по умолчанию не поддерживает большие файлы ресурсов, например, при добавлении файла шрифта, это нужно включить в файле pro. CONFIG += resources_big

  2. После наследования QWidget в Qt таблица стилей не работает. Есть три способа решения этой проблемы. Настоятельно рекомендуется первый способ.

  • Метод один: установить свойство this->setAttribute(Qt::WA_StyledBackground, true);
  • Метод два: изменить наследование на QFrame, так как QFrame имеет встроенную функцию paintEvent, которая будет использоваться при применении таблицы стилей.
  • Метод три: при переопределении функции paintEvent класса QWidget использовать QStylePainter для рисования.
void Widget::paintEvent(QPaintEvent *)
{
    QStyleOption option;
    option.initFrom(this);
    QPainter painter(this);
    style()->drawPrimitive(QStyle::PE_Widget, &option, &painter, this);
}
  1. Иногда, когда на интерфейсе есть пружина, может потребоваться динамически изменить стратегию растяжения соответствующей пружины. Соответствующий метод — changeSize, многие люди будут искать его, начиная с set.

  2. При использовании QFile не рекомендуется часто открывать файл для записи, а затем закрывать его. Например, если вывод журнала происходит с интервалом в 5 мс, производительность ввода-вывода сильно снижается. В таких случаях рекомендуется оставить файл открытым до подходящего момента, например, закрыть файл в деструкторе или при изменении даты и необходимости перезаписи журнала. Иначе частые открытия и закрытия файлов могут привести к зависаниям, особенно с большими файлами.

  3. Во многих сетевых приложениях требуется настраивать пакеты сердцебиения для поддержания соединения. Иначе при отключении питания или некорректном завершении программы другая сторона может не сразу распознать это, или распознавание может занять много времени (обычно не менее 30 секунд). Необходимо реализовать тайм-аут обнаружения. Однако некоторые программы не предоставляют протокол сердцебиения, в этом случае нужно активировать систему поддержания активности на уровне сокетов. Этот метод подходит для TCP-соединений.

int fd = tcpSocket->socketDescriptor();
int keepAlive = 1;      // Открываем свойство keepalive, значение по умолчанию: 0 (закрыто)
int keepIdle = 5;       // Если в течение 5 секунд нет обмена данными, выполняется обнаружение, значение по умолчанию: 7200 (с)
int keepInterval = 2;   // Интервал времени для отправки пакетов обнаружения составляет 2 секунды, значение по умолчанию: 75 (с)
int keepCount = 2;      // Количество повторных попыток обнаружения, если все попытки неудачны, соединение считается потерянным, значение по умолчанию: 9 (раз)
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle));
setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
setsockopt(fd, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
  1. Обычно сообщение «Это приложение не запустилось, потому что оно не смогло найти или загрузить плагин платформы Qt» появляется после упаковки программы, причиной является то, что каталог плагина platforms не был упакован или был упакован неправильно.

  2. Не рекомендуется включать китайский язык в tr, хотя текущая новая версия Qt поддерживает перевод с китайского на другие языки, но это нестандартно и неизвестно, кто этому научил (позже я обнаружил, что когда я только начинал изучать Qt, я также опубликовал несколько демонстраций в Интернете, и в то время tr включал китайский язык). Идея tr заключается в том, чтобы содержать английский текст, а затем переводить его на другие языки, такие как китайский. Сейчас многие начинающие разработчики злоупотребляют tr. Если перевод не требуется, лучше отключить tr. tr требует затрат, Qt по умолчанию считает, что он нужен для перевода, и выполняет специальную обработку.

  3. Различия между Qt и Qt Creator

  • Многие люди путают Qt и Qt Creator и часто спрашивают, какую версию Qt использовать. Но они отправляют версию Qt Creator.
  • Qt Creator — это интегрированная среда разработки IDE, написанная с использованием Qt, аналогичная Visual Studio.
  • Он может быть скомпилирован с помощью компилятора msvc (встроенного в среду установки Qt для Windows), mingw или gcc.
  • Если вы создаёте собственный плагин управления, вам нужно интегрировать его в Qt Creator, убедившись, что файл динамической библиотеки (dll или so и т. д.) этого плагина соответствует компилятору, версии Qt, разрядности и версии Qt Creator. В противном случае интеграция вряд ли удастся.
  • Обратите особое внимание на то, что версия Qt в пакете установки интегрированной среды может отличаться от версии в Qt Creator. Вы должны внимательно прочитать. Некоторые из них полностью совпадают.
  • Из-за того, что новая версия Qt требует онлайн-установки, и в онлайн-установщике нельзя выбрать версию Qt Creator, новая версия Qt Creator скомпилирована с использованием Qt6, поэтому возникает проблема несовместимости с системой win7.
  • Рекомендуется использовать систему win10 или win11 для разработки.
  • Вы можете разрабатывать в более новой версии Qt Creator, выбрав версию комплекта, поддерживающую win7, например 5.15, или выбрав версию комплекта, поддерживающую xp, 5.6, и после выпуска ваше приложение всё равно будет работать на старых системах.
  1. Если есть более двух мест с одинаковым кодом обработки, рекомендуется написать их в виде отдельной функции. Код должен быть стандартизирован и упрощён, например, if (a == 123) следует записать как if (123 == a), где значение идёт первым. Также, if (ok == true) должно быть записано как if (ok), а if (ok == false) как if (!ok) и так далее.

07: 061–070

  1. На вопрос о том, какая встраиваемая платформа Qt лучше, есть общий ответ (на момент 2018 года): imx6+335x более стабильны, с высокой производительностью используйте RK3288 RK3399, если хотите сэкономить, возьмите Allwinner H3, для развлечения можно попробовать Raspberry Pi или Orange Pi.

  2. Для длинных блоков комментариев кода рекомендуется использовать #if 0 #endif для включения блока кода, вместо того чтобы выделять этот блок кода и комментировать его с помощью двойного слеша. Когда вам нужно открыть этот код снова, вам придётся снова выделить его и отменить комментирование. Если вы используете #if 0, вам просто нужно изменить 0 на 1, это значительно повышает эффективность разработки.

  3. Существует множество способов упаковки и публикации приложений Qt. Начиная с Qt5, предоставляются инструменты для упаковки, такие как windeployqt (linuxdeployqt для Linux и macdeployqt для Mac), которые позволяют удобно упаковывать приложения. Однако эти инструменты не всегда идеальны, иногда они добавляют ненужные файлы, а иногда забывают упаковать нужные плагины, особенно в случае использования qml. Кроме того, они не распознают сторонние библиотеки. Если ваше приложение зависит от ffmpeg, вам нужно самостоятельно скопировать соответствующие библиотеки. Окончательный метод заключается в копировании вашего исполняемого файла в каталог bin в каталоге установки Qt, а затем упаковке всего вместе, удаляя ненужные компоненты, пока приложение не начнёт работать без проблем.

  4. Анимация в Qt основана на таймере QElapsedTimer. Он генерирует данные в соответствии с определёнными правилами алгоритма, а затем обрабатывает свойства.

  5. При рисовании круга без фона, только с цветом границы, можно заменить его рисованием дуги в 360 градусов, эффект будет таким же.

QRect rect(-radius, -radius, radius * 2, radius * 2);
// Можно выбрать один из двух методов, второй рисует дугу в 360 градусов = круг без фона
painter->drawArc(rect, 0, 360 * 16);
painter->drawEllipse(rect);
  1. Не стоит относиться к указателю d как к чему-то загадочному, на самом деле это просто частный класс, определённый в файле реализации класса, используемый для хранения локальных переменных. Лично я рекомендую не использовать этот механизм в небольших проектах, так как он снижает читаемость кода и увеличивает сложность. Начинающие разработчики могут запутаться, когда им придётся разбираться в таком коде.

  2. Многие разработчики при рисовании ограничиваются установкой одноцветной кисти для пера QPen, но на самом деле QPen позволяет устанавливать кисть. Это значительно увеличивает гибкость. Например, установив кисть для QPen, можно использовать различные градиенты, рисовать градиентные полосы прогресса и текст, вместо использования одного цвета.

  3. Многие элементы управления имеют viewport, например QTextEdit, QTableWidget и QScrollArea. Иногда при работе с этими элементами управления напрямую они не работают, нужно настроить viewport(). Например, чтобы установить прозрачный фон области прокрутки, нужно использовать scrollArea->viewport()->setStyleSheet("background-color:transparent;"), а не scrollArea->setStyleSheet("QScrollArea{background-color:transparent;}"). Иногда, если для отслеживания мыши установлено значение setMouseTracking как true и на данном окне есть другие элементы управления, то при перемещении курсора на другой элемент управления события перемещения мыши в родительском классе (MouseMove) не распознаются. В этом случае необходимо использовать событие HoverMove. Сначала нужно установить:

setAttribute(Qt::WA_Hover, true);
  1. Класс даты и времени QDateTime в Qt очень мощный, он может конвертировать строки и даты, миллисекунды и даты, а также количество секунд с 1970 года и даты.
QDateTime dateTime;
QString dateTime_str = dateTime.currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
// Из строки в миллисекунды (требуется полный формат года, месяца, дня, часа, минуты, секунды)
datetime.fromString("2011-09-10 12:07:50:541", "yyyy-MM-dd hh:mm:ss:zzz").toMSecsSinceEpoch();
// Из строки в секунды (требуется полный формат года, месяца, дня, часа, минуты, секунды)
datetime.fromString("2011-09-10 12:07:50:541", "yyyy-MM-dd hh:mm:ss:zzz").toTime_t();
// Из миллисекунд в год, месяц, день, час, минуту, секунду
datetime.fromMSecsSinceEpoch(1315193829218).toString("yyyy-MM-dd hh:mm:ss:zzz");
// Из секунд в год, месяц, день, час, минуту, секунду (если есть zzz, то будет 000)
datetime.fromTime_t(1315193829).toString("yyyy-MM-dd hh:mm:ss[:zzz]");

08: 071–080

  1. При использовании списков или массивов, таких как QList, QStringList, QByteArray, рекомендуется использовать метод at() для получения значений, а не оператор []. В официальной книге «C++ GUI Qt 4 программирование (второе издание)» этому уделяется особое внимание. Книга была написана основными разработчиками Qt, поэтому она достаточно авторитетна. Что касается сравнения скорости и эффективности использования at() и оператора [], то в интернете можно найти сравнения, проведённые другими пользователями. Оригинал текста находится на странице 212 книги. Текст описывает следующее: Qt использует неявное совместное использование для всех контейнеров и многих других классов. Неявное совместное использование гарантирует, что Qt не будет копировать данные, которые не предполагается изменять. Чтобы максимально использовать преимущества неявного совместного использования, можно принять две новые привычки программирования. Первая привычка заключается в том, чтобы использовать функцию at() при чтении из вектора или списка, а не оператора []. Это связано с тем, что классы контейнеров Qt не могут определить, будет ли оператор [] использоваться в левой части присваивания или в правой. Они предполагают наихудший случай и принудительно выполняют глубокое копирование. Функция at() не допускается в левой части присваивания.

  2. Если вам нужно, чтобы после выполнения exec() в диалоговом окне выполнялся другой код, добавьте строку кода перед exec(). Иначе выполнение будет заблокировано.

QDialog dialog;
dialog.setWindowModality(Qt::WindowModal);
dialog.exec();
  1. Для безопасного удаления объектов Qt рекомендуется использовать deleteLater вместо delete, так как deleteLater освобождает память в более подходящее время, в то время как delete может вызвать сбой программы. Для массового удаления объектов можно использовать qDeleteAll, например, qDeleteAll(btns).

  2. В QTableView, если вам нужны пользовательские кнопки столбцов, флажки, выпадающие списки и т. д., вы можете использовать пользовательский делегат QItemDelegate. Если вы хотите отключить столбец, верните 0 в функции createEditor пользовательского делегата. Пользовательский делегат отображается при входе в режим редактирования. Если вы хотите, чтобы он отображался постоянно, вам нужно перегрузить функцию paint и использовать drawPrimitive или drawControl для рисования.

  3. Когда вы ознакомитесь с несколькими методами, такими как drawPrimitive, drawControl, drawItemText, drawItemPixmap и т.д., соответствующими QApplication::style(), в сочетании со свойствами QStyleOption, вы сможете создавать различные пользовательские делегаты. Вы также можете напрямую использовать painter в функции paint для различных рисунков, таких как крутые таблицы, древовидные списки, выпадающие списки и т. д. QApplication::style()->drawControl четвёртый параметр метода, если он не установлен, нарисованный элемент управления не будет применять таблицу стилей.

  4. С координатами в уме и всем остальным — painter. Настоятельно рекомендуется просмотреть, попробовать и понять все функции в файле заголовка qpainter.h во время изучения пользовательских чертежей управления. Здесь представлены все встроенные интерфейсы рисования Qt. Попробуйте все соответствующие параметры, и вы откроете для себя много нового. Это определённо пробудит ваш интерес к рисованию, подобно тому, как это делал бы мастер каллиграфии, свободно бродящий по миру рисования кода.

  5. При использовании setItemWidget или setCellWidget иногда обнаруживается, что установленный элемент управления не центрирован, а выровнен по умолчанию влево и не растягивается автоматически. Для тех, кто стремится к совершенству, существует универсальный метод: поместите этот элемент управления в макет виджета, затем добавьте виджет в элемент, и проблема будет решена идеально. Кроме того, таким образом можно комбинировать несколько элементов управления для создания сложных элементов управления.

// Создание экземпляра индикатора выполнения
QProgressBar *progress = new QProgressBar;
// Добавление виджета + макета для умного центрирования
QWidget *widget = new QWidget;
QHBoxLayout *layout = new QHBoxLayout;
layout->setSpacing(0);
layout->setMargin(0);
layout->addWidget(progress);
widget->setLayout(layout);
ui->tableWidget->setCellWidget(0, 0, widget);
  1. Часто требуется чётко рисовать текст на известном фоне. В этом случае нужно вычислить соответствующий цвет текста.
// Автоматический расчёт подходящего переднего плана на основе цвета фона
double gray = (0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()) / 255;
QColor textColor = gray > 0.5 ? Qt::black : Qt::white;
  1. Отключите перетаскивание столбцов в QTableView, QTableWidget, QTreeView и QTreeWidget.
#if (QT_VERSION < QT_VERSION_CHECK(5,0,0))
    ui->tableView->horizontalHeader()->setResizeMode(0, QHeaderView::Fixed);
    ui->treeView->header()->setResizeMode(0, QHeaderView::Fixed);
#else
    ui->tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed);
    ui->treeView->header()->setSectionResizeMode(0, QHeaderView::Fixed);
#endif
  1. При переходе с Qt4 на Qt5 некоторые методы классов устарели. Если вы хотите использовать методы Qt4 в Qt5, например, setMovable класса QHeadVew, добавьте следующую строку в файл pro или pri: DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0

09: 081–090

  1. Класс QColor в Qt идеально инкапсулирует цвета и поддерживает различные преобразования, такие как rgb, hsb, cmy, hsl, которым соответствуют toRgb, toHsv, toCmyk, toHsl. Он также поддерживает настройку прозрачности и преобразование цветовых значений в шестнадцатеричный формат.
QColor color(255, 0, 0, 100);
qDebug() << color.name() << color.name(QColor::HexArgb);
// Вывод #ff0000 #64ff0000
  1. Тип данных QVariant очень универсален и часто используется при хранении конфигурационных файлов. Он имеет встроенные функции преобразования, такие как toString и toFloat, но иногда требуется преобразовать QVariant в QColor, а функции toColor нет. В таких случаях приходится прибегать к универсальным методам.
if (variant.typeName() == "QColor") {
    QColor color = variant.value<QColor>();
    QFont font = variant.value<QFont>();
    QString nodeValue = color.name(QColor::HexArgb);
}
  1. При преобразовании между QString и const char* в Qt лучше использовать toStdString().c_str(), а не toLocal8Bit().constData(). Например, если вы используете последний в setProperty, китайские иероглифы будут отображаться неправильно, а английские — нормально. Механизм сигналов и слотов в Qt просто потрясающий, это одна из уникальных ключевых функций Qt. Иногда нам нужно передавать сигналы между множеством окон для реализации обновления или обработки данных. Если иерархия окон довольно сложная, например, окно A является родителем окна B, а окно B — родителем окна C, и у окна C есть дочернее окно D, то если окну A нужно передать сигнал окну D, проблема возникает: сигнал должен пройти через окно B к окну C и только потом к окну D. В результате получается множество связей сигналов connect, которые сложно управлять.

Можно рассмотреть добавление глобального синглтона класса AppEvent, где будут храниться общие сигналы. Затем окно A может связать свои сигналы с AppEvent, а окно D может привязаться к сигналам AppEvent в соответствующих слотах. Это будет чисто, аккуратно и удобно в управлении.

  1. Меню правой кнопки мыши для QTextEdit по умолчанию на английском языке. Если вы хотите видеть его на китайском, достаточно загрузить widgets.qm файл. В одной Qt программе можно использовать несколько файлов локализации без конфликтов.

  2. В Qt есть глобальный сигнал переключения фокуса focusChanged, который можно использовать для создания пользовательского метода ввода. В Qt4 метод ввода контекста устанавливается по умолчанию, вы можете увидеть это, напечатав a.inputContext в функции main. Этот контекст метода ввода по умолчанию перехватывает два замечательных сигнала QEvent::RequestSoftwareInputPanel и QEvent::CloseSoftwareInputPanel, так что даже если вы установили глобальный фильтр событий, он не распознает эти два сигнала. Вам нужно выполнить a.setInputContext(0) в функции main, чтобы установить контекст метода ввода пустым. Начиная с версии Qt5.7, предоставляется встроенный метод ввода, который можно активировать, добавив qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard")) в начале функции main.

  3. После Qt5.10 минимальная ширина столбца для табличных виджетов QTableWidget или QTableView была изменена на 15 (ранее было 0). Поэтому в новых версиях Qt, если вы установите слишком малую ширину столбцов, она не применится, вместо этого будет использоваться минимальная ширина. Чтобы установить меньшую ширину столбцов, вам нужно снова установить ui->tableView->horizontalHeader()->setMinimumSectionSize(0).

  4. В исходном коде Qt встроено некоторое количество непубличных технологий, которые нельзя использовать напрямую, они спрятаны в секции private соответствующих модулей, таких как gui-private, widgets-private и т.д. Например, классы для работы с zip файлами QZipReader и QZipWriter находятся в модуле gui-private. Чтобы их использовать, необходимо добавить в pro файл строку QT += gui-private.

#include "QtGui/private/qzipreader_p.h"
#include "QtGui/private/qzipwriter_p.h"

QZipReader reader(dirPath);
QString path("");
// Распаковываем папку в текущий каталог
reader.extractAll(path);
// Имя папки
QZipReader::FileInfo fileInfo = reader.entryInfoAt(0);
// Распаковка файла
QFile file(filePath);
file.open(QIODevice::WriteOnly);
file.write(reader.fileData(QString::fromLocal8Bit("%1").arg(filePath)));
file.close();
reader.close();

QZipWriter *writer = new QZipWriter(dirPath);
// Добавляем папку
writer->addDirectory(unCompress);
// Добавляем файл
QFile file(filePath);
file.open(QIODevice::ReadOnly);
writer->addFile(data, file.readAll());
file.close();
writer->close();
  1. Теоретически, отправка и получение данных через последовательный порт и сеть по умолчанию асинхронны, операционная система управляет этим автоматически, и интерфейс не блокируется. Утверждения о том, что передача данных блокирует интерфейс, неверны; реальное время занимает обработка данных, а не их передача. Для проектов с небольшими объёмами данных обычно не рекомендуется использовать потоки для обработки, поскольку управление потоками требует дополнительных затрат. Не стоит помещать всё в потоки, потоки не являются универсальным решением. Потоки нужны только для действительно ресурсоёмких операций, таких как кодирование и декодирование.

  2. Получение ширины и высоты элемента управления в конструкторе может быть неправильным. Необходимо получать размеры после первого отображения элемента управления. Элементы управления устанавливают корректные значения ширины и высоты только после первого показа. Важно помнить, что это происходит после первого показа, а не после запуска программы или конструктора. Если после запуска программы некоторые элементы управления в контейнерах, таких как QTabWidget, не отображаются, попытка получить их ширину и высоту также может быть некорректной. Безопаснее всего получать размеры после первого показа.

10:091-100

  1. Обработку базы данных рекомендуется проводить в главном потоке. Если необходимо выполнять операции с базой данных в другом потоке, важно помнить, что открывать базу данных также следует в этом потоке. Нельзя открыть базу данных в главном потоке и выполнять SQL запросы в дочернем потоке, это может вызвать проблемы.

  2. В новой версии QTcpServer в 64-битной версии Qt функция incomingConnection может не вызываться. Это связано с изменением параметра функции incomingConnection в Qt5 с int на qintptr. Преимущество использования qintptr заключается в том, что в 32-битных системах он автоматически преобразуется в quint32, а в 64-битных — в quint64. Если в Qt5 продолжать использовать параметр int, то в 32-битной системе проблем не возникнет, а в 64-битной могут возникнуть ошибки. Поэтому для обеспечения совместимости с Qt4 и Qt5 необходимо использовать разные параметры.

#if (QT_VERSION > QT_VERSION_CHECK(5,0,0))
    void incomingConnection(qintptr handle);
#else
    void incomingConnection(int handle);
#endif
  1. Qt поддерживает автоматическое связывание всех интерфейсных элементов управления, таких как QPushButton и QLineEdit, с соответствующими сигналами и слотами. Например, для сигнала нажатия кнопки можно реализовать слот on_pushButton_clicked().

  2. Контрол QWebEngineView использует OpenGL, что может вызывать различные проблемы на некоторых компьютерах из-за низкого качества драйверов OpenGL. Например, при отображении в полноэкранном режиме может перестать работать правая кнопка мыши. В функции main необходимо включить программную отрисовку OpenGL.

#if (QT_VERSION > QT_VERSION_CHECK(5,4,0))
    // Оба метода работают, Qt по умолчанию использует AA_UseDesktopOpenGL
    QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
    // QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
#endif
    QApplication a(argc, argv);

Другой способ решения проблемы с неработающим контекстным меню при использовании QWebEngineView в полноэкранном режиме — сдвиг на один пиксель вверх.

QRect rect = qApp->desktop()->geometry();
rect.setY(-1);
rect.setHeight(rect.height());
this->setGeometry(rect);
  1. Класс QStyle предоставляет множество полезных методов, включая точное получение значения положения ползунка при нажатии мыши.
QStyle::sliderValueFromPosition(minimum(), maximum(), event->x(), width());
  1. При чтении и записи файлов с использованием QFile рекомендуется использовать файловые потоки QTextStream, это значительно ускоряет процесс, примерно на 30%. Чем больше файл, тем заметнее разница в производительности.
// Загрузка таблицы соответствий английских и китайских названий свойств
QFile file(":/propertyname.txt");
if (file.open(QFile::ReadOnly)) {
    // Скорость чтения с использованием QTextStream как минимум на 30% выше
#if 0
    while(!file.atEnd()) {
        QString line = file.readLine();
        appendName(line);
    }
#else
    QTextStream in(&file);
    while (!in.atEnd()) {
        QString line = in.readLine();
        appendName(line);
    }
#endif
    file.close();
}
  1. По умолчанию QFile.readAll() считывает файлы QSS в формате ANSI, не поддерживая UTF-8. Если редактировать и сохранять QSS файлы в QtCreator, это может привести к тому, что после загрузки QSS эффекта не будет. 1. Сценарии с жёсткими требованиями к синхронизации данных лучше поместить в многопоточность, иначе вы будете ждать и застрянете там;
  • Многопоточность требует системных ресурсов. Теоретически, если количество потоков превышает количество ядер CPU, то на самом деле планирование многопоточности может занять больше времени. Вам следует взвесить все за и против при использовании;
  • Повторно подчёркиваем: не стоит ожидать от сетевой коммуникации Qt поддержки высокой параллельности, максимум до 1000 подключений могут работать нормально, обычно рекомендуется использовать соединения в количестве до 500. Если требуется большое количество высокопараллельных операций, используйте сторонние библиотеки, такие как swoole и т. д.
  1. На встроенном Linux, если установлено окно без рамки, и в этом окне есть текстовые поля и другие элементы, обнаруживается, что невозможно сфокусироваться для ввода. В этом случае необходимо активно активировать окно.

// Такой способ установки окна без рамки на встроенном устройстве не позволяет получить фокус
setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint);

// Необходимо активно активировать окно после show
w->show();
w->activateWindow();
  1. Функция replace класса QString изменяет исходную строку. Помните, она возвращает новую строку после замены, но также изменяет исходную.

  2. Эффекты, связанные с классом QGraphicsEffect, очень впечатляющие, они могут создавать множество эффектов, таких как прозрачность, градиент, тень и т.д., но этот класс сильно нагружает CPU. Если нет особой необходимости, не рекомендуется его использовать. Даже если он используется, это должно быть сделано в сценах, где компонент не будет часто перерисовываться в будущем, иначе это может привести к сбоям.

12: 111–120

  1. В разных платформах пути к файлам имеют разные разделители. Например, в Linux системах обычно используется /, а в Windows — \. Qt поддерживает / в путях внутри программы независимо от операционной системы (win или linux), но некоторые сторонние библиотеки могут потребовать преобразования в соответствующий системный путь. Для этого можно использовать встроенные методы Qt для преобразования разделителей.
QString path = "C:/temp/test.txt";
path = QDir::toNativeSeparators(path);
// Вывод: C:\\temp\\test.txt

QString path = "C:\\temp\\test.txt";
path = QDir::toNativeSeparators(path);
// Вывод: C:/temp/test.txt
  1. Используя метод QMetaObject::invokeMethod, можно достичь множества эффектов, включая синхронное и асинхронное выполнение, что в значительной степени решает проблемы обработки сигналов и слотов между потоками. Например, есть сценарий приложения, где в обратном вызове нужно асинхронно вызвать публичную функцию. Если вызвать её напрямую, то это не удастся. В таком случае нужно использовать QMetaObject::invokeMethod(obj, "fun", Qt::QueuedConnection).
  • Функция invokeMethod имеет множество перегруженных параметров, которые позволяют передавать возвращаемые значения и параметры метода и т. д.
  • invokeMethod поддерживает не только слоты, но и сигналы, и это удивительно потокобезопасно, что позволяет использовать его в потоках без опасений.
  • Тестирование показало, что можно выполнять только методы, обозначенные как signals или slots.
  • По умолчанию можно выполнять функции в private slots, но не в public или protected.
  • Важно: перед выполнением функции должен стоять префикс slots или signals. Функции без этих меток не будут найдены в метаданных и вызовут ошибку No such method.
  • Дополнение от 6 ноября 2021 года: чтобы выполнить функцию в private, protected или public, нужно добавить ключевое слово Q_INVOKABLE перед функцией.
  • Фактически, любой метод функции может быть выполнен, что превосходит ограничения доступа private, protected и public. Это означает, что любая функция класса может быть выполнена через invokeMethod, даже если она помечена как private.
// Объявление сигналов и слотов в заголовке
signals:
    void sig_test(int type, double value);
private slots:
    void slot_test(int type, double value);
private:
    Q_INVOKABLE void fun_test(int type, double value);

// Связывание сигналов и слотов в конструкторе
connect(this, SIGNAL(sig_test(int, double)), this, SLOT(slot_test(int, double)));

// Запуск сигналов и слотов при нажатии кнопки
void MainWindow::on_pushButton_clicked()
{
    QMetaObject::invokeMethod(this, "sig_test", Q_ARG(int, 66), Q_ARG(double, 66.66));
    QMetaObject::invokeMethod(this, "slot_test", Q_ARG(int, 88), Q_ARG(double, 88.88));
    QMetaObject::invokeMethod(this, "fun_test", Q_ARG(int, 99), Q_ARG(double, 99.99));
}

// Выводит 66 66.66 и 88 88.88
void MainWindow::slot_test(int type, double value)
{
    qDebug() << type << value;
}

// Выводит 99 99.99
void MainWindow::fun_test(int type, double value)
{
    qDebug() << type << value;
}
  1. Сигналы в Qt5 являются публичными и могут быть испускаемы напрямую там, где это необходимо, тогда как в Qt4 они были защищены и требовали определения публичной функции для их испускания.

  2. Начиная с версии Qt 5.15, официальные дистрибутивы больше не включают установочные пакеты, предоставляя только исходный код. Можно скомпилировать самостоятельно или установить онлайн. Также можно скопировать установленные файлы на компьютер для использования в офлайн-режиме. Вероятно, компиляция различных версий каждый раз вызывает неудобства, и основной целью является сбор статистики об использовании пользователями, например, через онлайн-установку. Возможно, в будущем будет усилена коммерциализация.

  3. Иногда нам нужно проверить, доступен ли определённый модуль в текущей версии Qt. Для этого мы можем использовать функцию qtHaveModule в Qt5 (введена для проверки наличия модулей). Если мы хотим проверить наличие модуля, добавленного через QT += в нашем проекте, мы можем использовать contains.

qtHaveModule(webenginewidgets) {
message("Текущий модуль Qt содержит webenginewidgets")
}

!qtHaveModule(webkit) {
message("Текущей версии Qt не найден модуль webkit")
}

contains(QT, network) {
message("В текущем проекте присутствует модуль network")
}

!contains(QT, widgets) {
message("Модуль widgets не добавлен в текущий проект")
}
  1. C++11 представил новый формат строковых литералов, который позволяет избежать использования escape-символов \ в строках, особенно полезно при работе с JSON.
QString s1 = R"(test\001.jpg)";
s1.replace("\\", "#");
qDebug()<< s1;
// Результат: test#001.jpg
  1. При печати информации на Android рекомендуется использовать qInfo(), а не qDebug().

  2. Точность таймера по умолчанию в Qt недостаточна высока (например, сохранение записи или файла каждые минуту, при использовании стандартного таймера может оказаться, что иногда запись сохраняется за 60 секунд, а иногда за 59 секунд. Если нужна более высокая точность, можно установить setTimerType(Qt::PreciseTimer). Qt предлагает два типа таймеров: QTimer и QObject с событием timeevent. Для QObject таймера нужно вызвать startTimer(interval, Qt::PreciseTimer):

  • Qt::PreciseTimer — точный таймер, старается поддерживать миллисекундную точность.
  • Qt::CoarseTimer — грубый таймер, пытается сохранить точность в пределах 5% от требуемого интервала времени.
  • Qt::VeryCoarseTimer — очень грубый таймер, сохраняет только целую часть секунд.
  • Точность зависит от прерываний в операционной системе. Если прерывание занимает 5 мс, точность таймера не может быть выше 5 мс.
  1. Классы, связанные с QGraphicsEffect, потребляют много ресурсов CPU и могут вызывать конфликты при отрисовке, поэтому их использование не рекомендуется, за исключением редких случаев и мест, где отрисовка происходит нечасто.

  2. Использование QSettings для работы с реестром может вызвать ошибку «QSettings: failed to set subkey „xxx“ (отказано в доступе)», если программа запущена не от имени администратора. Необходимо запустить программу от имени администратора вручную. ```cpp #include #include

bool checkPermission(const char *permission) { #ifdef Q_OS_ANDROID QtAndroid::PermissionResult result = QtAndroid::checkPermission(permission); if (result == QtAndroid::PermissionResult::Denied) { return false; } QFutureQtAndroidPrivate::PermissionResult resultFuture = QtAndroidPrivate::requestPermission(permission); if (resultFuture.resultAt(0) == QtAndroidPrivate::PermissionResult::Denied) { return false; } #else QFutureQtAndroidPrivate::PermissionResult result = QtAndroidPrivate::requestPermission(permission); if (result.resultAt(0) == QtAndroidPrivate::PermissionResult::Denied) { return false; } #endif return true; }

int main(int argc, char *argv[]) { QApplication a(argc, argv);

// 请求权限
checkPermission("android.permission.READ_EXTERNAL_STORAGE");
checkPermission("android.permission.WRITE_EXTERNAL_STORAGE");

return a.exec();

}


137. Qt重载qDebug输出自定义的信息。
```cpp
struct FunctionInfo {
    QString function;
    QString name;
    QString groupEnabled;
    QString action;
    QString group;

    friend QDebug operator << (QDebug debug, const FunctionInfo &functionInfo) {
        QString info = QString("功能: %1  名称: %2  启用: %3  方法: %4  分组: %5")
                        .arg(functionInfo.function).arg(functionInfo.name).arg(functionInfo.groupEnabled)
                        .arg(functionInfo.action).arg(functionInfo.group);
        debug << info;
        return debug;
    }
};
  1. Для обработки адаптивного масштабирования на экранах с высоким разрешением используются следующие методы:
// Метод 1: в начале функции main добавьте следующую строку, начиная с версии 5.6
#if (QT_VERSION > QT_VERSION_CHECK(5,6,0))
    QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    // После включения поддержки высокого масштабирования может потребоваться также включить следующее свойство
    QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif

// Метод 2: создайте файл qt.conf в каталоге исполняемого файла и добавьте следующий контент
[Platforms]
WindowsArguments = dpiawareness=0
// Следующая строка используется для решения проблемы отображения текста с «лесенкой» в Qt при высоком DPI
WindowsArguments = fontengine=freetype
// По состоянию на 2 февраля 2023 года, проверено JB (JB大佬), что разделение строк запятой не даёт эффекта, необходимо использовать запятую для разделения
WindowsArguments = dpiawareness=0, fontengine=freetype

// Метод 3: установите внутренние переменные окружения Qt в начале функции main
qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "1.5");

// Метод 4: новые версии Qt, такие как Qt5.14, исправили поддержку обработки экранов с высоким разрешением, не поддерживающих целочисленное масштабирование
qputenv("QT_ENABLE_HIGHDPI_SCALING", "1");
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);

// Отключение масштабирования
// Тестирование показало, что AA_Use96Dpi работает нормально в версиях Qt начиная с 5.9, но в более ранних версиях, таких как 5.7, некоторые элементы управления могут некорректно работать при масштабировании 175%, например, QTextEdit. Чтобы исправить это, нужно обернуть их в виджет.
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
    QApplication::setAttribute(Qt::AA_Use96Dpi);
#endif
#if (QT_VERSION >= QT_VERSION_CHECK(5,14,0))
    QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Floor);
#endif

// В Qt6 AA_Use96Dpi не работает, необходимо установить принудительное масштабирование DPI следующим образом
qputenv("QT_FONT_DPI", "96");
  1. QTabWidget имеет механизм автоматического создания кнопок для переключения вкладок. Иногда вы можете не захотеть видеть эти кнопки, установив usesScrollButtons в false. На самом деле, свойство usesScrollButtons у QTabWidget применяется к объекту QTabBar внутри QTabWidget. Поэтому достаточно установить это свойство для глобального объекта QTabBar. Зачем устанавливать его глобально? Потому что если вы установите это свойство только для QTabWidget, а затем используете QDockWidget для объединения вкладок в QMainWindow, то всё равно будут отображаться кнопки переключения.
// Устанавливаем отсутствие кнопок переключения для tabWidget
ui->tabWidget->setUsesScrollButtons(false);
// Устанавливаем отсутствие кнопок переключения для tabBar
ui->tabWidget->tabBar()->setUsesScrollButtons(false);
// Устанавливаем отсутствие кнопок переключения для всей системы вкладок
QTabBar{qproperty-usesScrollButtons:false;}
// Включаем автоматическое расширение вкладок (это было автоматически рассчитано ранее)
QTabBar{qproperty-expanding:false;}
// Делаем кнопки закрытия вкладок видимыми
QTabBar{qproperty-tabsClosable:true;}
// Другие свойства можно найти в заголовочном файле QTabBar, включая сюрпризы
// Этот метод позволяет установить любые свойства, определённые в Q_PROPERTY для всех визуальных классов
  1. По умолчанию разделительные линии в QMainWindow имеют большой размер. Иногда вы хотите сделать их меньше или вообще убрать. Сначала вы думали, что это связано с QSplitter, но после поиска во всех дочерних элементах ничего не нашли. Наконец, вы обнаружили соответствующие настройки в таблице стилей.
// Это действительно неожиданный способ настройки
QMainWindow::separator{width:1px;height:1px;margin:1px;padding:1px;background:#FF0000;}

15: 141–150

  1. QImage поддерживает значки xpm. Изучая код стилей QStyle в Qt, можно обнаружить множество определений значков xpm. Эти значки создаются с помощью кода, и это потрясающе.
static const char * const imgData[] = {
    "15 11 6 1",
    "   c None",
    "+  c #979797",
    "@  c #C9C9C9",
    "$  c #C1C1C1",
    "b  c None",
    "d  c None",
    " $++++++++$    ",
    "$+bbbbbbbb+$   ",
    "+b $$      +$  ",
    "+b $@       +$ ",
    "+b           +$",
    "+b           d+",
    "+b          d+$",
    "+b $$      d+$ ",
    "+b $@     d+$  ",
    "$+dddddddd+$   ",
    " $++++++++$    "};

// Теперь мы можем отобразить изображение со стрелкой напрямую
QImage img(imgData);
QLabel lab;
lab.setPixmap(QPixmap::fromImage(img));
lab.show();
  1. При одновременном использовании закрепляемого окна QDockWidget и QOpenGLWidget возникают проблемы с контекстом OpenGL при переключении между плавающим и закреплённым состояниями. Необходимо установить общий контекст OpenGL в начале функции main.
int main(int argc, char *argv[]) {
    // Нужно установить общий контекст, иначе при переключении из закреплённого состояния в плавающее окно QOpenGLWidget будет неработоспособно
#if (QT_VERSION >
``` ```cpp
// QT_VERSION_CHECK(5,4,0))
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
#endif
QApplication a(argc, argv);
...
}
  1. 关于Qt中文乱码的问题,个人也稍微总结了一点,应该可以解决99%以上的Qt版本的乱码问题。
  • 第一步:代码文件选择用utf8编码带bom。
  • 第二步:在有中文汉字的代码文件顶部加一行(一般是cpp文件) #pragma execution_character_set("utf-8") 可以考虑放在head.h中,然后需要的地方就引入head头文件就行,而不是这行代码写的到处都是;这行代码是为了告诉msvc编译器当前代码文件用utf8去编译。
  • 第三步:main函数中加入设置编码的代码,以便兼容Qt4,如果没有Qt4的场景可以不用,从Qt5开始默认就是utf8编码。
void QtHelper::setCode()
{
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
#if _MSC_VER
    QTextCodec *codec = QTextCodec::codecForName("gbk");
#else
    QTextCodec *codec = QTextCodec::codecForName("utf-8");
#endif
    QTextCodec::setCodecForLocale(codec);
    QTextCodec::setCodecForCStrings(codec);
    QTextCodec::setCodecForTr(codec);
#else
    QTextCodec *codec = QTextCodec::codecForName("utf-8");
    QTextCodec::setCodecForLocale(codec);
#endif
}
  1. 关于Qt众多版本(至少几百个)都不兼容的问题,在经过和Qt中国的林斌大神和其他大神(Qt非官方技术交流群)头脑风暴以后,最终得出以下的结论。
  • Qt在二进制兼容这块,已经做了最大的努力,通过将各种代码细节隐藏,Q指针+D指针技巧,尽量保持了接口的统一;
  • 是否兼容最主要考虑编译器的因素,毕竟任何Qt版本都是需要通过编译器编译成对应的二进制文件,由他说了算。如果两个Qt版本采用的编译器版本一样,极大概率可执行文件是兼容的,比如 Qt5.10+msvc2015 32 位 和 Qt5.11+msvc2015 32位 编译出来的可执行文件,都用Qt5.11的库是可行的;
  • mingw编译器的Qt版本也是如此,就是因为Qt官方安装包集成的mingw编译器一直在更新(极少附近版本没有更新mingw编译器版本除外),比如5.7用的mingw53,5.12用的mingw73,5.15用的mingw81,因为带的Qt库也是这个编译器编译出来的,所以导致看起来全部不兼容;
  • 如果想要完全兼容,还有一个注意要素,那就是对应代码使用的类的头文件接口是否变了,按道理原有的接口极少会变,一般都是新增加,或者大版本才会改变,比如Qt4-Qt5-Qt6这种肯定没法兼容的,接口和模块都变了;
  • 大胆的猜测:如果Qt5.6到Qt5.15你全部用一种编译器比如mingw73或者msvc2015重新编译生成对应的Qt运行库,然后在此基础上开发程序,最后生成的可执行文件用Qt5.15的库是都可以的,这样就轻松跨越了多个版本兼容;
  • 大胆的建议:在附近的几个版本统一编译器,比如5.6-5.12之间就统一用mingw53或者msvc2015,5.12-5.15统一用msvc2017,要尝鲜其他编译器的可以自行源码编译其他版本,这样最起码附近的一大段版本(大概2-3年的版本周期)默认就兼容了。
  • 本人测试的是widget部分,qml未做测试,不清楚是否机制一样;
  1. 通过酷码大哥(Qt开发者交流群)的指点,到今天才知道,Qt设置样式表支持直接传入样式表文件路径,亲测4.7到5.15任意版本,通过查看对应函数的源码可以看到内部会检查是否是 'file:///' 开头,是的话则自动读取样式表文件进行设置,无需手动读取。
//以前都是下面的方法
QFile file(":/qss/psblack.css");
if (file.open(QFile::ReadOnly)) {
    QString qss = QLatin1String(file.readAll());
    qApp->setStyleSheet(qss);
    file.close();
}

//其实一行代码就行
qApp->setStyleSheet("file:///:/qss/psblack.css");
//特别说明,只支持qApp->setStyleSheet 不支持其他比如widget->setStyleSheet
  1. Qt中自带的很多控件,其实都是由一堆基础控件(QLabel、QPushButton等)组成的,比如日历面板 QCalendarWidget 就是 QToolButton+QSpinBox+QTableView 等组成,妙用 findChildren 可以拿到父类对应的子控件集合,可以直接对封装的控件中的子控件进行样式的设置,其他参数的设置比如设置中文文本(默认可能是英文)等。
//打印子类类名集合
void printObjectChild(const QObject *obj, int spaceCount)
{
    qDebug() << QString("%1%2 : %3")
             .arg("", spaceCount)
             .arg(obj->metaObject()->className())
             .arg(obj->objectName());

    QObjectList childs = obj->children();
    foreach (QObject *child, childs) {
        printObjectChild(child, spaceCount + 2);
    }
}

//拿到对话框进行设置和美化
QFileDialog *fileDialog = new QFileDialog(this);
fileDialog->setOption(QFileDialog::DontUseNativeDialog, true);
QLabel *lookinLabel = fileDialog->findChild<QLabel*>("lookInLabel");
lookinLabel->setText(QString::fromLocal8Bit("文件目录:"));
lookinLabel->setStyleSheet("color:red;");

//设置日期框默认值为空
QLineEdit *edit = ui->dateEdit->findChild<QLineEdit *>("qt_spinbox_lineedit");
if (!edit->text().isEmpty()) {
    edit->clear();
}
  1. Qt内置了各种对话框,比如文件对话框-QFileDialog ,颜色对话框-QColorDialog ,默认都会采用系统的对话框风格样式,这样可以保持和系统一致,如果不需要的话可以取消该特性,取消以后会采用Qt自身的对话框,这样才能进行美化和其他处理。
QFileDialog *fileDialog = new QFileDialog(this);
//不设置此属性根本查找不到任何子元素,因为默认采用的系统对话框
fileDialog->setOption(QFileDialog::DontUseNativeDialog, true);
qDebug() << fileDialog->findChildren<QLabel *>();
//打印输出 QLabel(0x17e2ff68, name="lookInLabel"), QLabel(0x17e35f88, name="fileNameLabel"), QLabel(0x17e35e68, name="fileTypeLabel")
  1. QtCreator集成开发环境,也内置了对快速添加注释的支持,比如最常用的在头文件开头添加一大段通用模板的注释,标注文件创建者、时间等信息。
  • 菜单->工具->选项->文本编辑器->右侧tab页面片段(snippets);
  • 组选择C++, 可以看到这里面已经内置了不少定义比如foreach,可以依葫芦画瓢;
  • 添加一个片段, 比如名字是fun, 触发种类是这个片段的简单描述;
  • 当我们在代码文件中键入fun时, 会自动弹出智能提醒, 选择我们的代码片段回车, 自动填充代码;
  • 按tab可以在变量间切换, 输入完成后回车, 完成编辑;
/**
  * @brief $name$
  * @param $param$
  * @author feiyangqingyun
  * @date $date$
  */
$ret$ $name$($param$)
{
    $$
}
``` В эпоху Qt5, как говорят, произошли значительные улучшения в механизме работы сигналов и слотов.

- До Qt5 обычно писали connect так: connect(sender, SIGNAL(signalFunc()), receiver, SLOT(receiveFunc())). То есть при использовании connect нужно было оборачивать сигнал макросом SIGNAL, а функцию слота  макросом SLOT, чтобы механизм Moc от Qt мог их распознать.

- Даже если во время компиляции сигнала или слота не существовало или были неправильные параметры, ошибка не появлялась, но при выполнении возникала подсказка. Для статического языка вроде C++ это не очень удобно и мешает отладке.

- После Qt5 более рекомендуется использовать «запись с взятием адреса». При таком подходе, если при компиляции сигнал или слот не существует, компиляция не проходит, что служит своего рода проверкой на этапе компиляции и снижает вероятность ошибок.

- Если нет необходимости поддерживать совместимость с Qt4 из-за исторических проблем, лучше использовать новый подход. Он обеспечивает строгую проверку типов и поддерживает разнообразные способы записи.

- Для простых логических операций настоятельно рекомендуется использовать лямбда-выражения для непосредственной обработки.
```cpp
class MainWindow : public QMainWindow
{
    Q_OBJECT

public: MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    Ui::MainWindow *ui;

private:
    void test_fun();

private slots:
    void test_slot();
};

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    // Ранний подход, работает со всеми версиями Qt, поддерживает только функции с ключевым словом slots
    // connect(ui->pushButton, SIGNAL(clicked()), this, SLOT(test_fun()));
    connect(ui->pushButton, SIGNAL(clicked()), this, SLOT(test_slot()));

    // Новый подход, поддерживается в Qt5 и последующих версиях, работает с любыми функциями, не требует использования ключевого слова slots
    connect(ui->pushButton, &QPushButton::clicked, this, &MainWindow::test_fun);
    connect(ui->pushButton, &QPushButton::clicked, this, &MainWindow::test_slot);

    // Альтернативный подход с использованием лямбда-выражений, позволяет выполнять код напрямую
    connect(ui->pushButton, &QPushButton::clicked, [this] {test_fun();});
    connect(ui->pushButton, &QPushButton::clicked, [this] {
        qDebug() << "hello lambda";
    });

    // Лямбда с параметром
    connect(ui->pushButton, &QPushButton::clicked, [&] (bool isCheck) {
        qDebug() << "hello lambda" << isCheck;
    });

    // Заголовочный файл содержит signals:void sig_test(int i);
    connect(this, &MainWindow::sig_test, [] (int i) {
        qDebug() << "hello lambda" << i;
    });
    emit sig_test(5);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::test_fun()
{
    qDebug() << "test_fun";
}

void MainWindow::test_slot()
{
    qDebug() << "test_slot";
}
  1. Механизм работы таблиц стилей Qt имеет различные подходы, которые учитывают разные требования и сценарии. Классы, унаследованные от QWidget, и класс qApp поддерживают метод setStyleSheet. Таблицы стилей можно объединить в один файл или добавить в файл ресурсов.
  • Подход «Боец»: содержимое qss пишется прямо в коде, где это необходимо. Различные элементы управления вызывают метод setStyleSheet для передачи содержимого таблицы стилей. Можно также изменить таблицу стилей через контекстное меню элемента управления.

  • Подход «Воин»: содержимое qss хранится в файле, который считывается для установки таблицы стилей. При публикации программы qss-файл включается в комплект.

  • Подход «Мастер»: qss-файл добавляется в файл ресурсов (qrc), компилируется непосредственно в исполняемый файл для предотвращения несанкционированных изменений.

  • Подход «Чародей»: в qss-файле определяются переменные, которые заменяются соответствующими значениями (например, цветами) при чтении файла. Это похоже на динамическую смену скинов.

  • Подход «Король»: хотя размещение таблицы стилей в отдельном файле упрощает изменение, оно также делает его уязвимым для несанкционированного доступа. Встраивание таблицы стилей в исполняемый файл ограничивает гибкость. Чтобы обновить таблицу стилей без перекомпиляции всего файла, используется команда rcc для компиляции ресурса в двоичный файл. Затем можно заменить этот двоичный файл для обновления стиля.

  • Подход «Император»: наследуя класс qstyle, можно реализовать все стили интерфейса и установить единый стиль для всей системы. Известная система UOS использует этот подход, запрещая использование таблиц стилей и выполняя всю отрисовку через painter.

16:151-160

  1. Когда ресурсы в Qt становятся слишком большими, процесс компиляции замедляется, и любые изменения в ресурсах требуют пересборки исполняемого файла. Qt предоставляет стратегию для решения этой проблемы: преобразование ресурсов в двоичные rcc-файлы. Это отделяет ресурсы от основного кода и позволяет динамически загружать их при необходимости.
// Использование двоичных файлов ресурсов в Qt
// Скомпилируйте qrc в двоичный файл rcc с помощью команды в консоли
rcc -binary main.qrc -o main.rcc
// Зарегистрируйте ресурс в приложении, обычно после запуска main
QResource::registerResource(qApp->applicationDirPath() + "/main.rcc");
  1. При настройке шрифта часто возникает путаница, особенно когда пытаются установить размер шрифта для всех элементов управления в окне. Оказывается, шрифт применяется только к главному окну, а дочерние элементы управления остаются без изменений.
// Предположим, что в окне есть дочерние элементы управления, и по умолчанию шрифт имеет размер 12 пикселей. Родительский класс — QWidget, имя родительского класса — Widget

// Эти методы установят шрифт только для главного окна, дочерние элементы не будут применять шрифт, требуется индивидуальный вызов setFont
QFont font;
font.setPixelSize(20);
this->setFont(font);
this->setStyleSheet("{font:26px;}");
this->setStyleSheet("QWidget{font:26px;}");
this->setStyleSheet("Widget{font:26px;}");

// Вот как правильно установить шрифт для всего элемента управления и его дочерних элементов с помощью таблицы стилей
this->setStyleSheet("font:26px;");
this->setStyleSheet("*{font:26px;}");
this->setStyleSheet("QWidget>*{font:26px;}");
this->setStyleSheet("Widget>*{font:26px;}");

// Глобальная установка шрифта
qApp->setFont(font);
  1. Класс QImage в Qt предоставляет мощные возможности для работы с изображениями, включая преобразование форматов и замену цветов пикселей. Если нужно преобразовать одноцветное изображение в другой цвет, важно учитывать формат изображения, особенно если оно содержит альфа-значения. Например, может потребоваться преобразовать его в Format_ARGB32 или Format_RGBA8888.
// Функции pixel и setPixel доступны в любой версии Qt
// Функции pixelColor и setPixelColor доступны начиная с Qt 5.6
// Функция pixel возвращает значение цвета в формате QRgb, которое нужно преобразовать с помощью qRed, qGreen, qBlue, qAlpha
QImage image("1.png");
image = image.convertToFormat(QImage::Format_ARGB32);
int width = image.width();
int height = image.height();
// Перебираем каждый пиксель изображения
for (int x = 0; x < width; ++x) {
    for (int y = 0; y < height; ++y) {
        QString name = image.pixelColor(x, y).name();
        // Заменяем все цвета, кроме белого, на красный
        if (name != "#ffffff") {
            image.setPixelColor(x, y, Qt::red);
        }
    }
}

// Сохраняем файл
image.save("2.png");
``` В приложениях, связанных с базами данных, если речь идёт только о версии для одного компьютера и нет особых требований (например, по указанию руководства или необходимости удалённого хранения данных), настоятельно рекомендуется использовать базу данных sqlite. Это вывод, полученный автором в результате бесчисленных сравнительных тестов и применения в N коммерческих проектах.

- Qt изначально имеет встроенную поддержку базы данных sqlite, нужно только включить плагин при публикации приложения (можно заметить, что файл динамической библиотеки плагина больше, чем у других типов баз данных, потому что исходный код базы данных скомпилирован напрямую, в то время как другие компилируют только исходный код плагина для промежуточного коммуникационного взаимодействия);
- скорость работы не имеет себе равных: при одинаковой структуре базы данных (структура таблиц, индексы и т. д.) скорость запросов, пакетных обновлений, транзакций базы данных и т.д. как минимум в три раза выше, чем у других систем, и этот разрыв становится всё более заметным с увеличением объёма данных;
- работа с данными объёмом в несколько десятков миллионов не представляет проблемы, при этом скорость и производительность остаются на приемлемом уровне. Не стоит верить слухам в интернете о том, что sqlite не поддерживает данные объёмом более десяти миллионов. Автор лично проверил работу с данными порядка миллиарда, но рекомендует использовать sqlite для данных объёмом до десятков миллионов, уделяя особое внимание проектированию таблиц базы данных и индексов;
- для других баз данных также следует учитывать различия версий, а формат источника данных ODBC может легко привести к ошибкам и сбоям выполнения;
- у базы данных sqlite есть несколько серьёзных недостатков: отсутствие поддержки шифрования, сетевого доступа, некоторых функций продвинутых баз данных и работы с огромными объёмами данных (порядка миллиардов), но для большинства проектов на Qt этого достаточно;
- удобство поддержки баз данных примерно следующее: sqlite > postgresql > mysql > odbc;
- если используется режим соединения с базой данных через источник данных ODBC, достаточно установить имя базы данных, соответствующее имени нового источника данных, затем настроить имя пользователя и пароль, установка хоста и порта не требуется, поскольку эти параметры уже настроены в источнике данных, здесь нужно только подтвердить информацию пользователя;
- источник данных ODBC бывает 32-битным и 64-битным. В диспетчере источников данных, если добавленный источник данных отображается как соответствующий только 32-битной или 64-битной платформе, ваше приложение Qt должно быть скомпилировано для соответствующей битовой версии, чтобы успешно подключиться. Если отображается 64-разрядная версия, подключение с использованием 32-разрядного приложения не удастся.
- 32-разрядное приложение Qt с соответствующей 32-разрядной динамической библиотекой libmysql может подключаться к 32/64-разрядному серверу MySQL, 64-разрядное приложение также может подключаться к 32/64-разрядному MySQL, просто необходимо предоставить соответствующую версию динамической библиотеки. Чтобы узнать, является ли MySQL 32-разрядным или 64-разрядным, используйте команду mysql.exe -V.
- Если драйвер не загружен (Driver not loaded) при наличии доступного и работоспособного драйвера MySQL, это, скорее всего, связано с неправильной версией скопированной динамической библиотеки libmysql или несоответствием битности.

Все эти выводы были сделаны автором в среде Qt, результаты могут быть неточными и предоставляются только для справки. Другие среды программирования, такие как C# и JAVA, не рассматриваются, так как различия могут быть связаны с эффективностью промежуточного коммуникационного процесса.

155. Qt 5.10 и более поздние версии предоставляют новые классы QRandomGenerator и QRandomGenerator64 для управления случайными числами, которые удобнее использовать, особенно для генерации случайных чисел в заданном диапазоне.
```cpp
// Ранний метод обработки: сначала инициализируйте семя случайного числа, затем получите случайное число
qsrand(QTime::currentTime().msec());
// Получить случайное число от 0 до 10
qrand() % 10;
// Получить случайное вещественное число от 0 до 1
qrand() / double(RAND_MAX);

// Новый метод обработки: поддерживается всеми версиями начиная с 5.10, включая qt6
QRandomGenerator::global()->bounded(10);      // Генерирует целое число от 0 до 10
QRandomGenerator::global()->bounded(10.123);  // Генерирует вещественное число от 0 до 10.123
QRandomGenerator::global()->bounded(10, 15);  // Генерирует целое число от 10 до 15

// Метод, совместимый с версиями qt4-qt6 и более поздними: используйте стандартные функции случайных чисел C++
srand(QTime::currentTime().msec());
rand() % 10;
rand() / double(RAND_MAX);

// Общая формула: a — начальное значение, n — диапазон целых чисел
int value = a + rand() % n;
// Случайное число в диапазоне (min, max)
int value = min + 1 + (rand() % (max - min - 1));
// Случайное число в диапазоне (min, max]
int value = min + 1 + (rand() % (max - min + 0));
// Случайное число в диапазоне [min, max)
int value = min + 0 + (rand() % (max - min + 0));
// Случайное число в диапазоне [min, max]
int value = min + 0 + (rand() % (max - min + 1));

// Если вы генерируете случайные числа в потоке, время запуска потоков почти одинаково,
// возможно получение одинаковых случайных чисел. Даже установка случайного числа на текущее время не поможет,
// компьютер работает слишком быстро, вероятно, время будет одинаковым даже в пределах одной миллисекунды.
// Хитрость заключается в использовании идентификатора текущего потока в качестве начального значения перед функцией run.
// Время ненадёжно, идентификатор потока уникален.
// Помните, что преобразование void * в числовое значение должно выполняться с помощью long long,
// в 32-битном режиме можно использовать int, но в 64-битном — long, убедитесь, что используете quint64 напрямую.
srand((long long)currentThreadId());
qrand((long long)currentThreadId());
  1. После изменения размера пользовательского интерфейса в Qt возникает ошибка, из-за которой стиль при наведении курсора не удаляется, требуется симуляция движения мыши.
void frmMain::on_btnMenu_Max_clicked()
{
    ......

    // После максимизации возникает ошибка: стиль при наведении не удаляется,
    // требуется симуляция перемещения мыши
    QEvent event(QEvent::Leave);
    QApplication::sendEvent(ui->btnMenu_Max, &event);
}
  1. В проекте включена поддержка синтаксиса C++11.
greaterThan(QT_MAJOR_VERSION, 4): CONFIG += c++11
lessThan(QT_MAJOR_VERSION, 5): QMAKE_CXXFLAGS += -std=c++11
  1. При загрузке большого текста, например, 10 МБ, в текстовые элементы управления Qt, такие как QTextEdit, программа может зависнуть или аварийно завершиться. Это происходит из-за того, что включено одно свойство по умолчанию, которое необходимо отключить.
ui->textEdit->setUndoRedoEnabled(false);
  1. Несколько общих советов и опыта, через которые автор прошёл множество раз.
  • Для функций с возвращаемым значением обязательно явно возвращайте результат. Некоторые компиляторы могут скомпилировать код без возвращаемого значения, но во время выполнения возникнут проблемы и результаты будут неверными, так как отсутствует возвращаемое значение.
  • Локальные переменные должны иметь явно заданные начальные значения. Это хорошая привычка, которую следует развивать, иначе компилятор может присвоить неожиданные начальные значения (например, int по умолчанию равен 0, иногда может стать большим числом, bool может иметь разные начальные значения в зависимости от компилятора). Явное задание начального значения более надёжно.
  • Некоторые функции имеют много параметров, и их количество может увеличиться в будущем. Изменение источника потребует модификации связанных сигналов и слотов. Типы и порядок параметров должны оставаться неизменными, соответствующие слоты также требуют изменений. Объём работы при внесении изменений очень велик и неудобен. Поэтому для функций с переменным количеством параметров рекомендуется использовать структуры. Это упрощает добавление новых параметров и не требует изменения связанных сигналов и слотов, таких как передача таблиц данных студентов или товаров. Структуры — лучший вариант.
  1. Ширина панели вкладок QTabWidget определяется автоматически в соответствии с длиной текста. Когда текст длинный, ширина панели вкладок увеличивается. Во многих случаях требуется одинаковая ширина или равномерное заполнение.
// Способ 1: заполнение строк пробелами
ui->tabWidget->addTab(httpClient1, "Тест");
ui->tabWidget->addTab(httpClient1, "Управление персоналом");
ui->tabWidget->addTab(httpClient1, "Настройки системы");

// Способ 2: автоматическое определение минимальной ширины при изменении размера
void MainWindow::resizeEvent(QResizeEvent *e)
{
    int count = ui->tabWidget->tabBar()->count();
    int width = this->width() - 30;
    QString qss = QString("QTabBar::tab{min-width:%1px;}").arg(width / count);
    this->setStyleSheet(qss);
}

// Способ 3: установка глобального стиля, разная ширина для разного количества вкладок
QStringList list;
list << QString("QTabWidget[tabCount=\"2\"]>QTabBar::tab{min-width:%1px;}").arg(100);
list << QString("QTabWidget[tabCount=\"3\"]>QTabBar::tab{min-width:%1px;}").arg(70);
qApp->setStyleSheet(list.join(""));
// Установка свойства tabCount для автоматического определения ширины
ui->tabWidget->setProperty("tabCount", 2);
ui->tabWidget->setProperty("tabCount", 3);

// Способ 4: настоятельно рекомендуется — использование встроенного метода setExpanding
``` ```
//打印通信用的本地绑定地址和端口
qDebug() << socket->localAddress() << socket->localPort();
//打印通信服务器对方的地址和端口
qDebug() << socket->peerAddress() << socket->peerPort() << socket->peerName();

//udp客户端
QUdpSocket *socket = new QUdpSocket(this);
//绑定网卡和端口,没有绑定过才需要绑定
//采用端口是否一样来判断是为了方便可以直接动态绑定切换端口
if (socket->localPort() != 6005) {
    socket->abort();
    socket->bind(QHostAddress("192.168.1.2"), 6005);
}
//指定地址和端口发送数据
socket->writeDatagram(buffer, QHostAddress("192.168.1.3"), 6000);

//上面是Qt5可以使用bind,Qt4中的QTcpSocket的对应接口是protected的没法直接使用,需要继承类重新实现把接口放出来。
//Qt4中的QUdpSocket有bind函数是开放的,奇怪了,为何Qt4中独独QTcpSocket不开放。
TcpSocket *socket = new TcpSocket(this);
socket->setLocalAddress(QHostAddress("192.168.1.2"));
socket->setLocalPort(6005);
  1. Что касается сетевой коммуникации, TCP и UDP — это два разных базовых сетевых коммуникационных протокола, которые не связаны друг с другом в отношении прослушивания и связи через порт. Разные протоколы или разные IP-адреса сетевых карт могут использовать один и тот же порт. Ранее кто-то сказал, что его компьютер может прослушивать один и тот же порт для связи, что перевернуло его прежнее представление, ведь в книгах ясно сказано, что это невозможно, но после удалённого просмотра стало понятно, что он выбрал другой IP-адрес сетевой карты, так что это, конечно, возможно.
  • TCP слушает порт 6000 на сетевой карте 1, также можно слушать порт 6000 на сетевой карте 2.
  • TCP слушает порт 6000 на сетевой карте 1, UDP всё ещё может продолжать слушать порт 6000 на той же сетевой карте 1.
  • TCP слушает порт 6000 на сетевой карте 1, другие TCP на этой же карте могут слушать только порты, отличные от 6000.
  • Та же логика применима к протоколу UDP.
  1. Открытый компонент QCustomPlot для создания графиков очень хорош, автор, по крайней мере, мастер своего дела. Он демонстрирует отличную производительность при отображении данных кривых. Были обобщены некоторые важные моменты, которые легко упустить.
  • Можно поменять местами оси XY, чтобы создать горизонтальный эффект, независимо от типа графика — будь то кривые, столбцы, сгруппированные графики или составные графики.
  • Ненужные подсказки можно удалить, вызвав legend->removeItem.
  • Две кривые можно объединить в одну область, используя setChannelFillGraph.
  • Чтобы ускорить отрисовку, можно отключить сглаживание, установив setAntialiased.
  • Можно настроить различные стили линий и данные.
  • Стиль стрелок на осях координат можно изменить с помощью setUpperEnding.
  • Для создания сгруппированных столбчатых графиков можно использовать QCPBarsGroup, этот класс отсутствует в официальных демонстрациях, поэтому его легко не заметить.
  • Начиная с версии V2.0, поддерживается настройка сортировки данных. По умолчанию QCustomPlot выполняет сортировку, но можно установить третий параметр setData равным true, если данные уже отсортированы, что позволяет рисовать линии в обратном направлении.
  • При частом построении данных можно настроить параметры построения в очереди, чтобы избежать повторных вызовов replot и повысить производительность. Если не включить эту опцию, возможны ошибки отрисовки.
  • Несколько графиков QCustomPlot можно объединить в одном элементе управления, что значительно повышает эффективность отрисовки, вместо создания нескольких экземпляров QCustomPlot. После объединения графики отображаются отдельно в разных частях координатной сетки, обеспечивая тот же эффект, что и несколько элементов управления QCustomPlot, но с гораздо лучшей производительностью.
//Поменять местами оси XY
QCPAxis *yAxis = customPlot->yAxis;
QCPAxis *xAxis = customPlot->xAxis;
customPlot->xAxis = yAxis;
customPlot->yAxis = xAxis;

//Удалить легенду
customPlot->legend->removeItem(1);

//Объединить две кривые в замкнутую область
customPlot->graph(0)->setChannelFillGraph(customPlot->graph(1));

//Отключить сглаживание и сглаживание при перетаскивании
customPlot->setNoAntialiasingOnDrag(true);
customPlot->graph()->setAntialiased(false);
customPlot->graph()->setAntialiasedFill(false);
customPlot->graph()->setAntialiasedScatters(false);
//Установить быструю отрисовку для значительного ускорения рисования линий шириной более 1 пикселя
customPlot->setPlottingHint(QCP::phFastPolylines);

//Различные методы установки данных
customPlot->graph(0)->setData();
customPlot->graph(0)->data()->set();

//Установка различных стилей линий и данных
customPlot->graph()->setLineStyle(QCPGraph::lsLine);
customPlot->graph()->setScatterStyle(QCPScatterStyle::ssDot);
customPlot->graph()->setScatterStyle(QCPScatterStyle(shapes.at(i), 10));

//Можно также установить изображение или пользовательскую форму
customPlot->graph()->setScatterStyle(QCPScatterStyle(QPixmap("./sun.png")));
QPainterPath customScatterPath;
for (int i = 0; i < 3; ++i) {
    customScatterPath.cubicTo(qCos(2 * M_PI * i / 3.0) * 9, qSin(2 * M_PI * i / 3.0) * 9, qCos(2 * M_PI * (i + 0.9) / 3.0) * 9, qSin(2 * M_PI * (i + 0.9) / 3.0) * 9, 0, 0);
}
customPlot->graph()->setScatterStyle(QCPScatterStyle(customScatterPath, QPen(Qt::black, 0), QColor(40, 70, 255, 50), 10));

//Изменить стиль стрелок на оси координат
customPlot->xAxis->setUpperEnding(QCPLineEnding::esSpikeArrow);
customPlot->yAxis->setUpperEnding(QCPLineEnding::esSpikeArrow);

//Установить фоновое изображение
customPlot->axisRect()->setBackground(QPixmap("./solarpanels.jpg"));
//На холсте также можно установить фоновое изображение
customPlot->graph(0)->setBrush(QBrush(QPixmap("./balboa.jpg")));
//Для всего элемента управления можно задать цвет заливки или изображение
customPlot->setBackground(QBrush(gradient));
//Установить цвет линии нулевой точки
customPlot->xAxis->grid()->setZeroLinePen(Qt::NoPen);
//Управление интерактивными возможностями, такими как масштабирование и выбор объектов
customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);

//Сгруппированный график
QCPBarsGroup *group = new QCPBarsGroup(customPlot);
QList<QCPBars*> bars;
bars << fossil << nuclear << regen;
foreach (QCPBars *bar, bars) {
    //Установить ширину столбцов
    bar->setWidth(bar->width() / bars.size());
    group->append(bar);
}
//Установить интервал между группами
group->setSpacing(2);

//Рисование линий в обратном направлении
QVector<double> keys, values;
keys << 0 << 1 << 2 << 3 << 4 << 5 << 4 << 3;
values << 5 << 4 << 6 << 7 << 7 << 6 << 5 << 4;
customPlot->graph(0)->setData(keys, values, true);

//Включить построение в очереди для повышения производительности
customPlot->replot(QCustomPlot::rpQueuedReplot);

QCPAxis *axis = customPlot->xAxis; ```cpp
double lower = axis->range().lower;
double upper = axis->range().upper;
double origin = (upper - lower) / 2;
//设置刻度线按照设置优先而不是可读性优先
axis->ticker()->setTickStepStrategy(QCPAxisTicker::tssMeetTickCount);
//设置原点值为范围值的中心点
axis->ticker()->setTickOrigin(origin);

//下面演示如何在一个控件中多个不同的曲线对应不同坐标轴
//拿到图表布局对象
QCPLayoutGrid *layout = customPlot->plotLayout();
//实例化坐标轴区域
QCPAxisRect *axisRect = new QCPAxisRect(customPlot);
//拿到XY坐标轴对象
QCPAxis *xAxis = axisRect->axis(QCPAxis::atBottom);
QCPAxis *yAxis = axisRect->axis(QCPAxis::atLeft);
//将坐标轴指定行列位置添加到布局中
layout->addElement(i, 0, axisRect);
//添加对应的画布到指定坐标轴
QCPGraph *graph = customPlot->addGraph(xAxis, yAxis);

18:171-180

  1. В программировании на Qt часто возникают проблемы с кодировкой, так как необходимо учитывать совместимость с различными системами. По умолчанию в Windows используется кодировка gbk или gb2312. Возможно, в будущем компилятор msvc будет поддерживать кодировку utf8, поэтому при передаче китайских имён файлов в некоторых программах могут возникать ошибки. Это связано с тем, что соответствующие интерфейсы используют устаревшую функцию fopen вместо более новой функции fopen_s. Аналогичная ситуация может возникнуть и в других функциях, например, в fmod. В таких случаях требуется обработка кодировки.
QString fileName = "c:/测试目录/1.txt";
//如果应用程序main函数中没有设置编码则默认采用系统的编码,可以直接通过toLocal8Bit转成正确的数据
const char *name = fileName.toLocal8Bit().constData();

//如果设置过了下面两句则需要主动转码
QTextCodec *codec = QTextCodec::codecForName("utf-8");
QTextCodec::setCodecForLocale(codec);

QTextCodec *code = QTextCodec::codecForName("gbk");
const char *name = code->fromUnicode(fileName).constData();

//推荐方式2以防万一保证绝对的正确,哪怕是设置过主程序的编码
//切记一旦设置过QTextCodec::setCodecForLocale会影响toLocal8Bit

//有时候可能还有下面这种情况
#ifdef Q_OS_WIN
#if defined(_MSC_VER) && (_MSC_VER >= 1400)
    QTextCodec *code = QTextCodec::codecForName("utf-8");
#else
    QTextCodec *code = QTextCodec::codecForName("gbk");
#endif
    const char *name = code->fromUnicode(fileName).constData();
#else
    const char *name = fileName.toUtf8().constData();
#endif
  1. При изучении исходного кода Qt были замечены некоторые тенденции и изменения:
  • Использование внутренних типов данных Qt для всех типов данных, даже если они были переопределены. Например, quint8 фактически является unsigned char, а qreal — double. Раньше в исходном коде могли использоваться double, но теперь они постепенно заменяются на qreal.
  • Замена цикла while(1) на for(;;), так как после компиляции в ассемблерный код for(;;) превращается в одну инструкцию, а while(1) — в четыре. Меньше инструкций означает меньшее использование регистров и отсутствие переходов, что теоретически ускоряет выполнение.
  • В Qt было переопределено ключевое слово forever, которое представляет собой for(;;). Это очень удобно.
  • Поддержка автоматического определения типа данных C++11 и последующих стандартов позволяет использовать универсальный тип данных auto. Исходный код Qt постепенно переходит на использование auto, что ускоряет написание кода и позволяет компилятору автоматически определять типы данных. Скорость компиляции не страдает, так как компилятору всё равно нужно определить тип данных для правой части выражения. Однако это может усложнить чтение кода, так как иногда требуется самостоятельно разбираться в логике определения типов.
  1. Для загрузки локальных файлов в Qt используется класс QUrl. Рекомендуется добавлять префикс file:/// к локальным файлам.
QString url = "file:///c:/1.html";
//浏览器控件打开本地网页文件
webView->setUrl(QUrl(url));
//打开本地网页文件,下面两种方法都可以
QDesktopServices::openUrl(QUrl::fromLocalFile(url));
QDesktopServices::openUrl(QUrl(url, QUrl::TolerantMode));
  1. При сетевых запросах часто возникает проблема с таймаутом, поскольку по умолчанию он составляет 30 секунд. Если происходит сбой сети, запрос может выполняться слишком долго, прежде чем будет обнаружена ошибка. Поэтому необходимо вручную установить тайм-аут, чтобы прервать запрос, если он превышает установленное время. Начиная с версии Qt 5.15, существует встроенная функция setTransferTimeout для установки тайм-аута.
//局部的事件循环,不卡主界面
QEventLoop eventLoop;

//设置超时 5.15开始自带了超时时间函数 默认30秒
#if (QT_VERSION >= QT_VERSION_CHECK(5,15,0))
manager->setTransferTimeout(timeout);
#else
QTimer timer;
connect(&timer, SIGNAL(timeout()), &eventLoop, SLOT(quit()));
timer.setSingleShot(true);
timer.start(timeout);
#endif

QNetworkReply *reply = manager->get(QNetworkRequest(QUrl(url)));
connect(reply, SIGNAL(finished()), &eventLoop, SLOT(quit()));
eventLoop.exec();

if (reply->bytesAvailable() > 0 && reply->error() == QNetworkReply::NoError) {
    //读取所有数据保存成文件
    QByteArray data = reply->readAll();
    QFile file(dirName + fileName);
    if (file.open(QFile::WriteOnly | QFile::Truncate)) {
        file.write(data);
        file.close();
    }
}
  1. В Qt существует три основных типа проектов: консольные проекты, соответствующие QCoreApplication, традиционные GUI-приложения, соответствующие QApplication, и quick/qml-проекты, соответствующие QGuiApplication. Многие свойства должны быть установлены в main функции в самом начале, чтобы они вступили в силу, например, поддержка высокого разрешения экрана или настройка режима OpenGL. Для каждого типа проекта требуется соответствующий экземпляр QApplication.
//如果是控制台程序则下面的QApplication换成QCoreApplication
//如果是quick/qml程序则下面的QApplication换成QGuiApplication
int main(int argc, char *argv[])
{
    //можно использовать эту строку для тестирования встроенной виртуальной клавиатуры Qt
    qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));

    //设置不应用操作系统设置比如字体
    QApplication::setDesktopSettingsAware(false);

#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
    //设置高分屏缩放舍入策略
    QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Floor);
#endif
#if (QT_VERSION > QT_VERSION_CHECK(5,6,0))
    //设置启用高分屏缩放支持
    //要注意开启后计算到的控件或界面宽度高度可能都不对,全部需要用缩放比例运算下
    QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    //设置启用高分屏图片支持
``` ```
locale = QLocale::Japanese;

//下面永远输出中文的周一到周日
locale.toString(QDateTime::currentDateTime(), "ddd");
//下面永远输出中文的星期一到星期日
locale.toString(QDateTime::currentDateTime(), "dddd");
  1. QSqlTableModel значительно упростил отображение, добавление, удаление и изменение таблиц базы данных, но только операции постраничного просмотра базы данных немного сложны.
// Создаем экземпляр модели таблицы базы данных
QSqlTableModel *model = new QSqlTableModel(this);
// Указываем имя таблицы
model->setTable("table");
// Устанавливаем сортировку столбцов
model->setSort(0, Qt::AscendingOrder);
// Устанавливаем режим редактирования
model->setEditStrategy(QSqlTableModel::OnManualSubmit);
// Выполняем запрос немедленно
model->select();
// Привязываем модель таблицы базы данных к таблице
ui->tableView->setModel(model);

// Тестирование показало, что в условиях фильтрации можно использовать не только предложение where, но и сортировку и ограничение
model->setFilter("1=1 order by id desc limit 100");

// Если в условиях фильтрации используется предложение сортировки, то метод setSort использовать нельзя
// Следующий код приведет к ошибке, возможно, из-за повторного добавления предложения order by в setSort
model->setSort(0, Qt::AscendingOrder);
model->setFilter("1=1 order by id desc limit 100");

// С помощью setFilter можно установить простое предложение where без необходимости добавлять 1=1
model->setFilter("name='张三'");
// Если есть другие предложения, такие как сортировка или ограничение, необходимо добавить 1=1 в начале
// Это означает сортировку по идентификатору в порядке возрастания и отображение записей с 5 по 15
model->setFilter("1=1 and name='张三' order by id asc limit 5,10");

// Несколько условий соединяются с помощью and
// Рекомендуется писать 1=1 перед любыми условиями фильтрации и заканчивать их символом ; для предотвращения ошибок
model->setFilter("1=1 and name='张三' and result>=70;");

// Это представляет собой запрос на выборку записей, где имя равно '张三', отсортированных по идентификатору в обратном порядке, начиная с 10-й записи до 110
model->setFilter("1=1 and name='张三' order by id desc limit 10,100;");

// Начинаем добавлять запись с третьей строки
model->insertRow(2);
// Заполняем только что добавленную строку, по умолчанию она пуста, и пользователь должен ввести данные вручную в таблицу
model->setData(model->index(2, 0), 100);
model->setData(model->index(2, 1), "张三");
// Подтверждаем изменения
model->submitAll();

// Удаляем четвертую строку
model->removeRow(3);
model->submitAll();

// В общем, после операций вставки, удаления или изменения необходимо вызвать model->submitAll() для фактического выполнения, иначе данные будут обновлены только в модели данных, а не в базе данных

// Отменяем изменения
model->revertAll();

19: 181-190

  1. Qt изначально был создан для Linux, и его развитие началось с Linux, поэтому многие программисты Qt часто используют Linux в качестве среды разработки, например, популярную систему Ubuntu. Вот несколько полезных команд Linux.
Команда Функция
sudo -s Переключение на администратора, sudo -i изменяет текущий каталог
apt install g++ Установка программного пакета (требуется разрешение администратора), альтернатива — yum install
cd /home Переход в домашний каталог
ls Список всех каталогов и файлов в текущем каталоге
ifconfig Просмотр информации о сетевом интерфейсе, включая IP-адрес (в Windows — ipconfig)
tar -zxvf bin.tar.gz Распаковка файла в текущий каталог
tar -jxvf bin.tar.xz Распаковка файла в текущий каталог
tar -zxvf bin.tar.gz -C /home Распаковка файла в каталог /home (помните, что C пишется заглавными буквами)
tar -zcvf bin.tar.gz bin Сжатие каталога bin в файл tar.gz (сжатие обычно)
tar -jcvf bin.tar.xz bin Сжатие каталога bin в файл tar.xz (высокое сжатие, рекомендуется)
tar -... j z обозначает разные методы сжатия, x — распаковка, c — сжатие
gedit 1.txt Открытие текстового файла с помощью блокнота
vim 1.txt Открытие файла с помощью vim, часто сокращается до vi
./configure make -j4 make install Общая команда компиляции исходного кода, первый шаг — выполнение скрипта конфигурации, второй шаг — многопоточная компиляция, третий шаг — установка скомпилированных файлов
./configure -prefix /home/liu/Qt-5.9.3-static -static -sql-sqlite -qt-zlib -qt-xcb -qt-libpng -qt-libjpeg -fontconfig -system-freetype -iconv -nomake tests -nomake examples -skip qt3d -skip qtdoc Общая команда компиляции Qt
./configure -static -release -fontconfig -system-freetype -qt-xcb -qt-sql-sqlite -qt-zlib -qt-libpng -qt-libjpeg -nomake tests -nomake examples -prefix /home/liu/qt/Qt5.6.3 Компиляция Qt со статическими библиотеками и поддержкой китайского языка
./configure -prefix /home/liu/Qt-5.9.3-static -static -release -nomake examples -nomake tests -skip qt3d Упрощенная команда компиляции
./configure --prefix=host --enable-static --disable-shared --disable-doc Команда компиляции ffmpeg
  1. Встроенная система перенаправления журналов Qt очень проста в использовании, и после ее использования отладка с использованием точек останова больше не требуется. Она поддерживает вывод соответствующих сообщений с помощью qdebug, и вы также можете включить просмотр журнала отладки после выпуска программы.
// Qt5 начал предоставлять контекстную информацию для вывода сообщений журнала, такую как текущий файл кода, номер строки и имя функции, связанные с напечатанным сообщением.
// Для вывода контекста в режиме release необходимо добавить DEFINES += QT_MESSAGELOGCONTEXT в pro-файл, который по умолчанию отключен в release.
// Помните, что не следует использовать qdebug и подобные команды в функциях логгирования, так как это приведет к бесконечной рекурсии.
// Обычно существует три способа перенаправления журнала:
// 1: Запись в файл журнала, такой как txt.
// 2: Сохранение в базу данных для последующей классификации и анализа.
// 3: Перенаправление в сеть, чтобы все печатные сообщения передавались через tcp при подключении к программе с помощью небольшого инструмента.

// Перенаправление журнала
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
void Log(QtMsgType type, const QMessageLogContext &context, const QString &msg)
#else
void Log(QtMsgType type, const char *msg)
#endif
{
    // Добавляем блокировку, чтобы предотвратить сбой из-за слишком частых вызовов qdebug в многопоточном режиме
    static QMutex mutex;
    QMutexLocker locker(&mutex);
    QString content;

    // Здесь можно добавить разные заголовки в зависимости от типа
    switch (type) {
        case QtDebugMsg:
            content = QString("%1").arg(msg);
            break;

        case QtWarningMsg:
            content = QString("%1").arg(msg);
            break;

        case QtCriticalMsg:
            content = QString("%1").arg(msg);
            break;

        case QtFatalMsg:
            content = QString("%1").arg(msg);
            break;
    }

    // Добавляем информацию о файле кода, номере строки и имени функции
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0)) ```cpp
//将内容传给函数进行处理
SaveLog::Instance()->save(content);
}

//安装日志钩子,输出调试信息到文件,便于调ровки
void SaveLog::start()
{
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
    qInstallMessageHandler(Log);
#else
    qInstallMsgHandler(Log);
#endif
}

//卸载日志钩子
void SaveLog::stop()
{
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
    qInstallMessageHandler(0);
#else
    qInstallMsgHandler(0);
#endif
}
  1. С момента стандарта C++11 синтаксический сахар появляется в изобилии, среди которых лямбда-выражения используются наиболее широко. Они поддерживаются, по существу, начиная с Qt5. Для программистов, привыкших к C99, это нечто новое, поэтому здесь специально сделаны небольшие заметки для понимания.
  • Формат кода: capture mutable ->return-type {statement}
  • [capture]: список захвата, список захвата всегда появляется в начале лямбда-функции. На самом деле, [] является символом начала лямбда-функции, и компилятор определяет, является ли следующий код лямбда-функцией, в соответствии с этим символом. Список захвата может захватывать переменные из контекста для использования в лямбда-функции.
  • (parameters): список параметров, совпадает со списком параметров обычной функции. Если передача параметров не требуется, можно опустить скобки вместе с ними.
  • mutable: модификатор mutable, по умолчанию лямбда-функция является константной функцией, а mutable может отменить её константность. При использовании этого модификатора список параметров нельзя опускать (даже если параметры пусты).
  • ->return-type: возвращаемый тип, используется для объявления возвращаемого типа функции в форме отслеживания возвращаемого типа. Мы можем опустить эту часть, даже если возвращаемое значение не требуется. Кроме того, если возвращаемый тип ясен, эту часть также можно опустить, позволяя компилятору вывести возвращаемый тип.
  • {statement}: тело функции, содержимое совпадает с содержимым обычной функции, но кроме использования параметров, можно использовать все захваченные переменные.

Список захвата имеет следующие формы:

  • [var] представляет передачу значения переменной var.
  • [=] представляет передачу значений всех переменных в родительской области видимости (включая this).
  • [&var] представляет захват переменной var по ссылке.
  • [&] представляет захват всех переменных в родительской области видимости по ссылке (включая this).
  • [this] представляет передачу значения текущего указателя this.
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    //按钮单击不带参数
    connect(ui->pushButton, &QPushButton::clicked, [] {
        qDebug() << "hello lambda";
    });

    //按钮单击带参数
    connect(ui->pushButton, &QPushButton::clicked, [] (bool isCheck) {
        qDebug() << "hello lambda" << isCheck;
    });

    //自定义信号带参数
    connect(this, &MainWindow::sig_test, [] (int i, int j) {
        qDebug() << "hello lambda" << i << j;
    });

    emit sig_test(5, 8);
}
  1. Из-за большого количества версий Qt иногда требуется проверка версии Qt для обеспечения совместимости между несколькими версиями или даже между Qt4/Qt5/Qt6. Некоторые заголовочные файлы или имена классов были изменены или добавлены, поэтому требуется проверка версии Qt. Следует отметить, что если вы используете QT_VERSION_CHECK в заголовочном файле, вам необходимо сначала включить #include "qglobal.h", иначе компиляция завершится неудачно, потому что функция QT_VERSION_CHECK определена в файле заголовка qglobal.h.
//по крайней мере нужно включить qglobal.h, теоретически все классы Qt включают этот заголовочный файл, так что вы можете включить другие заголовки Qt, например, qobject.h
#include "qglobal.h"
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
#include "qscreen.h"
#else
#include "qdesktopwidget.h"
#endif
  1. При преобразовании QString в char * или const char *, пожалуйста, помните о двух шагах. Это урок, полученный кровью. В одной ситуации из-за отсутствия разделения на два шага, msvc в режиме отладки выдавал исключения, а в выпуске работал нормально, в то время как mingw и gcc работали нормально как в отладке, так и в релизе. Это было очень непонятно, потребовалось много времени на поиск проблемы, и методом исключения была найдена причина.
  • Преобразование QString не зависит от того, содержит ли она китайские или английские символы.
  • Во время преобразования QByteArray не имеет значения, какой конкретный тип используется, будь то toUtf8, toLatin1, toLocal8Bit, toStdString и т.д.
  • После преобразования не имеет значения, является ли результат char * или const char *.
  • Проблема возникает случайно, вероятность возникновения выше в режиме отладки.
  • Согласно анализу coolcode, возможной причиной (неопределённой) является то, что msvc выполняет заполнение после освобождения памяти в режиме отладки, а не в release.
  • В целом, рекомендуется разделить процесс на два этапа. Если вы передаёте параметры, вы можете просто передать их в качестве аргументов.
QString text = "xxxxx";
//при таком преобразовании могут возникнуть проблемы
char *data = text.toUtf8().data();
//преобразование в два этапа точно не вызовет проблем
QByteArray buffer = text.toUtf8();
char *data = buffer.data();
const char *data = buffer.constData();

void test(const char *text) {}
//разделение на два этапа гарантирует отсутствие проблем
QByteArray buffer = QString("xxx").toUtf8();
const char *text = buffer.constData();
test(text);
//можно напрямую передать в качестве аргумента
test(QString("xxx").toUtf8().constData());
  1. Что касается использования QList или QVector, то это вопрос выбора многих разработчиков Qt. Основная причина заключается в том, что эти два инструмента предоставляют практически одинаковые интерфейсные функции, такие как вставка, удаление и получение значений.
  • В большинстве случаев можно использовать QList. Операции, такие как append, prepend и insert, обычно выполняются быстрее в QList.
  • QList хранит свои элементы в памяти на основе индексных меток, что быстрее, чем итеративное повторение на основе итераторов, и ваш код будет короче.
  • Если вам нужен настоящий связанный список и вы хотите гарантировать фиксированное время вставки, используйте итераторы вместо меток. Используйте QLinkedList().
  • Если вам нужно выделить непрерывное пространство памяти для хранения или ваши элементы намного больше, чем указатель, избегайте отдельных операций вставки, чтобы предотвратить переполнение стека. В этом случае используйте QVector.
  • Если вас больше интересует скорость получения данных, используйте QVector. QCustomPlot использует QVector для частого извлечения большого объёма данных для построения графиков.
  • Если вас больше волнует скорость обновления данных (добавление, удаление и т. д.), используйте QList (соответствующая операция — []=значение), но поскольку QChart в основном использует QList для доступа к данным (соответствующая операция — at()), это также является одной из причин зависания при работе с большими объёмами данных.
  • Кривые и диаграммы в основном тратят большую часть времени на доступ к данным и их визуализацию после настройки.
  • Короче говоря: QList быстрее обновляет данные (вставка, добавление и т. д.), а QVector быстрее получает данные.
  • Когда объём данных невелик, разница в производительности между двумя вариантами незначительна.
  • Похоже, что в Qt6 эти два класса были объединены (Qter, страдающий от синдрома выбора, был освобождён), QVector=QList, QVector является псевдонимом QList, возможно, базовый код был изменён, чтобы использовать преимущества обоих.
  1. Несколько официальных замечаний о mouseTracking (отслеживание мыши) и tabletTracking (отслеживание планшета).
  • Свойство mouseTracking используется для сохранения состояния включения отслеживания мыши. По умолчанию отслеживание мыши отключено.

  • Если отслеживание мыши отключено, соответствующий компонент будет получать только события перемещения мыши, когда нажата хотя бы одна кнопка мыши.

  • При включённом отслеживании мыши любой компонент будет получать события перемещения мыши. Метод hasMouseTracking() используется для возврата информации о том, включена ли в данный момент функция отслеживания мыши.

  • Метод setMouseTracking(bool enable) служит для установки состояния функции отслеживания мыши (включена или выключена).

  • Основная функция, связанная с отслеживанием мыши — mouseMoveEvent().

  • Свойство tabletTracking сохраняет информацию о том, включено ли отслеживание планшета для компонента. По умолчанию это свойство не используется.

  • Если отслеживание планшета отключено, компонент будет получать события перемещения пера только при касании экрана пером или при нажатии хотя бы одной кнопки на пере.

  • При включённом отслеживании планшета компонент может получать события перемещения пера, когда перо находится рядом с экраном, но ещё не касается его.

  • Это можно использовать для отслеживания позиции пера и вспомогательных функций компонентов (таких как вращение и наклон), а также для предоставления информации об этих операциях графическому интерфейсу.

  • Метод hasTabletTracking() возвращает информацию о текущем состоянии отслеживания планшета.

  • Метод setTabletTracking(bool enable) используется для включения или выключения отслеживания планшета.

  • Основной функцией, связанной с отслеживанием планшета, является tabletEvent().

  1. Что касается таких элементов управления, как QTableWidget, при вызове встроенных функций removeRow, clearContents и clear, которые удаляют элементы и содержимое внутри, автоматически вызываются деструкторы item или cellwidget для освобождения ресурсов, поэтому нет необходимости освобождать их вручную.
// Каждый раз, когда вы вызываете clearContents, предыдущие item будут автоматически очищены
ui->tableWidget->clearContents();
for (int i = 0; i < count; ++i) {
    ui->tableWidget->setItem(i, 0, new QTableWidgetItem("aaa"));
    ui->tableWidget->setItem(i, 1, new QTableWidgetItem("bbb"));
    ui->tableWidget->setCellWidget(i, 2, new QPushButton("ccc"));
}
  1. Для таких типов элементов управления, как QListView (QListWidget), QTreeView (QTreeWidget) и QTableView (QTableWidget), можно использовать setChecked, чтобы соответствующие элементы отображали эффект флажка. Многие люди (включая меня в прошлом) ошибочно полагают, что это элементы управления флажками. На самом деле это индикаторы соответствующих элементов управления. Поэтому, если вы хотите изменить стиль, нельзя просто установить стиль QCheckBox и ожидать, что он сработает. Вместо этого вам нужно отдельно настроить стиль индикатора.
QCheckBox::indicator,QGroupBox::indicator,QTreeWidget::indicator,QListWidget::indicator{
width:13px;
height:13px;
}

QCheckBox::indicator:unchecked,QGroupBox::indicator:unchecked,QTreeWidget::indicator:unchecked,QListWidget::indicator:unchecked{
image:url(:/qss/flatwhite/checkbox_unchecked.png);
}

...
  1. Что касается QTableView (использующего модель данных) и настройки ширины столбцов и названий столбцов в QTableWidget, иногда обнаруживается, что они не работают. Оказывается, порядок установки кода имеет значение. Например, перед установкой ширины столбца необходимо сначала установить количество столбцов (setColumnWidth), иначе столбцов не будет, и ширина столбца не будет иметь смысла. То же самое относится и к установке заголовков столбцов (setHorizontalHeaderLabels). Сначала должно быть указано количество столбцов.
void frmSimple::initForm()
{
    // Создание экземпляра модели данных
    model = new QStandardItemModel(this);

    // Установка количества строк и столбцов
    row = 100;
    column = 10;
    // Настройка названий столбцов и ширины столбцов
    for (int i = 0; i < column; ++i) {
        columnNames << QString("Столбец%1").arg(i + 1);
        columnWidths << 60;
    }
}

void frmSimple::on_btnLoad1_clicked()
{
    // Сначала устанавливаем модель данных, иначе setColumnWidth не работает
    ui->tableView->setModel(model);

    // Устанавливаем количество столбцов, заголовки столбцов и ширину столбцов
    model->setColumnCount(column);
    // Простой способ установки заголовков столбцов
    model->setHorizontalHeaderLabels(columnNames);
    for (int i = 0; i < column; ++i) {
        ui->tableView->setColumnWidth(i, columnWidths.at(i));
    }

    // Цикл добавления данных строк
    QDateTime now = QDateTime::currentDateTime();
    model->setRowCount(row);
    for (int i = 0; i < row; ++i) {
        for (int j = 0; j < column; ++j) {
            QStandardItem *item = new QStandardItem;
            // В последнем столбце отображается разница во времени
            if (j == column - 1) {
                item->setText(now.addSecs(i).toString("yyyy-MM-dd HH:mm:ss"));
            } else {
                item->setText(QString("%1_%2").arg(i + 1).arg(j + 1));
            }
            model->setItem(i, j, item);
        }
    }
}

void frmSimple::on_btnLoad2_clicked()
{
    // Установка заголовков столбцов, количества столбцов и ширины столбцов
    ui->tableWidget->setColumnCount(column);
    // Простой способ установки заголовков столбцов
    ui->tableWidget->setHorizontalHeaderLabels(columnNames);
    for (int i = 0; i < column; ++i) {
        ui->tableWidget->setColumnWidth(i, columnWidths.at(i));
    }

    // Добавление данных
    QDateTime now =
``` ```
QDateTime::currentDateTime();
ui->tableWidget->setRowCount(row);
for (int i = 0; i < row; ++i) {
    for (int j = 0; j < column; ++j) {
        QTableWidgetItem *item = new QTableWidgetItem;
        //最后一列显示时间区别开来
        if (j == column - 1) {
            item->setText(now.addSecs(i).toString("yyyy-MM-dd HH:mm:ss"));
        } else {
            item->setText(QString("%1_%2").arg(i + 1).arg(j + 1));
        }
        ui->tableWidget->setItem(i, j, item);
    }
}
}

20:191-200

  1. При обработке очередей QList мы чаще всего используем функцию append для добавления элемента, многие люди сначала думают о вставке элемента с помощью insert(0,xxx), чтобы вставить его вперёд. На самом деле, QList предоставляет функции для вставки элементов вперёд — prepend и push_front.
QStringList list;
list << "aaa" << "bbb" << "ccc";

// Добавление элемента в конец списка эквивалентно append
list.push_back("ddd");
// Добавление элемента в начало списка эквивалентно prepend
list.push_front("xxx");

// Добавление элемента в конец
list.append("ddd");
// Добавление элемента в начало
list.prepend("xxx");

// Вставка элемента на первую позицию эквивалентно prepend
list.insert(0, "xxx");

// Вывод QList("xxx", "aaa", "bbb", "ccc", "ddd")
qDebug() << list;
  1. Qt включает несколько типов, связанных с QList, QMap и QHash, которые можно использовать напрямую, без необходимости создавать собственные длинные типы.
//qwindowdefs.h
typedef QList<QWidget *> QWidgetList;
typedef QList<QWindow *> QWindowList;
typedef QHash<WId, QWidget *> QWidgetMapper;
typedef QSet<QWidget *> QWidgetSet;

//qmetatype.h
typedef QList<QVariant> QVariantList;
typedef QMap<QString, QVariant> QVariantMap;
typedef QHash<QString, QVariant> QVariantHash;
typedef QList<QByteArray> QByteArrayList;
  1. Поля и отступы в макетах Qt определяются автоматически в зависимости от разрешения экрана и коэффициента масштабирования системы. Например, при разрешении 1080P поля составляют 9 пикселей, а при 2K — 11 пикселей. Если вы скомпилировали программу на компьютере с разрешением 1080P и видите поля размером 6 или 9 пикселей, то при переходе на 2K или 4K разрешение они станут больше. Чтобы сохранить одинаковый вид приложения на всех разрешениях, необходимо вручную настроить эти значения. Здесь есть нюанс: если вы хотите оставить значение 9 для всех разрешений, нужно сначала изменить его на другое число, например, 10, а затем вернуть обратно к 9. Только так система поймёт, что вы действительно внесли изменения. В дизайнере свойств справа от значения есть маленькая серая стрелка для восстановления значений по умолчанию.

  2. Поддержка высоких разрешений и масштабирования DPI в Qt становится всё более зрелой. Начиная с версии Qt5.6, можно включить поддержку высоких разрешений с помощью свойства AA_EnableHighDpiScaling. С версии 5.14 появилась возможность задавать стратегии масштабирования, такие как поддержка масштабирования с использованием дробных коэффициентов вместо целых чисел. В версии Qt6 это свойство включено по умолчанию и не может быть отключено. Часто нам нужны два режима: один, где высокие разрешения и масштабирование никогда не применяются, и другой, где они применяются автоматически.

// Режим, в котором высокие разрешения и масштабирование никогда не применяются
int main(int argc, char *argv[])
{
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
    QApplication::setAttribute(Qt::AA_Use96Dpi);
#endif
#if (QT_VERSION >= QT_VERSION_CHECK(5,14,0))
    QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Floor);
#endif

    QApplication a(argc, argv);
    ....
    return a.exec();
}

// Автоматический режим применения высоких разрешений и масштабирования
// Существует множество методов, но наиболее подходящим является использование файла конфигурации для задания стратегии масштабирования.
// Создайте файл qt.conf в том же каталоге, что и исполняемый файл
[Platforms]
WindowsArguments = dpiawareness=0

// Иногда требуется позволить пользователю выбирать стратегию. После включения поддержки высоких разрешений просто поместите файл qt.conf в тот же каталог, что и исполняемый файл. Даже если в коде установлено отключение поддержки высоких разрешений, это не повлияет. Стратегия из файла qt.conf будет иметь приоритет.
  1. Несколько моментов, на которые следует обратить внимание при работе с QSS:
  • QSS основан на CSS и поддерживает стандарт CSS2. Многие CSS3-правила, доступные онлайн, не работают в QSS. Не удивляйтесь этому.
  • QSS не поддерживает все возможности CSS2. Например, text-align официально поддерживается только для QPushButton и QProgressBar, поэтому важно проверять документацию.
  • Иногда, используя *{xxx}, вы можете обнаружить, что большинство стилей применяются, но некоторые — нет. Возможно, вам потребуется установить стиль в соответствующем окне с помощью this->setStyleSheet().
  • Стили QSS имеют приоритет. Если не указан родительский объект, стиль применяется ко всему. Например, {color:#ff0000;} в виджете применит этот стиль ко всем дочерним объектам виджета. Эта проблема возникает у разработчиков каждую неделю. Вы можете столкнуться с различными странностями и несоответствиями. Решение — указать класс или имя объекта. Например, #widget{color:#ff0000;} применит стиль только к объекту widget. Другой способ — QWidget#widget{color:#ff0000;}. Это применит стиль только к самому окну, а не к дочерним элементам, таким как кнопки или метки. Подробные правила см. в официальной документации.
  • В целом, QSS работает хорошо. Скорость разбора и производительность значительно улучшились в последних версиях Qt5 по сравнению с Qt4, особенно после исправления многих ошибок в QSS. Несмотря на наличие некоторых ошибок, следует относиться к ним с пониманием.
  • Официальные ресурсы для изучения QSS: http://47.100.39.100/qtwidgets/stylesheet-reference.html и http://47.100.39.100/qtwidgets/stylesheet-examples.html.
  1. Несколько способов реализации задержек в Qt:
void QtHelperCore::sleep(int msec)
{
    if (msec <= 0) {
        return;
    }

#if 1
    // Неблокирующий метод задержки, рекомендуемый многими
    QEventLoop loop;
    QTimer::singleShot(msec, &loop, SLOT(quit()));
    loop.exec();
#else
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
    // Блокирующий метод задержки, может заморозить главный интерфейс, если используется в главном потоке
    QThread::msleep(msec);
#else
    // Неблокирующий метод задержки, не замораживает главный интерфейс
    QTime endTime = QTime::currentTime().addMSecs(msec);
    while (QTime::currentTime() < endTime) {
        QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
    }
#endif
#endif
}
  1. В связи с ростом популярности отечественных разработок, различные отечественные системы и базы данных постепенно внедряются в мир разработчиков. Вот несколько ключевых моментов:
  • Система «Золотой Барс» Neokylin основана на CentOS. Перевод на русский язык:

«Серебряная Галактика» Kylin ранних версий, например, V2, основана на FreeBSD, новые версии V4 и V10 — на Ubuntu.

  • Ubuntu Kylin — это локализованная версия Ubuntu, в которую добавлены некоторые элементы лунного календаря и т. д.
  • Deepin основан на Debian.
  • UOS основан на deepin или, можно сказать, является коммерческой ветвью deepin.
  • Ubuntu основан на Debian.
  • В мире Linux в основном есть два типа дистрибутивов: Debian (Ubuntu, Deepin, UOS, «Серебряная Галактика» Kylin и др.) и Redhat (Fedora, CentOS, «Чжонбай» Kylin neokylin, «Чжунсин» newstart и др.), которые соответствуют командам установки apt-get и yum соответственно. Подавляющее большинство систем Linux основаны на этих двух дистрибутивах или являются их производными.
  • Теоретически программы, скомпилированные для одной системы на основе одного и того же ядра, могут быть перенесены на другую систему, если компиляторы совпадают, например, оба используют gcc4.9. Программа Qt, скомпилированная на 64-разрядной версии Ubuntu 14.04 с помощью gcc4.9, может работать на 64-битной версии UOS.
  • Системы с более новыми версиями компиляторов обычно совместимы со старыми версиями, например, программа, скомпилированная с помощью gcc4.9, будет работать на gcc7.0, но не наоборот.
  • Это означает, что если вы хотите обеспечить совместимость с большим количеством систем, лучше всего использовать компилятор более старой версии для компиляции вашей программы, конечно, при условии, что ваш код поддерживает синтаксис, например, C++11 должен поддерживаться как минимум начиная с gcc4.7. Если ваш код использует C++11, вы должны выбрать версию компилятора не ниже gcc4.7.
  • Для написания программ для Linux с использованием Qt, чтобы исполняемый файл был совместим с различными дистрибутивами Linux, достаточно скомпилировать программу Qt с помощью компилятора более старой версии, например gcc4.7, на системах с ядрами Debian и Redhat.
  • Дополнение от 27 января 2022 года: согласно официальному пакету установки Qt (относительно диалогового окна), обнаружено, что он скомпилирован с использованием компилятора Redhat и gcc4.9 (Qt5.14/5.15 использует gcc5.3 в qtc), и может работать на всех дистрибутивах Linux (лично проверено на различных версиях Ubuntu, Fedora, CentOS, Deepin, UOS, «Серебряной Галактике» Kylin, «Чжонбае» Kylin neokylin, «Чжунсине» newstart и др.). Я также попробовал упаковать и опубликовать его в соответствии с этой версией, и он оказался пригодным для использования. Удивительно, но система Redhat также может работать на Debian.
  • Дополнение от 10 февраля 2022 года: программа, статически скомпилированная в Debian, также может работать в системе Redhat, возможно, статическая компиляция удаляет многие зависимости.
  • Дополнение от 1 марта 2022 года: дополнение от старшего по званию. Если нет специфических зависимостей, программы, скомпилированные с помощью более новых версий компиляторов, также могут работать на системах со старыми компиляторами, например, исполняемый двоичный файл, созданный с помощью gcc11/clang13 в Alpine Linux, всё ещё может работать на CentOS 5/Ubuntu 10. Проблема не в версии компилятора или функции C++11, она слишком сложна и включает в себя множество аспектов, таких как версия ядра, GNU libc, ABI-совместимость и так далее.
  • Согласно правилам среды компилятора, используемым программным обеспечением QtCreator, старые версии обычно могут работать на новых, например, Qt5 может работать на Ubuntu 14/16/18/20, но программы, скомпилированные новым компилятором, не могут работать на старых системах компилятора и будут выдавать сообщения об ошибках, таких как отсутствие GLBC, LIBCXX, symbol xxxxxx и т.д. Например, Qt6 может работать на Ubuntu 20, но не на Ubuntu 18/16/14 и т.д.
  • При разработке на UOS рекомендуется использовать среду Qt, предоставляемую системой, а также устанавливать среду разработки через командную строку. Не рекомендуется использовать официальный пакет установки Qt для создания среды, потому что Qt в UOS модифицирована, и программы, разработанные в среде, созданной с помощью официального пакета установки, могут иметь проблемы с зависимостями при упаковке и публикации. Использование системной версии Qt может избежать этой проблемы.
  • База данных «Золотая кладовая» Народного банка Китая использует модифицированную базу данных PostgreSQL, это означает, что вы можете подключиться к базе данных Народного банка Китая, используя плагин PostgreSQL в Qt.
  • Вышеизложенное не обязательно полностью верно, пожалуйста, поправьте меня, если это не так.
  1. Рассматривая историю развития Qt, мы видим почти непрерывный процесс разделения и объединения логики. Например, изначально UI-элементы управления, такие как QPushButton, находились в модуле QtGui. Позже, когда они стали неудобными в управлении и обновлении, был выделен отдельный модуль QtWidgets. В Qt6 классы QList и QVector были объединены в один класс, создавая впечатление воссоединения разделённого. Кроме того, некоторые математические функции и методы стандартной библиотеки C++ постепенно заменяются стандартными функциями C++, начиная с разделения и заканчивая унификацией.

  2. Qt постоянно обновляется и совершенствуется, хотя качество нового кода явно уступает эпохе Nokia, но, по крайней мере, есть прогресс. Основные улучшения в настоящее время сосредоточены в модуле QML, а также в базовой части, поскольку как виджеты, так и QML используют общую базовую логику. Основа должна быть прочной и надёжной. За последние несколько лет я сравнивал производительность различных классов и функций в разных версиях Qt (от старых до новых) и обнаружил, что официальные веб-сайты предоставляют информацию о повышении производительности соответствующих классов и методов в новых версиях, что действительно соответствует действительности. Что касается того, насколько именно улучшилась производительность, я не могу точно сказать.

  • Производительность алгоритма base64 значительно улучшилась.
  • Значительно улучшилась производительность классов, связанных с QList.
  • Сравнительный тест показывает, что производительность QStringList и QMap примерно одинакова, начиная с версии 5.12.
  • В ранних версиях QStringList поиск значения занимал меньше времени, если значение было вставлено перед поиском, в то время как QMap не имел такого различия.
QStringList list1, list2;
QMap<QString, QString> map;

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    for (int i = 0; i < 100000; ++i) {
        QString s1 = QString("%1").arg(i);
        QString s2 = QString("A%1").arg(i);
        list1 << s1;
        list2 << s2;
        map.insert(s1, s2);
    }
}

void MainWindow::on_pushButton_clicked()
{
    QElapsedTimer time;
    time.start();
    qDebug() << "111" << time.nsecsElapsed() << list2.at(list1.indexOf("9999"));
}

void MainWindow::on_pushButton_2_clicked()
{
    QElapsedTimer time;
    time.start();
    qDebug() << "222" << time.nsecsElapsed() << map.value("9999");
}
  1. Различия между тремя версиями сборки в QtCreator: debug, release и profile.
  • Debug-версия предназначена для отладки и содержит много информации для разработчиков, такой как символы отладки. Она полезна для детального изучения стека вызовов во время отладки, но имеет низкую производительность и может вызывать задержки во время выполнения.
  • Release-версия создана для распространения и не содержит символов отладки. Она оптимизирована для повышения производительности и скрывает все утверждения. Если программа работает медленно или зависает, проблема, скорее всего, связана с самой программой.
  • Profile-версия представляет собой компромисс между отладочной и релизной версиями. Она содержит часть информации для отладки и обеспечивает лучшую производительность по сравнению с отладочной версией.
  • Размеры пустых окон, созданных с использованием версии Qt 5.7 для трёх типов сборок: debug (1319 КБ), release (24 КБ) и profile (90 КБ).
  • Библиотеки, связанные с debug-версией, имеют расширение .d, в то время как библиотеки, связанные с release и profile версиями, не имеют этого расширения. Многие ошибочно полагают, что библиотеки profile-версии имеют расширение .d.
  • Новое онлайн-приложение для установки Qt позволяет выбирать, устанавливать ли библиотеки для отладки (файлы с расширением .d в каталоге lib), тогда как в предыдущих версиях они устанавливались по умолчанию. Теперь пользователь может отказаться от их установки для уменьшения размера.
  • Независимо от того, установлены ли библиотеки для отладки или нет, вы можете выбрать режим отладки для генерации файлов, соответствующих режиму отладки.
  • Вне зависимости от выбранного режима, вы можете включить логирование в программе для вывода информации в лог, что удобно для сбора информации о работе программы и передачи её разработчикам для анализа проблем.
  • Изначально инструменты разработки обычно имели только режимы отладки и выпуска. Однако с ростом потребностей пользователей и сценариев использования некоторые инструменты разработки породили profile-режим, а другие, такие как flutter, даже четвёртый test-режим.

21: 201–210

  1. Компиляция динамической библиотеки в режиме отладки автоматически добавляет расширение .d к имени файла.
CONFIG(debug, debug|release) {
    win32:      TARGET = $$join(TARGET,,,d)
    mac:        TARGET = $$join(TARGET,,,_debug)
    unix:!mac:  TARGET = $$join(TARGET,,,d)
}

#判断当前套件是debug还是release
CONFIG(debug, debug|release) {}
CONFIG(release, debug|release) {}
  1. Описание формата файлов проекта pro в QtCreator.
Наименование Описание
QT += core gui Добавляет необходимые модули в проект, влияет на автоматическое появление выпадающего списка при включении файлов в код. Если модуль не добавлен в файл pro, то автодополнение не будет работать. Обычно используется при создании пакетов для выпуска, соответствующие динамическим библиотекам, таким как Qt5Core.dll.
TARGET = xxx Определяет имя конечного целевого файла, который может быть исполняемым файлом или библиотекой.
background:#FF0000;
}

QSlider::add-page:vertical{ width:8px; background:#00FF00; }

QSlider::sub-page:vertical{ width:8px; background:#FF0000; }

QSlider::handle:vertical{ height:10px; background:#0000FF; }

### 22:211-220
211. При упорядочивании закреплённых окон некоторые редко используемые настройки QMainWindow легко забыть, поэтому рекомендуется просмотреть функции заголовка файла QMainWindow. Для подробного ознакомления со всеми параметрами закрепления см. статью [https://zhuanlan.zhihu.com/p/388544168](https://zhuanlan.zhihu.com/p/388544168).
```cpp
// Устанавливаем гибкие параметры для различных типов вложенности, например, вертикального и горизонтального расположения
// Эти настройки переопределяют параметры, заданные в setDockOptions ниже, поэтому важно соблюдать порядок
//this->setDockNestingEnabled(true);

// Задаем параметры закрепления, запрещаем перекрытие, разрешаем только перетаскивание и вложение
this->setDockOptions(AnimatedDocks | AllowNestedDocks);

// Определяем нижнюю левую область как область слева, нижнюю правую область как область справа, иначе нижняя область будет заполнена растягиванием
this->setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
this->setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
  1. Когда мы извлекаем данные из QModelIndex, обычные данные роли (поддерживаемые типом QVariant, такие как toString, toInt, toDouble и т.д.) можно легко получить, а специфические типы данных требуют использования универсальной функции получения значения шаблона T value().
// Отображение текста
QString text = index.data(Qt::DisplayRole).toString();
// Выравнивание текста
int align = index.data(Qt::TextAlignmentRole).toInt();
// Шрифт текста
QFont font = index.data(Qt::FontRole).value<QFont>();
// Цвет переднего плана
QColor color = index.data(Qt::ForegroundRole).value<QColor>();
// Цвет фона
QColor color = index.data(Qt::BackgroundRole).value<QColor>();
  1. Многие думают, что перетаскивания достаточно в событии dropEvent, но это не так, это неэффективно, необходимо сначала выполнить event->accept() в событии dragEnterEvent, иначе эффекта не будет. Многие, особенно новички, застревают здесь, я тоже споткнулся здесь, было очень больно!
void frmMain::dropEvent(QDropEvent *event)
{
    QList<QUrl> urls = event->mimeData()->urls();
}

void frmMain::dragEnterEvent(QDragEnterEvent *event)
{
    if(event->mimeData()->hasFormat("application/x-qabstractitemmodeldatalist")) {
        event->setDropAction(Qt::MoveAction);
        event->accept();
    } else {
        event->ignore();
    }
}
  1. Начиная с версии Qt5.6, встроенный браузер основан на движке webengine, и если требуется веб-интерактивность, то необходимо использовать файл qwebchannel.js, предоставленный Qt. Этот файл не рекомендуется изменять, поскольку поддержка webengine постоянно обновляется, и соответствующие файлы qwebchannel.js для каждой версии Qt различаются. Это означает, что вы должны использовать соответствующий файл qwebchannel.js для вашей версии Qt, который по умолчанию находится в каталоге C:\Qt\Qt5.12.11\Examples\Qt-5.12.11\webchannel\shared. После тестирования нескольких версий Qt было обнаружено, что использование более новой версии qwebchannel.js в более старой версии не работает, но более старая версия в более новой работает. Поэтому рекомендуется использовать соответствующую версию.

  2. Для удаления пробелов из QString существует несколько сценариев, возможно, потребуется удалить пробелы из левой, правой или всех позиций.

// Удаление пробелов из строки -1=удаление пробелов слева 0=удаление всех пробелов 1=удаление пробелов справа 2=удаление начальных и конечных пробелов 3=удаление начальных и конечных, оставление одного пробела в середине
QString QtHelperData::trimmed(const QString &text, int type)
{
    QString temp = text;
    QString pattern;
    if (type == -1) {
        pattern = "^ +\\s*";
    } else if (type == 0) {
        pattern = "\\s";
        //temp.replace(" ", "");
    } else if (type == 1) {
        pattern = "\\s* +$";
    } else if (type == 2) {
        temp = temp.trimmed();
    } else if (type == 3) {
        temp = temp.simplified();
    }

    // Использование регулярного выражения для удаления пробелов
    if (!pattern.isEmpty()) {
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
        temp.remove(QRegularExpression(pattern));
#else
        temp.remove(QRegExp(pattern));
#endif
    }

    return temp;
}

// Тестовый код
QString text = "  a  b  c d  ";
// Результат: a  b  c d
QtHelper::trimmed(text, -1);
// Результат: abcd
QtHelper::trimmed(text, 0);
// Результат:  a  b  c d
QtHelper::trimmed(text, 1);
// Результат: a  b  c d
QtHelper::trimmed(text, 2);
// Результат: a b c d
QtHelper::trimmed(text, 3);
  1. Сетевая библиотека Qt поддерживает широковещательный поиск UDP и многоадресный поиск, причем многоадресный поиск может выполнять поиск между подсетями. Иногда вы можете столкнуться с проблемами, в этом случае вы можете попробовать отключить виртуальную машину локального компьютера и прокси-серверы, если они у вас есть. Также были случаи, когда программы, использующие tcpsocket, работали в Qt4 и Qt5, но не работали в Qt6, выдавая ошибку «The proxy type is invalid for this operation». Оказалось, что проблема была вызвана настройками прокси-сервера на локальном компьютере, которые, вероятно, игнорировались в предыдущих версиях Qt.
// Также можно пропустить прокси-сервер через код
#include <QNetworkProxy>
QNetworkProxyFactory::setUseSystemConfiguration(false);
// Далее устанавливаем прокси-сервер
tcpSocket->setProxy(QNetworkProxy::NoProxy);

// Статья для чтения https://www.cnblogs.com/cppskill/p/11730452.html
// Начиная с 5.8, тип сокета по умолчанию — DefaultProxy, а не NoProxy, неизвестно почему.
  1. Что касается кросс-компиляции, то для новичков это сложная задача, которую трудно преодолеть. Однако, как только вы ее преодолеете, последующие задачи кросс-компиляции будут легкими. Вам нужно настроить среду кросс-компиляции, но к счастью, производители предоставляют готовые среды, включая компиляторы. Следуя официальным руководствам, вы сможете избежать большинства проблем.
  • Компиляция ffmpeg и qt в Linux системе довольно проста, даже для начинающих, при условии, что ваш локальный компилятор gcc g++ работает нормально.

  • Любой компилятор, включая встроенные, должен быть проверен на работоспособность, прежде чем использовать его. Например, g++ -v для проверки версии gcc и arm-linux-g++ -v для версии встроенного компилятора.

  • Чтобы проверить версию кросс-компилятора, используйте команду /opt/FriendlyARM/toolschain/4.5.1/bin/arm-linux-g++ -v.

  • Важно убедиться, что битность компилятора соответствует битности операционной системы. Обычно 32-битные компиляторы используются для кросс-компиляции на 32-битных системах, хотя 32-битные компиляторы могут использоваться на 64-битных системах после установки зависимостей, но это не рекомендуется, так как могут возникнуть проблемы. 64-битные компиляторы работают только на 64-битных системах.

  • Если вы установили переменные окружения, вы можете опустить длинные пути и просто ввести имя исполняемого файла. Если переменные окружения не установлены, вам придется вводить полный путь. Установка переменных окружения нужна только для того, чтобы компилятор автоматически нашёлся во время компиляции. На самом деле можно вообще не устанавливать переменные окружения, а указать расположение компилятора с помощью абсолютного пути.

  • Компиляция на Linux, будь то ffmpeg, qt или что-то ещё, всегда выполняется по общим шагам: первый шаг: ./configure, второй шаг: make, третий шаг: make install.

  • Что касается конкретных параметров после configure, обратитесь к руководству соответствующего исходного пакета. Поисковик выдаст множество результатов. Конечно, вы можете использовать значения по умолчанию и компилировать без каких-либо параметров. Компилятор автоматически примет параметры по умолчанию.

  • Команда кросс-компиляции ffmpeg: ./configure --prefix=host --enable-static --disable-shared --disable-doc --cross-prefix=/opt/FriendlyARM/toolschain/4.5.1/bin/arm-linux- --arch=arm --target-os=linux

  • Предварительные условия для кросс-компиляции qt: измените файл qmake.conf в папке mkspecs/qws/linux-arm-g++, если переменные окружения не установлены, задайте абсолютный путь к нужному компилятору и измените имя компилятора на нужное вам.

  • Например, изменение компилятора gcc: QMAKE_CC = /opt/FriendlyARM/toolschain/4.5.1/bin/arm-linux-gcc

  • Команда кросс-компиляции qt4.8.5: ./configure -prefix host -embedded arm -xplatform qws/linux-arm-g++ -release -opensource -confirm-license -qt-sql-sqlite -qt-gfx-linuxfb -plugin-sql-sqlit -no-qt3support -no-phonon -no-svg -no-webkit -no-javascript-jit -no-script -no-scripttools -no-declarative -no-declarative-debug -qt-zlib -no-gif -qt-libtiff -qt-libpng -no-libmng -qt-libjpeg -no-rpath -no-pch -no-3dnow -no-avx -no-neon -no-openssl -no-nis -no-cups -no-dbus -little-endian -qt-freetype -no-opengl -no-glib -nomake demos -nomake examples -nomake docs -nomake tools

  • Команда кросс-компиляции qt5.9.8: ./configure -prefix host -xplatform linux-arm-g++ -recheck-all -opensource -confirm-license -optimized-qmake -release -no-separate-debug-info -strip -shared -static -c++std c++1z -no-sse2 -pch -compile-examples -gui -widgets -no-dbus -no-openssl -no-cups -no-opengl -linuxfb -qt-zlib -qt-libpng -qt-libjpeg -qt-freetype

  • В целом, кросс-компиляция и обычная компиляция отличаются только тем, что нужно вручную указать путь к кросс-компилятору. Для ffmpeg это делается с помощью параметра --cross-prefix=, а для qt, который является более сложным, через изменение файла конфигурации и указание имени файла конфигурации с помощью параметра -xplatform.

  • Компиляция Qt6 довольно сложна, обычно используется cmake для компиляции. На Linux сначала используйте исходные коды cmake версии 3.19 и выше. Скомпилируйте и создайте cmake с помощью make, затем используйте cmake для компиляции qt и создания qmake, наконец, вызовите qmake для компиляции вашего проекта qt.

  • На самом деле, компиляция Qt нужна только для использования библиотек внутри него, поэтому нет необходимости использовать demo, doc, tool, example и т.д., что занимает много времени. Поэтому настоятельно рекомендуется удалять их при компиляции, что значительно ускоряет процесс компиляции.

  • Рекомендуется компилировать от имени обычного пользователя, включая распаковку исходных кодов, потому что библиотеки, скомпилированные таким образом, могут использоваться обычными пользователями. Если компиляция выполнена от имени root-администратора, то впоследствии потребуются права администратора.

  • Многие системы предоставляют возможность распаковки файлов с помощью контекстного меню правой кнопкой мыши, но это также возможно, хотя и медленнее. Рекомендуется использовать командную строку для распаковки и удаления каталогов.

  • Параметры компиляции Qt могут отличаться в каждой версии, поскольку код постоянно обновляется, и даже некоторые описания категорий могут измениться. Например, параметр -qt-xcb был заменён на -xcb в версии 5.15, а параметр -qt-sql-sqlite был заменён на -qt-sqlite. Обязательно прочитайте файл readme в исходном коде, где указаны минимальные требования к версии компиляции среды. После версии qt5 конкретные параметры конфигурации описаны в файле config в каталоге qtbase.

  • Если после компиляции появляется сообщение об ошибке GL/gl.h, необходимо установить пакеты apt install libgl1-mesa-dev libglu1-mesa-dev или yum install mesa-libGL-devel mesa-libGLU-devel.

  • Описание параметров компиляции можно найти по ссылке https://blog.csdn.net/xi_gua_gua/article/details/53413930.

В Qt иногда возникают проблемы с загрузкой изображений, которые могут быть вызваны неправильным расширением файла. Например, изображение с расширением jpg может иметь расширение png, а изображение bmp может иметь расширение jpg. QImage и QPixmap загружают изображения, используя расширение файла для вызова соответствующего алгоритма анализа изображения. Это простой способ, но быстрый, так как не требуется анализировать конкретный внутренний формат изображения. Если вы хотите гарантировать успешную загрузку независимо от расширения файла, вам нужно обработать загрузку, читая данные изображения напрямую.

// Можно использовать изображение из ресурса или локальный файл
QString fileName = ":/test.png";

// Этот метод использует расширение файла для определения формата,
// но если расширение неправильное, загрузка не удастся
ui->label->setPixmap(QPixmap(fileName));

// Загрузка с использованием данных изображения гарантирует успех
QFile file(fileName);
file.open(QIODevice::ReadOnly);
QByteArray data = file.readAll();

// Обработка с использованием QImage
QImage img;
img.loadFromData(data);
// Также можно использовать этот метод
// QImage img = QImage::fromData(data);
ui->label->setPixmap(QPixmap::fromImage(img));

// Обработка с использованием QPixmap
QPixmap pix;
pix.loadFromData(data);
ui->label->setPixmap(pix);

Несколько интересных фактов о версиях Qt:

  • Qt4.8.7 — последняя версия серии Qt4, которая считается самой стабильной и классической (многие встраиваемые платы всё ещё используют Qt4.8). Эта версия была выпущена примерно в то же время, что и Qt5.5.
  • Qt5.6.3 — последняя версия, поддерживающая систему XP на долгосрочной основе. Qt5.7.0 — последняя версия, не поддерживающая XP (возможно, небольшое количество функций не поддерживается, лично не сталкивался).
  • Qt5.12.3 — последняя версия, предоставляющая плагин для базы данных MySQL. В последующих версиях плагин нужно компилировать самостоятельно, официальные установочные пакеты больше не предоставляются.
  • Qt5.12.5 — последняя версия с максимальной производительностью таблиц стилей. После изучения кода было обнаружено, что в последующих версиях для исправления ошибки были внесены циклические вложенные настройки, что привело к резкому снижению производительности, особенно при большом количестве интерфейсов.
  • Qt5.14.2 — последняя версия, предлагающая двоичные установочные пакеты. Последующие версии требуют онлайн-установки.
  • Серии Qt5.15 — последние версии, поддерживающие Windows 7. Версии Qt6 требуют изменения исходного кода для поддержки Windows 7, что может быть сложно для начинающих разработчиков.
  • Версии Qt6.0/6.1 поддерживают Windows 7, но из-за отсутствия многих модулей и большого количества ошибок использование этих версий не имеет смысла.
  • Qt6 не поддерживает Windows 7 ни на этапе разработки, ни на этапе выполнения. Независимо от этапа, если библиотека Qt не поддерживается, она не будет работать нигде.
  • Новая версия qtc7 скомпилирована с использованием Qt6, поэтому она может работать только на Windows 10 и более поздних версиях. Это означает, что для разработки с использованием нового qtc7 и Qt5 необходимо использовать Windows 10 или более позднюю версию.
  • Приглашаем всех дополнить информацию, например, о том, какие версии требуют оплаты за коммерческое использование в будущем. Кажется, что использование Qt4 с динамической библиотекой и без изменения исходного кода Qt может снизить юридические риски. Такие ситуации, как правило, возникают из-за слишком большого различия версий, например, когда библиотека Qt 5.9 загружается с помощью qtc, поставляемого вместе с Qt 5.5, в результате чего некоторые среды не могут её распознать. Возможно, в новой версии qtc были изменены правила распознавания некоторых объектов. Поэтому обычно рекомендуется использовать новый qtc для загрузки старой библиотеки Qt, а не старый qtc для загрузки новой библиотеки Qt.

23: 221–230

  1. При работе с моделью данных таблицы часто возникает следующая ситуация: после удаления записи необходимо повторно выбрать определённую строку. QTableView и QTableWidget поддерживают операции множественного выбора и полного выбора, например, пакетное удаление может осуществляться путём множественного выбора.
// Получение модели данных таблицы
QAbstractItemModel *model = ui->tableView->model();
// Активная установка на третью строку
ui->tableView->setCurrentIndex(model->index(3, 0));
// Активная установка на последнюю строку
ui->tableView->setCurrentIndex(model->index(model->rowCount() - 1, 0));

// Установка режима выбора поддерживает множественный выбор, другие значения перечисления см. в документации.
ui->tableView->setSelectionMode(QAbstractItemView::MultiSelection);

// Выбор всего
ui->tableView->selectAll();
// Отмена всех выбранных элементов
ui->tableView->clearSelection();

// Выбор строки, обратите внимание, что если строка выбрана, то после выполнения она будет отменена, и так далее. Этот дизайн очень умный, аплодисменты.
ui->tableView->selectRow(row);
// Выбор столбца, обратите внимание, что если столбец выбран, то после выполнения он будет отменён, и так далее. Этот дизайн очень умный, аплодисменты.
ui->tableView->selectColumn(column);

// Получение содержимого выбранной строки
QItemSelectionModel *selections = ui->tableView->selectionModel();
QModelIndexList selected = selections->selectedIndexes();
foreach (QModelIndex index, selected) {
    qDebug() << index.row() << index.column() << index.data();   
}
  1. Иногда при чтении текстового файла можно обнаружить, что прочитанные китайские иероглифы искажены. В этом случае необходимо определить формат кодирования файла, а затем активно установить соответствующий формат кодирования для чтения, чтобы избежать искажений.
// Проверка кодировки файла 0=ANSI 1=UTF-16LE 2=UTF-16BE 3=UTF-8 4=UTF-8BOM
int DataCsv::findCode(const QString &fileName)
{
    // Предполагаем кодировку по умолчанию utf8
    int code = 3;
    QFile file(fileName);
    if (file.open(QIODevice::ReadOnly)) {
        // Считываем 3 байта для проверки
        QByteArray buffer = file.read(3);
        quint8 b1 = buffer.at(0);
        quint8 b2 = buffer.at(1);
        quint8 b3 = buffer.at(2);
        if (b1 == 0xFF && b2 == 0xFE) {
            code = 1;
        } else if (b1 == 0xFE && b2 == 0xFF) {
            code = 2;
        } else if (b1 == 0xEF && b2 == 0xBB && b3 == 0xBF) {
            code = 4;
        } else {
            // Попытка преобразования в utf8, если доступно больше 0 символов, это означает кодировку ansi
            QTextCodec::ConverterState state;
            QTextCodec *codec = QTextCodec::codecForName("utf-8");
            codec->toUnicode(buffer.constData(), buffer.size(), &state);
            if (state.invalidChars > 0) {
                code = 0;
            }
        }

        file.close();
    }

    return code;
}
  1. При выполнении запросов к удалённой базе данных иногда наблюдается низкая производительность, особенно при большом объёме данных в таблице. На локальном компьютере обработка данных происходит намного быстрее. Можно попробовать включить свойство только вперёд, query.setForwardOnly(true). В этом случае будет кэшироваться только одна порция данных, что значительно повысит эффективность запросов к удаленной базе данных. Говорят, что это может увеличить скорость в десятки или даже сотни раз. Конечно, это предполагает, что вам нужны только данные, полученные вперёд, если вам также нужны данные назад или вы используете их в модели данных QSqlQueryModel, вы не можете включить это свойство. Причина в том, что каждый раз, когда вы используете QSqlQuery для получения следующей записи, если вы не включаете свойство isForwardOnly (к сожалению, по умолчанию оно не включено), каждый раз будет выделяться новое пространство памяти для хранения уже посещённых и ещё не посещённых записей. Таким образом, каждый раз будет тратиться много места в памяти.

  2. Рисование с использованием painter в Qt очень гибкое и мощное, с богатым интерфейсом, но для многих начинающих это всё ещё довольно сложно, особенно различные странные сложные форматы, которые хорошо описываются в html, например, управление межстрочным и межсимвольным интервалами и т. д. В таких случаях можно использовать QTextDocument для передачи содержимого в формате html в QPainter для рисования, что очень идеально, просто и мощно, включая некоторые математические формулы и т.д.

void Form::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    QTextDocument doc;
    doc.setHtml(html);
    // Устанавливаем ширину текста
    doc.setTextWidth(200);
    // Указываем область рисования
    doc.drawContents(&painter, QRect(0, 0, 200, 70));
}
  1. В таблице стилей Qt есть приоритет для цветов выделения и парящих цветов. Наблюдая за поведением операционной системы по умолчанию, можно заметить, что когда элемент находится в состоянии выделения + парит, по умолчанию используется цвет парящего состояния. То есть, когда мышь перемещается по выделенному элементу списка, цвет фона принимает значение цвета парящего состояния. Однако в Qt, если установлены оба цвета, последний установленный цвет имеет приоритет. Если последним установленным цветом является цвет выделения, то при нахождении элемента в состоянии выделения + парения будет использоваться цвет выделения вместо цвета парящего состояния, помните!
// Ниже установлено, что при наведении курсора мыши на выбранный элемент item цвет фона будет #00FF00
QTableView::item:selected{background:#FF0000;}
QTableView::item:hover{background:#00FF00;}

// Ниже установлено, что при наведении курсора мыши на выбранный элемент item цвет фона будет #FF0000
QTableView::item:hover{background:#00FF00;}
QTableView::item:selected{background:#FF0000;}

// Стиль левого верхнего угла легко упустить из виду
QTableCornerButton:section{background:#FF0000;}
  1. Инструменты разработки qtc имеют множество встроенных функций, которые могут упростить выполнение некоторых проверок и обработки.
// Минимальное требование к версии
!minQtVersion(5, 15, 2) {
    message("Невозможно собрать инфраструктуру установщика Qt с версией $${QT_VERSION}.")
    error("Используйте версию Qt не ниже 5.15.2.")
}
  1. Иногда содержимое текстового поля слишком длинное, а курсор по умолчанию находится в конце, поэтому необходимо активно переместить его в начало.
// Все три метода работают
ui->lineEdit->setSelection(0, 0);
ui->lineEdit->setCursorPosition(0);
// Способ таблицы стилей
"QLineEdit{qproperty-cursorPosition:0;}
  1. Несколько замечаний о модуле браузера Qt:
  • До Qt 5.6 использовался webkit. Начиная с версии Qt 5.6, существует два случая: для компилятора mingw (Windows-система) соответствующая библиотека Qt больше не предоставляет модуль браузера.

  • В версиях Qt после 5.6 для систем Linux, Mac и других систем не существует ситуации отсутствия элемента управления браузером, везде используется webengine.

  • Только для Windows с компилятором mingw версия Qt не имеет модуля браузера. На самом деле, он есть во всех системах. Многие люди путаются в этом вопросе, думая, что только компилятор msvc имеет элемент управления браузером в Windows, но на самом деле это Qt-библиотека msvc для Windows, которая включает модуль браузера.

  • При установке Qt модуль webengine по умолчанию не отмечен, его необходимо установить вручную.

  • Не все версии Qt msvc имеют модуль веб-браузера webengine, даже если он отмечен, некоторые версии официально не компилируются, требуется самостоятельная компиляция. Необходимо проверить наличие файла Qt5WebEngine.dll в соответствующем каталоге установки Qt. 1. Если вам просто нужно восполнить недостаток модуля браузера в версии mingw, рекомендуется использовать miniblink.

  • Если вы хотите обеспечить совместимость с различными версиями и системами, рекомендуется использовать cef.

  • Если у вас нет исторического багажа, рекомендуется использовать webengine, который хорошо интегрирован с Qt.

  • Webkit и miniblink по умолчанию не поддерживают gpu, webengine по умолчанию использует gpu.

  • Qwebengine по умолчанию не поддерживает MP4, требуется самостоятельная перекомпиляция.

  1. Несколько советов по компиляции плагина базы данных.
  • Установите соответствующую базу данных, после установки появятся заголовочные файлы include и файлы библиотек lib, это необходимое условие, для успешной компиляции плагина базы данных необходимы эти два компонента. Обязательно учтите, что для 32-битной версии Qt необходимо установить 32-битную версию базы данных, разрядность должна совпадать.
  • Подготовьте исходный код плагина базы данных, например, qt-everywhere-src-5.14.2\qtbase\src\plugins\sqldrivers\mysql, при установке Qt можно выбрать src, или позже скачать исходный код с официального сайта и распаковать его.
  • Откройте исходный код подключаемого модуля базы данных для компиляции, например, для mysql откройте mysql.pro, для oracle откройте oci.pro.
  • В pro файле закомментируйте строку #QMAKE_USE += mysql, если проект oci, то #QMAKE_USE += oci.
  • Закомментируйте строку #include(..shadowed(..PWD)/qtsqldrivers-config.pri) в файле qsqldriverbase.pri.
  • Добавьте следующий код под содержимым файла mysql.pro:
path = C:/Qt/mysql-5.7.30-winx64
INCLUDEPATH += $$path/include
win32:LIBS += -L$$path/lib -llibmysql
  • Добавьте следующий код под содержимым файла oci.pro:
path = C:/app/Administrator/product/11.2.0/client_1
INCLUDEPATH += $$path/oci/include
win32:LIBS += -L$$path/oci/lib/msvc -loci
  • Добавьте следующий код под содержимым файла psql.pro:
path = "C:/Program Files/PostgreSQL/13"
INCLUDEPATH += $$path/include
win32:LIBS += -L$$path/lib -llibpq

Приведённая выше запись поддерживает как mingw, так и msvc, процесс компиляции для других систем аналогичен. После компиляции в корневом каталоге диска, на котором находится ваш текущий исходный код, появится каталог plugins, а внутри него — каталог sqldrivers с соответствующими динамическими библиотеками скомпилированных плагинов. По умолчанию код драйвера плагина oracle написан в соответствии с функциями oracle12, если вы связываетесь с oracle11, вам нужно изменить две строки кода, чтобы компиляция прошла успешно. Откройте файл qsql_oci.cpp, примерно в строке 1559 кода есть функция OCIBindByPos2, измените её на OCIBindByPos, ниже ещё есть строка bindColumn.lengths, измените её на (ub2*)bindColumn.lengths.

  1. Некоторые малоизвестные факты о разработке баз данных Qt.
  • Qt поддерживает связь с базами данных напрямую через библиотеки и через источники данных ODBC, охватывая все возможные сценарии.
  • При упаковке и выпуске программы на Qt для работы с базами данных необходимо учитывать различия между 32- и 64-битными версиями. Ваша программа должна быть скомпонована с соответствующей версией библиотеки: 32-разрядная версия требует 32-разрядных библиотек, 64-разрядная — 64-разрядных. Это требование распространяется и на библиотеки Qt. Проще всего опубликовать mysql, достаточно включить файл динамической библиотеки mysql (libmysql.dll в Windows), очень просто. Sqlserver не нужен, потому что он является родным для операционной системы. Для postgres вам понадобятся файлы libpq.dll, libintl-8.dll, libiconv-2.dll, libeay32.dll и ssleay32.dll. Для oracle требуются файлы oci.dll и oraociei11.dll (этот файл довольно большой, более 130 МБ), если это не сработает, рекомендуется установить клиентское программное обеспечение oracle client, а затем добавить путь к каталогу bin в переменную окружения.
  • После тестирования упакованного и выпущенного приложения было обнаружено, что 32-разрядное приложение может нормально подключаться к 64-разрядному mysql, а 64-разрядное приложение — к 32-разрядному mysql. Таким образом, достаточно, чтобы разрядность вашей программы соответствовала разрядности используемых вами библиотек (то же правило применяется при компиляции: для подключения к базе данных с помощью 32-разрядного Qt-приложения требуется 32-разрядный файл библиотеки базы данных). Нет необходимости соответствовать разрядности конкретной базы данных, и тестирование проводилось с использованием баз данных mysql, sqlserver и postgresql.
  • Благодаря многочисленным тестам и сравнениям было установлено, что метод прямого подключения к базе данных работает быстрее, чем использование источника данных ODBC для массовой вставки большого количества записей, примерно на 5%. Поэтому рекомендуется по возможности использовать этот метод, прибегая к использованию источника данных ODBC только в случае отсутствия прямого подключения. Qt по умолчанию включает плагин базы данных ODBC.
  • Различные базы данных автоматически преобразуют имена таблиц или полей в верхний или нижний регистр при выполнении SQL-запросов. MySQL преобразует имена таблиц в нижний регистр, PostgreSQL преобразует имена таблиц и полей в нижний регистр, Oracle преобразует имена таблиц и полей в верхний регистр. Это приводит к тому, что при использовании QSqlTableModel для установки имени таблицы базы данных необходимо убедиться, что оно соответствует регистру имени таблицы в базе данных, особенно при работе с PostgreSQL и Oracle. Я потратил много времени на эту проблему и чуть не обвинил Qt в ошибке.
void DbHelper::bindTable(const QString &dbType, QSqlTableModel *model, const QString &table)
{
    //postgresql全部小写,oracle全部大写,这两个数据库严格区分表名字段名的大小写卧槽
    QString flag = dbType.toUpper();
    if (flag == "POSTGRESQL") {
        model->setTable(table.toLower());
    } else if (flag == "ORACLE") {
        model->setTable(table.toUpper());
    } else {
        model->setTable(table);
    }
}
  • Qt позволяет открывать базы данных без указания имени базы данных, поскольку иногда требуется выполнить SQL-запросы для создания базы данных после подключения к серверу базы данных. Как можно подключиться к базе данных, которой ещё не существует? Тестирование показало, что sqlite, mysql, sqlserver и postgresql поддерживают эту функцию. Однако удаление и создание базы данных возможно только в том случае, если ни одна другая программа не использует эту базу данных; в противном случае операция не будет выполнена. Здесь я столкнулся со многими проблемами, почему операция не удалась? Позже я обнаружил, что третья сторона уже открыла базу данных с помощью инструмента, и проблема была решена после закрытия инструмента.
QSqlDatabase database = QSqlDatabase::addDatabase("QMYSQL");
//database.setDatabaseName("dbtool");
database.setHostName("127.0.0.1");
database.setPort(3306);
database.setUserName("root");
database.setPassword("root");

if (database.open()) {
    QSqlQuery query(database);
    qDebug() << "删除数据库" << query.exec("drop database dbtool");
    qDebug() << "创建数据库" << query.exec("create database dbtool");
    if (query.exec("select * from userinfo")) {
        while (query.next()) {
            qDebug() << "查询数据库" << query.value(0);
        }
    }
} else {
     qDebug() << "打开数据库" << database.lastError().text();
}
  • Отображение данных с использованием QSqlQueryModel и QTableView для данных типа int, если их больше 10 миллионов, они будут отображаться в экспоненциальном представлении, что крайне нежелательно. После долгих поисков в интернете я наконец нашёл человека, столкнувшегося с той же проблемой. Нужно добавить пустой делегат для этого столбца. Позже выяснилось, что пустой делегат не работает, когда количество строк превышает 100 миллионов, поэтому пришлось радикально изменить способ отображения данных в модели.
ui->tableView->setItemDelegateForColumn(0, new QItemDelegate);

//下面是终极大法
QVariant SqlQueryModel::data(const QModelIndex &index, int role) const
{
    QVariant value = QSqlQueryModel::data(index, role);
    //超过100万的数值会被科学计数显示需要这里转成字符串显示
    if (role == Qt::DisplayRole) {
        int result = value.toInt();
        if (result >= 1000000) {
            value = QString::number(result);
        }
    }
    return value
} В базах данных MySQL есть различные движки, среди которых MyIsam не поддерживает транзакции базы данных. По умолчанию обычно используется именно этот движок, поэтому после использования метода transaction в Qt и последующей команды commit вы можете обнаружить, что операция не была успешной, хотя на самом деле всё прошло успешно, и результаты в базе данных соответствуют ожиданиям.

Есть два решения: первое  изменить движок базы данных на InnoDB, второе  выполнить проверку на наличие ошибки после commit: if (database.commit() || !database.lastError().isValid()). Если метод lastError() недоступен или не работает, это также указывает на успешное выполнение операции.

Если вы используете соединение с базой данных через ODBC, то достаточно установить три параметра: databaseName (имя базы данных), userName (имя пользователя) и password (пароль пользователя), поскольку при настройке источника данных уже были заданы соответствующие хост-адрес, порт и имя связанной базы данных. При использовании соединения через ODBC вам нужно только повторно проверить информацию о пользователе. Здесь важно отметить, что при установке параметра databaseName необходимо ввести имя, указанное в конфигурации источника данных.

После проведения многочисленных сравнительных тестов, включая операции вставки, удаления, пакетной обработки, запроса и постраничного просмотра, можно сделать вывод, что скорость отклика части базы данных Qt следующая: sqlite > postgresql > oracle > mysql > odbc. Для объёмов данных свыше миллиона результаты следующие: postgresql > oracle > mysql > sqlite > odbc. На уровне миллиардов записей: oracle > postgresql > остальные. Все эти тесты проводились на основе уровня знаний начинающих пользователей. Более продвинутые темы, такие как разделение баз данных и таблиц, сложные запросы, кэширование и базы данных в памяти, не использовались.

У MySQL есть две основные версии: MySQL 5.7 и MySQL 8. Согласно официальным заявлениям, версия 8 намного быстрее версии 5, но мои личные тесты показали обратное: версия 5.7 работает быстрее, чем версия 8, независимо от того, идёт ли речь о запросах или пакетной вставке данных. Результаты поиска в интернете также подтверждают это наблюдение. Многие пользователи сообщают, что версия 8 работает медленнее.

Существует ответвление MySQL под названием MariaDB, которое превосходит MySQL по чистоте и производительности. Говорят, что MariaDB превосходит MySQL во всех аспектах. Мои личные сравнительные тесты также подтвердили значительное улучшение производительности при пакетной вставке и запросах. Кроме того, MariaDB полностью совместима с MySQL, и даже файлы библиотек могут быть легко переименованы для использования. Например, libmariadb.dll можно переименовать в libmysql.dll, и он будет работать без проблем. Более того, размер MariaDB составляет всего одну восьмую от размера MySQL. Это значительное преимущество при публикации приложения.

Если ваше приложение использует Qt и MySQL, то версии библиотек, которые вы распространяете, должны соответствовать версии базы данных, с которой вы работаете, иначе вы можете потерять возможность транзакций. Вы можете проверить это, используя функцию database.driver()->hasFeature(QSqlDriver::Transactions).

QSqlTableModel очень хорошо инкапсулирован и не загружает все данные сразу, а загружает только те данные, которые необходимы при прокрутке. Даже при работе с таблицами, содержащими миллиард строк, скорость остаётся высокой, такой же, как и при работе с таблицами в несколько тысяч строк.

При подключении к сетевой базе данных, если у вас настроен прокси-сервер в локальной сети, например, для доступа к GitHub, вы можете столкнуться с проблемами подключения в программах баз данных Qt. Чтобы решить эту проблему, необходимо отключить использование локального прокси-сервера, установив QNetworkProxyFactory::setUseSystemConfiguration(false). Если не уделить этому должного внимания, это может вызвать проблемы.

### 24: 231–240

231. Несколько моментов о наследовании, полиморфизме, virtual и override в C++.

* Подклассы могут напрямую использовать переменные и функции, защищённые в базовом классе.
* Если функция базового класса не объявлена как virtual, а подкласс имеет такую же функцию, то происходит переопределение. Когда вызывается через указатель базового класса, вызывается функция базового класса; когда вызывается через указатель подкласса, вызывается функция подкласса.
* Если функция базового класса объявлена как virtual, и подкласс имеет ту же функцию, происходит перегрузка. Независимо от того, вызывается ли она через указатель базового класса или указатель подкласса, всегда вызывается функция подкласса.
* Добавление override к функции требует, чтобы одноимённая функция в подклассе была виртуальной, и она должна быть переопределена, иначе компиляция не удастся.
* Можно не добавлять virtual к функциям подкласса, рекомендуется добавлять override без virtual.
* Чистые виртуальные функции в базовом классе (virtual void play() = 0;) не требуют реализации в файле .cpp базового класса, но они должны быть реализованы в подклассе, иначе компиляция завершится ошибкой.
* Основное преимущество полиморфного наследования заключается в выделении общих черт, таких как общие переменные, методы и сигналы, в базовый класс, позволяя подклассам реализовывать свои специфические функции.

232. О том, как реализовать эффект выделения всей строки при наведении курсора на ячейку в QTableView и QTableWidget. Большинство решений в Интернете ориентированы на QTableWidget, а для QTableView используются делегаты или пользовательские средства рисования.

* Предварительное условие: установите поведение выбора для автоматического выбора всей строки, tableView->setSelectionBehavior(QAbstractItemView::SelectRows).
* Установите фильтр событий, определите модель данных в текущей координате, затем установите текущую модель как модель в точке зависания курсора. Этот хитрый приём может сэкономить много работы.
* Он работает как для QTableView, так и для QTableWidget.

233. Как избежать конфликтов ключевых слов с сигналами, слотами и другими элементами сторонних библиотек в Qt.

* Первый шаг: добавьте CONFIG += no_keywords в файл pro.
* Второй шаг: замените все существующие сигналы на Q_SIGNALS, слоты на Q_SLOTS и т. д. в проекте.
* Третий шаг: полностью перекомпилируйте проект, чтобы устранить конфликты ключевых слов.

234. Различие между операционными системами и аппаратными платформами в файле pro.
```cpp
win32 {}
unix {}
//Qt5 можно использовать linux{} Qt4 обязательно unix:!maxc{}
unix:!maxc{}
linux {}
maxc {}
android {}
wasm {}

//представляет 64-битную платформу
contains(QT_ARCH, x86_64) {}
//представляет платформу arm
contains(QT_ARCH, arm) || contains(QT_ARCH, arm64) {}
//универсальный метод, просто переключитесь на комплект печати QT_ARCH и посмотрите, какие символы
message($$QT_ARCH)
  1. При отображении видео обычно используются три механизма масштабирования: автоматический режим (масштабирование с сохранением пропорций, если изображение больше области отображения, в противном случае отображение в исходном размере), обычный режим (всегда масштабирование с сохранением пропорций) и режим заполнения (растяжение изображения для заполнения области отображения). Класс QImage в Qt предоставляет параметры для настройки стратегии масштабирования. Qt::KeepAspectRatio означает масштабирование с сохранением пропорций. Однако часто нам нужно установить размер виджета, и класс QSize предоставляет метод scale для решения этой задачи. Этот метод легко упустить из виду.
//Режимы масштабирования
enum ScaleMode {
    //Автоматический режим (масштаб с сохранением пропорций, если изображение больше области отображения, иначе отображение в исходном размере)
    ScaleMode_auto = 0,
    //Обычный режим (всегда масштаб с сохранением пропорций)
    ScaleMode_normal = 1,
    //Режим заполнения (растягивание изображения для заполнения области отображения)
    ScaleMode_fill = 2
};

//Возвращает центральную область, учитывая размер изображения, область виджета и размер рамки
static QRect getCenterRect(const QSize &imageSize, const QRect &widgetRect, int borderWidth = 2, const ScaleMode &scaleMode = ScaleMode_auto)
{
    QSize newSize = imageSize;
    QSize widgetSize = widgetRect.size() - QSize(borderWidth * 2, borderWidth * 2);

    if (scaleMode == ScaleMode_auto) {
        if (newSize.width() > widgetSize.width() || newSize.height() > widgetSize.height()) {
            newSize.scale(widgetSize, Qt::KeepAspectRatio);
        }
    } else if (scaleMode == ScaleMode_normal) {
        newSize.scale(widgetSize, Qt::KeepAspectRatio);
    } else {
        newSize = widgetSize;
    }

    int x = widgetRect.center().x() - newSize.width() / 2;
    int y = widgetRect.center().y() - newSize.height() / 2;
    return QRect(x, y, newSize.width(), newSize.height());
}

//Создаёт изображение подходящего размера, учитывая размер виджета и стратегию масштабирования
static void getScaledImage(QImage &image, const QSize &widgetSize, const ScaleMode &scaleMode = ScaleMode_auto, bool fast = true)
{ ```cpp
// Qt::SmoothTransformation;
if (scaleMode == ScaleMode_auto) {
    if (image.width() > widgetSize.width() || image.height() > widgetSize.height()) {
        image = image.scaled(widgetSize, Qt::KeepAspectRatio, mode);
    }
} else if (scaleMode == ScaleMode_normal) {
    image = image.scaled(widgetSize, Qt::KeepAspectRatio, mode);
} else {
    image = image.scaled(widgetSize, Qt::IgnoreAspectRatio, mode);
}
}
  1. 关于在头文件中定义函数使用static关键字的教训。
  • Иногда нам нужно поместить некоторые часто используемые функции в один файл, чтобы вызывать их из разных мест. Если мы напишем int doxxx{}, то при повторном использовании получим ошибку компиляции «повторное определение».
  • В этом случае нам нужно добавить ключевое слово static перед функцией, чтобы она стала static int doxxx{}. Это позволит компилировать и запускать программу без ошибок. Я думал, что всё будет хорошо, но я был слишком молод.
  • Если функция используется только внутри класса или не содержит статических переменных, проблем не возникнет. Проблема возникает, когда статическая функция копируется каждый раз при включении заголовочного файла, что приводит к повторной инициализации статических переменных в функции. Это неправильно.
  • Чтобы решить эту проблему, лучшим решением является использование класса. Все функции и переменные помещаются в класс, и проблема решается.
  • О ключевом слове static в C/C++ рекомендуется прочитать статью по ссылке: https://zhuanlan.zhihu.com/p/37439983.
// Файл test.h

// Следующая функция вызовет ошибку компиляции "повторное определение"
void test() {}

// Следующие четыре функции будут скопированы при каждом включении заголовочного файла
static void test1() {}
inline void test2() {}
static inline void test3() {}
inline static void test4() {}

// Правильное написание
class tt {
    void test() {}
    static void test1() {}
    inline void test2() {}
    static inline void test3() {}
    inline static void test4() {}
}
  1. При выполнении запросов к базе данных обычно создаются индексы для ускорения процесса. Например, поля с часто используемыми условиями могут быть проиндексированы. Однако, если запрос сформулирован неправильно, даже при наличии индексов в предложении where, может произойти полное сканирование таблицы, что делает индексы бесполезными.
  • Запросы с использованием like, особенно с подстановочными знаками в начале или в конце строки, не используют индексы. Запросы с right-hand wildcards (например, '...%') могут использовать индексы.
  • Заявления select, содержащие is null, выполняются медленно, а запросы с is not null никогда не используют индексы. Для таблиц с большим объёмом данных следует избегать использования is null в запросах.
  • Операторы неравенства <> и != ограничивают использование индексов и могут привести к полному сканированию таблицы.
  • Когда в предложении where сравниваются два условия, одно из которых имеет индекс, а другое — нет, использование or приведёт к полному сканированию.
  • Запрос count(*) без условий приведёт к полному сканированию таблицы.
  • Использование in и not in может привести к сканированию всей таблицы, поэтому лучше использовать between, если это возможно.
  • Вместо оператора больше (>), можно использовать оператор больше или равно (>=). Например, эффективный запрос: select * from table where id >= 4, менее эффективный: select * from table where id > 3.
  • Для таблиц с небольшим объёмом данных (несколько тысяч строк), добавление или удаление индексов может не иметь большого значения. В некоторых случаях добавление индексов может увеличить размер файла базы данных и замедлить обновление.
  1. Из-за постоянных обновлений и изменений в Qt, включая добавление, разделение и настройку компонентов, при написании проектов необходимо учитывать совместимость версий. Многолетний опыт показывает, что универсальным решением является добавление определения DEFINES в файл проекта pro и соответствующая обработка в файлах проекта и кода. После внесения этих изменений код может компилироваться и работать как в старых, так и в новых версиях.
//pro pri 文件
// Указывает на основную версию > 4 и младшую версию > 6, то есть версия >= 5.7
greaterThan(QT_MAJOR_VERSION, 4) {
greaterThan(QT_MINOR_VERSION, 6) {
DEFINES += qchart
}}

// Учитывая выпуск Qt6 и последующие версии Qt7, Qt8 и т.д., одного вышеуказанного условия недостаточно
// Указывает на основную версию > 5, то есть версия >= 6.0
greaterThan(QT_MAJOR_VERSION, 5) {
DEFINES += qchart
}

// Проверяет наличие определения и импортирует соответствующий модуль
contains(DEFINES, qchart) {
QT += charts
}

// Код файла
#ifdef qchart
// Выполнение нужного кода
#endif

// Изучение кода проектов Qt показывает, что можно напрямую проверять версии больше определённой, без необходимости многократной проверки основной версии
// Тестирование с версиями 4.8, 5.5, 5.7, 5.15, 6.2, 6.7 и т. д. работает нормально
// Недостатком является то, что передаваемая основная версия должна быть меньше или равна указанной в первом параметре основной версии
greaterThan(QT_MAJOR_VERSION, 4)|greaterThan(QT_MINOR_VERSION, 7) {
message(Текущая версия больше 4.7)
}

// Аналогично для проверки версии компилятора
// Также доступны QT_GCC_MAJOR_VERSION, QT_GCC_MINOR_VERSION, QT_CLANG_MAJOR_VERSION и QT_CLANG_MINOR_VERSION
// Вывод текущего имени компилятора QMAKE_CXX даёт результат cl / g++
// Вывод текущих определений компилятора QMAKE_COMPILER_DEFINES даёт результат _MSC_VER=1800 _WIN32 / __GNUC__ WIN32
// Вывод версии компилятора MSVC_VER даёт результат msvc2013=12.0 / msvc2015=14.0
  1. При использовании графического элемента управления QChart вы можете заметить, что отступы по умолчанию слишком велики. Часто требуется отобразить больше информации на экране, поэтому необходимо настроить отступы.
// Установка закруглённости углов области фона
chart->setBackgroundRoundness(0);
// Настройка внутренних отступов
chart->setMargins(QMargins(0, 0, 0, 0));
// Настройка внешних отступов
chart->layout()->setContentsMargins(0, 0, 0, 0);
  1. Qt предоставляет встроенные функции для сжатия и распаковки данных. Если вам нужно передать данные, такие как изображения, аудиофайлы или файлы в формате base64, сжатие данных с помощью qCompress может сэкономить около 30% пропускной способности. Важно, чтобы обе стороны использовали Qt, поскольку полученные данные необходимо распаковать с помощью qUncompress.
// Сжатие данных перед отправкой
QByteArray buffer = "...";
buffer = qCompress(buffer);
socket->write(buffer);

// Распаковка полученных данных
QByteArray data = socket->readAll();
data = qUncompress(data);

25:241-250

  1. Класс QString, по моему мнению, является жемчужиной среди всех классов Qt. Он безупречно инкапсулирует различные преобразования двоичных данных, например, преобразование данных в десятичную или шестнадцатеричную систему, или наоборот. Многие люди думают, что поддерживаются только двоичные, десятичные и шестнадцатеричные системы, но это не так. Реализованы преобразования между любыми системами счисления от 2 до 36. Вы можете просмотреть исходный код для деталей реализации.
char data[2];
data[0] = 0x10;
data[1] = 25;

// Вывод двоичного представления "10000" "11001"
qDebug() << "Двоичное представление" << QString::number(data[0], 2) << QString::number(data[1], 2);
// Вывод пятеричного представления "31" "100"
qDebug() << "Пятеричное представление" << QString::number(data[0], 5) << QString::number(data[1], 5);
// Вывод десятичного представления "16" "25"
qDebug() << "Десятичное представление" << QString::number(data[0]) << QString::number(data[1]);
// Вывод шестнадцатеричного представления "10" "19"
qDebug() << "Шестнадцатеричное представление" << QString::number(data[0], 16) <<
``` ```cpp
QString::number(data[1], 16);
  1. Модуль QtSql инкапсулирует различные операции с базами данных, что делает работу Qt с различными базами данных очень простой. Поддерживаются самые разнообразные базы данных, включая наиболее базовый способ подключения к различным базам данных через ODBC. Есть один легко упускаемый из виду момент: при подключении к базе данных sqlserver вы обнаружите, что сторонние инструменты баз данных также не настроены для работы с базой данных, но могут успешно подключаться, в то время как обычный способ подключения баз данных в Qt не работает, потому что ваш код написан неправильно, и вам нужно использовать другой способ написания.
// Подключение к базе данных sqlite
QSqlDatabase database = QSqlDatabase::addDatabase("QSQLITE");
// Достаточно указать абсолютный путь к файлу базы данных
database.setDatabaseName("d:/test.db");

// Подключение к базе данных mysql
QSqlDatabase database = QSqlDatabase::addDatabase("QMYSQL");
database.setDatabaseName("test");
database.setHostName("127.0.0.1");
database.setPort(3306);
database.setUserName("root");
database.setPassword("root");

// Подключение к базе данных sqlserver
// Способ 1 через источник данных odbc, при условии, что источник данных должен быть настроен.
QSqlDatabase database = QSqlDatabase::addDatabase("QODBC");
database.setDatabaseName("источник данных");
database.setUserName("sa");
database.setPassword("123456");

// Способ 2 через строку драйвера, без настройки источника данных. Указание имени базы данных включает в себя IP-адрес хоста и порт, а также информацию о пользователе, поэтому все последующие настройки не требуются. Настоятельно рекомендуется этот метод.
QSqlDatabase database = QSqlDatabase::addDatabase("QODBC");
QStringList list;
list << QString("DRIVER={%1}").arg("SQL SERVER");
list << QString("SERVER=%1,%2").arg("127.0.0.1").arg(1433);
list << QString("DATABASE=%1").arg("test");
list << QString("UID=%1").arg("sa");
list << QString("PWD=%1").arg("123456");
database.setDatabaseName(list.join(";"));

// Подключение к базе данных postgresql
QSqlDatabase database = QSqlDatabase::addDatabase("QPSQL");
database.setDatabaseName("test");
database.setHostName("127.0.0.1");
database.setPort(5432);
database.setUserName("postgres");
database.setPassword("123456");

// Подключение к базе данных oracle
QSqlDatabase database = QSqlDatabase::addDatabase("QOCI");
database.setDatabaseName("test");
database.setHostName("127.0.0.1");
database.setPort(1521);
database.setUserName("system");
database.setPassword("123456");

// Подключение к базе данных kingbase (ядро — postgresql)
QSqlDatabase database = QSqlDatabase::addDatabase("QPSQL");
database.setDatabaseName("test");
database.setHostName("127.0.0.1");
database.setPort(54321);
database.setUserName("SYSTEM");
database.setPassword("123456");

// Через источник данных odbc подключение к различным базам данных, при условии, что источник данных настроен. Достаточно установить имя базы данных как имя источника данных, ввести имя пользователя и пароль, остальные настройки, такие как IP-адрес хоста и порт, не нужны.
QSqlDatabase database = QSqlDatabase::addDatabase("QODBC");
database.setDatabaseName("источник данных");
database.setUserName("system");
database.setPassword("123456");
  1. Если функция connect(obj, SIGNAL(), this, SLOT());, связывающая сигнал и слот, выполняется несколько раз, связь будет повторяться (то есть выполняться несколько раз). Чтобы отменить связь между сигналом и слотом, достаточно выполнить функцию disconnect(obj, SIGNAL(), this, SLOT()); один раз. Многие новички сталкиваются с проблемой, когда нажатие кнопки приводит к многократному выполнению функции. Причина этого часто кроется в том, что в коде была использована автоматически сгенерированная Qt функция слота, например on_objName_clicked();, а затем в коде был вызван connect для повторной привязки. Совет: Qt может фильтровать привязки, если они полностью идентичны, рассматривая их как одну привязку вместо нескольких.
// Чтобы гарантировать, что всегда будет только одна привязка, перед привязкой выполните отмену привязки один раз
disconnect(obj, SIGNAL(), this, SLOT());
connect(obj, SIGNAL(), this, SLOT());

// По совету опытных разработчиков из сообщества, оказывается, можно избежать этой проблемы, передав пятый параметр UniqueConnection в функцию connect. Согласно официальной документации, этот параметр фильтрует повторяющиеся сигналы.
connect(obj, SIGNAL(), this, SLOT(), Qt::UniqueConnection);
  1. Изучая исходный код примеров, поставляемых вместе с Qt, вы заметите, что в более поздних версиях предпочтение отдаётся использованию интеллектуального указателя QScopedPointer для определения объектов. Преимущество такого подхода заключается в том, что после new вам больше не нужно беспокоиться об освобождении ресурсов, так как интеллектуальный указатель освободит их в подходящий момент, аналогично тому, как если бы вы могли опустить строку кода вида xxx->deleteLater(). Кроме того, это позволяет избежать ненужных проблем, поскольку во многих случаях вам пришлось бы проверять if (!xxx), чтобы убедиться, что объект в порядке.
QWidget *widget;
// Используем new
widget = new QWidget;
// Освобождаем объект после использования
widget->deleteLater();

// Использование интеллектуального указателя
QScopedPointer<QWidget> widget;
// Просто используем new, освобождение ресурсов происходит автоматически
widget.reset(new QWidget);
  1. При попытке вызвать setLayout для повторного установления макета в виджете, который уже имеет макет, Qt выдаёт предупреждение, сообщая, что макет уже существует и его необходимо удалить перед установкой нового. Рекомендуется использовать метод layout()->deleteLater() для безопасного удаления макета, однако в данном случае это не сработает, и потребуется использовать delete layout(). Это довольно странно.

  2. При написании классов иногда возникает необходимость присваивать значения переменным и получать их. Обычно для этого используются функции setxxx и getxxx. Часто внутри этих функций всего одна строка кода, и тогда может возникнуть вопрос, почему бы просто не сделать переменные public и использовать их напрямую, сэкономив таким образом две функции и несколько строк кода. На самом деле использование функций set и get обусловлено необходимостью расширения функциональности. Например, если позже потребуется фильтровать присвоения значений или разрешить доступ только для чтения или записи, изменение правил затронет все места использования переменной. Это может привести к проблемам.

  3. Что касается быстрого завершения потока, вызов terminate для принудительного завершения может вызвать проблемы. Обычно мы используем флаг для завершения потока. Однако, если выполнение функции занимает много времени или время ожидания в run слишком велико, поток может завершиться некорректно. В таких случаях можно рассмотреть стратегию разделения тела потока на части. Если проблема связана с длительным выполнением функции, можно добавить проверку флага остановки внутри функции. Если же проблема в длительном ожидании, можно разбить время ожидания на несколько коротких интервалов и проверять флаг остановки на каждом интервале. Это ускорит завершение потока без длительного ожидания.

void Thread::run()
{
    while (!stopped) {
        doTask();

        // Здесь длительное ожидание может замедлить выход
        // msleep(3000);

        // Специально добавляем небольшую задержку и проверяем флаг остановки после каждой задачи
        int count = 0; ```
while (!stopped) {
    msleep(100);
    count++;
    //如果到了30次=30*100=3000毫秒也跳出
    if (count == 30) {
        break;
    }
}  
}     
stopped = false;    
}

void Thread::doTask()
{
    while(1) {
        if (stopped) {
            return;
        }

        doTask1();
        doTask2();
    }    
}
//Qt对有共同父类窗体的控件优化到了极致,下面生成了1000个widget才新增不到3mb的内存。
for (int i = 0; i < 1000; ++i) {
    QWidget *w = new QWidget(this);
    w->setGeometry(0, 0, 100, 100);
    w->show();
}

QWidget *w1, *w2, *w3;
//将w1控件移到最前面相当于在该父窗体中置顶
w1->raise();
//将w1控件移到最后面相当于在该父窗体中置底
w1->lower();
//将w1控件移到w2控件下面
w1->stackUnder(w2);
class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();

protected:
    void closeEvent(QCloseEvent *);

private slots:
    void on_pushButton_clicked();

private:
    Ui::MainWindow *ui;
    QLabel *lab;
};

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    lab = new QLabel;
    lab->resize(400, 300);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::closeEvent(QCloseEvent *)
{
    //先把子窗体释放
    lab->deleteLater();
}

void MainWindow::on_pushButton_clicked()
{
    lab->show();
}
//下面这个会立即执行
QResizeEvent event(size(), size());
QApplication::sendEvent(this, &event);

//下面这个会立即执行
QResizeEvent *event = new QResizeEvent(size(), size());
QApplication::sendEvent(this, event);

//下面这个不会报错但是也不会执行因为事件对象是局部变量
QResizeEvent event(size(), size());
QApplication::postEvent(this, &event);

//下面的方式非常安全
QResizeEvent *event = new QResizeEvent(size(), size());
QApplication::postEvent(this, event);

26:251-260

//必须要先引入这个头文件
#include "qglobal.h"

#ifdef Q_OS_WIN
...
#else
...
#endif

#ifdef Q_CC_MSVC
#pragma execution_character_set("utf-8")
#endif
//QTimer::singleShot(1000, thread, SLOT(xxx()));

static QTimer *timer = NULL;
if (!timer) {
    timer = new QTimer;
    QObject::connect(timer, SIGNAL(timeout()), thread, SLOT(xxx()));
    timer->setSingleShot(true);
    timer->setInterval(1000);
}
timer->stop();
timer->start();

Иногда мы обнаруживаем, что после установки прозрачности фона на контроллере он становится чёрным. Вы можете попробовать установить значение прозрачности равное 1 вместо полной прозрачности равной 0. Это выглядит прозрачным, но сохраняет свойства окна. Если вы хотите отключить системную тень без рамки, вы можете установить свойство w.setWindowFlags(w.windowFlags() | Qt::NoDropShadowWindowHint).

В Qt фильтр событий представляет собой универсальное решение, особенно при установке фильтра событий для всего приложения, который позволяет получать все события. Например, можно получить событие отпускания кнопки мыши в системной панели заголовка и обработать перемещение всех окон без рамок. Я рекомендую использовать его только в случае крайней необходимости, так как он может снизить производительность, перехватывая все события в самом начале. Если после получения соответствующего события вы выполняете длительную обработку, это может привести к зависанию пользовательского интерфейса основного потока.

void AppInit::start()
{
    qApp->installEventFilter(this);
}

bool AppInit::eventFilter(QObject *watched, QEvent *event)
{
    if (event->type() == QEvent::NonClientAreaMouseButtonPress) {
        qDebug() << "Система заголовка нажата";
    } else if (event->type() == QEvent::NonClientAreaMouseButtonRelease) {
        qDebug() << "Система заголовка отпущена";
    }
    
    QWidget *w = (QWidget *)watched;
    if (!w->property("canMove").toBool()) {
        return QObject::eventFilter(watched, event);
    }

    static QPoint mousePoint;
    static bool mousePressed = false;

    QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
    if (mouseEvent->type() == При компиляции динамической библиотеки файлов в Linux может создаться множество символических ссылок (на значке есть маленькая стрелка `/libuntitled.so/libuntitled.so.1/libuntitled.so.1.0libuntitled.so.1.0.0`), что зачастую выглядит раздражающе. В Windows создаётся один файл, и вам нужно всего лишь добавить строку `CONFIG += plugin` в ваш pro или pri файл, чтобы создать только один libuntitled.so файл.

2023-04-02 Дополнение: также можно использовать `CONFIG += unversioned_libname unversioned_soname`, чтобы реализовать это. `unversioned_libname` используется для удаления различных версий номеров lib, а `unversioned_soname`  для удаления номеров версий из ссылок при компиляции (если не добавить этот параметр, хотя и будет создан libuntitled.so, но при компиляции ссылок будут выдаваться ошибки о зависимости с номерами версий).

Для получения более подробной информации см. [https://blog.csdn.net/gongjianbo1992/article/details/129889588](https://blog.csdn.net/gongjianbo1992/article/details/129889588).

О методе устранения ошибки, возникающей при онлайн-установке Qt, которая указывает на сетевую ошибку при загрузке «http://mirrors.aliyun.com...»: откройте командную строку и запустите программу установки, например, `C:\Users\Administrator>D:\Qt\Qt6\MaintenanceTool.exe`, затем добавьте параметр `--mirror https://mirrors.cloud.tencent.com/qt` вручную. Полная команда будет выглядеть так: `C:\Users\Administrator>D:\Qt\Qt6\MaintenanceTool.exe --mirror https://mirrors.cloud.tencent.com/qt`. Нажмите Enter для запуска. Аналогично, вы можете использовать другие внутренние адреса зеркал (Tencent Cloud https://mirrors.cloud.tencent.com/qt / Alibaba Cloud https://mirrors.aliyun.com/qt). Иногда обновления зеркал могут быть медленными, поэтому вы можете перейти на сайт https://mirrors.aliyun.com/qt/online/qtsdkrepository/, чтобы проверить, есть ли соответствующая версия.

С версии Qt6.4 мультимедийный модуль предоставляет ffmpeg в качестве декодера для использования (по умолчанию используется ffmpeg в версии 6.5). Вы можете изменить используемый декодер, установив переменную окружения. В первой строке функции main добавьте `qputenv("QT_MEDIA_BACKEND", "ffmpeg");`. Известная проблема заключается в том, что если выбрать ffmpeg, то временно не поддерживается китайский каталог и китайские названия (исправлено в версии 6.5.1), если требуется поддержка китайского языка, необходимо перейти на Windows.
```cpp
// Установка декодера на ffmpeg/поддерживается всеми системами
qputenv("QT_MEDIA_BACKEND", "ffmpeg");
// Для системы Windows
qputenv("QT_MEDIA_BACKEND", "windows");
// Для Linux системы
qputenv("QT_MEDIA_BACKEND", "gstreamer");
// Для Mac системы
qputenv("QT_MEDIA_BACKEND", "darwin");
// Для Android системы
qputenv("QT_MEDIA_BACKEND", "android");

По умолчанию QComboBox регулирует свою ширину в соответствии с шириной символов элементов. Если текст элемента длинный, ширина выпадающего списка становится широкой, что может привести к искажению интерфейса. Есть несколько способов предотвратить это:

  • Метод 1: установите стратегию растяжения выпадающего списка на QSizePolicy::Ignored и поместите его в контейнер, убедившись, что другие элементы в макете контейнера имеют фиксированный размер или фиксированное заполнение. Это гарантирует, что выпадающий список не будет расширяться вместе с элементами.
  • Лучший метод: используйте ui->comboBox->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon). Когда ширина элемента превышает определённое значение, отображается многоточие. Чтобы применить эту политику ко всему проекту, добавьте в таблицу стилей следующую строку: qApp->setStyleSheet("QComboBox{qproperty-sizeAdjustPolicy:AdjustToMinimumContentsLengthWithIcon}");. После этого все выпадающие списки в проекте будут автоматически применять эту политику.

При сохранении float значения с использованием класса QSettings оно преобразуется в значение, начинающееся с @Variant, которое трудно интерпретировать. Например, 1.0 = @Variant(\0\0\0\x87?\x80\0\0). Если вы хотите напрямую редактировать конфигурационный файл для изменения параметров, это становится сложным. Существует два решения:

  • Преобразуйте значение в QString при записи: set.setValue("SaveVideoRatio", QString::number(SaveVideoRatio));.
  • Измените тип параметра с float на double: например, измените float SaveVideoRatio на double SaveVideoRatio. Рекомендуется первый метод, поскольку он не требует изменения типа данных и может быть выполнен путём редактирования одной строки кода. Однако следует отметить, что точность double отличается от float, например, float i = 0.1 станет double i = 0.10000000149011612. В Qt6 эта проблема полностью решена, и преобразование больше не требуется.

Примерно с Qt5.12 был добавлен внешний вид платформы platformthemes. Это означает, что при упаковке и выпуске приложения необходимо включать его, чтобы применить стили оформления на уровне системы. Без него приложение может выглядеть устаревшим, как в стиле Windows 2000 на Windows.

Иногда после настройки автозапуска программы при запуске, если программа вызывает другую программу B через QProcess и программе B требуется прочитать конфигурационный файл из каталога, обнаруживается, что файл не может быть прочитан, потому что рабочий каталог по умолчанию после запуска отличается от каталога исполняемого файла (если запустить программу двойным щелчком, такой проблемы не возникнет, и текущий каталог будет установлен на каталог исполняемого файла автоматически). Поэтому нам нужно выполнить код QDir::setCurrent(qApp->applicationDirPath());, чтобы явно указать операционной системе текущий каталог. Хотя функция setWorkingDirectory в QProcess была протестирована, она оказалась неэффективной для программ, запущенных после старта, и вместо неё следует использовать QDir::setCurrent. Однако у этого подхода есть побочный эффект: как только вызывается QDir::setCurrent, все относительные пути в вашей программе будут относиться к этому пути, что может вызвать непредвиденные результаты.

271–280

В процессе программирования часто возникает необходимость преобразовать QString в char * или const char *. После преобразования в QByteArray используйте функции .data() или .constData() для выполнения преобразования. Важно отметить, что использование .data() для преобразования в const char * не рекомендуется, хотя оно и работает без ошибок, так как выполняет глубокое копирование, увеличивая нагрузку на память. Если длина строки невелика, это не имеет большого значения, но если строка длинная, дополнительная нагрузка на память может быть значительной. Это хорошая практика программирования.

// Изучение кода показывает, что функция data имеет две перегрузки
inline char *QByteArray::data()
{ detach(); return d->data(); }
inline const char *QByteArray::data() const
{ return d->data(); }
inline const char *QByteArray::constData() const
{ return d->data(); }

QByteArray data = "abc";
// Глубокое копирование
char *d1 = data.data();
// Глубокое копирование
const char *d2 = data.data();
// Мелкое копирование
const char *d3 = data.constData();

// Глубокое копирование
test(data.data());
// Мелкое копирование
test(data.constData());
void test(const char *data)
{
}

// Относительно того, когда вызывать .data(), чтобы выполнить мелкое копирование, мастер Coolcode говорит, что это происходит, когда QByteArray объявлен как const
const QByteArray data;
// Мелкое копирование
const char *d = data.data();

// Мастер Coolcode добавляет: начиная с версии Qt 5.7, была введена функция qAsConst, предназначенная исключительно для тупого преобразования.
// Эта функция реализует функциональность std::as_const() из стандарта C++17, преобразуя изменяемое левое значение в константное левое значение.
// Добавление функции qAsConst было сделано для того, чтобы контейнеры Qt могли поддерживать основанные на диапазоне циклы в стандарте C++11.
// Функция в основном используется, чтобы избежать отсоединения контейнеров Qt в контексте неявного совместного использования.
QString s = "abc";
// Здесь будет выполнено глубокое копирование, что приведёт к снижению производительности
for (QChar ch : s)
// Здесь глубокого копирования не произойдёт
for (QChar ch : qAsConst(s))
// Ниже также выполняется мелкое копирование, но в программировании и в реальности объявление переменной как const не всегда легко достижимо
const QString s;
for (QChar ch : s) // Резюме: для контейнеров, реализованных в Qt, таких как QVector, QMap, QHash, QLinkedList, QList и т. д., если необходимо использовать цикл в форме for (var : container), используйте следующую форму:
for (var : qAsConst(container))
  1. После компиляции и запуска программы на Ubuntu с новой версией Qt 6.5 появляется сообщение об ошибке qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found. Невозможно нормально отобразить оконные программы. Необходимо вручную установить соответствующие библиотеки xcb. Выполните команду sudo apt install libxcb*.

  2. В некоторых случаях нам нужно выполнить определённую обработку перед QApplication a(argc, argv);, например, QApplication::setAttribute должен быть выполнен в первую очередь. Однако во многих случаях параметры этого параметра не могут быть жёстко заданы, поскольку условия среды могут сильно различаться. Желательно настроить их через файл конфигурации. Тогда возникает проблема: для нормального чтения файла конфигурации обычно требуется указать путь. Если это ./, то это, скорее всего, не текущий путь приложения. Если вы запускаете программу двойным щелчком мыши, то это точно текущий путь приложения, а если нет — это текущий путь системы. Это означает, что если вы запускаете приложение при загрузке системы или вызываете его запуск после загрузки с помощью system, QProcess и т.д., то путь может быть неправильным. Чтобы гарантировать правильность пути, необходимо получить первый аргумент main функции argv и получить путь, просмотрев код Qt.

// Программа сначала получает путь и имя приложения
static void getCurrentInfo(char *argv[], QString &path, QString &name);
// Программа сначала считывает значение узла файла конфигурации
static QString getIniValue(const QString &fileName, const QString &key);
static QString getIniValue(char *argv[], const QString &key, const QString &dir = QString());

void QtHelper::getCurrentInfo(char *argv[], QString &path, QString &name)
{
    // Необходимо использовать fromLocal8Bit для обеспечения корректного отображения китайских путей
    QString argv0 = QString::fromLocal8Bit(argv[0]);
    QFileInfo file(argv0);
    path = file.path();
    name = file.baseName();
}

QString QtHelper::getIniValue(const QString &fileName, const QString &key)
{
    QString value;
    QFile file(fileName);
    if (file.open(QFile::ReadOnly | QFile::Text)) {
        while (!file.atEnd()) {
            QString line = file.readLine();
            if (line.startsWith(key)) {
                line = line.replace("\n", "");
                line = line.trimmed();
                value = line.split("=").last();
                break;
            }
        }
    }
    return value;
}

QString QtHelper::getIniValue(char *argv[], const QString &key, const QString &dir)
{
    QString path, name;
    QtHelper::getCurrentInfo(argv, path, name);
    QString fileName = QString("%1/%2%3.ini").arg(path).arg(dir).arg(name);
    return getIniValue(fileName, key);
}

int main(int argc, char *argv[])
{
    int openGLType = QtHelper::getIniValue(argv, "OpenGLType").toInt();
    QtHelper::initOpenGL(openGLType);
    QApplication a(argc, argv);
    ...
}
  1. Когда мы выбираем строку в QTableView/QTreeView/QTableWidget/QTreeWidget, мы обнаруживаем, что цвет переднего плана некоторых ячеек перекрывается, например, установленный красный цвет становится белым после выбора. Это не то, что нам нужно, поэтому мы должны удалить его с помощью пользовательского делегата.
class ItemDelegate : public QItemDelegate
{
    Q_OBJECT
public:
    explicit ItemDelegate(QObject *parent = 0);

protected:
    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
};

#include "itemdelegate.h"

ItemDelegate::ItemDelegate(QObject *parent) : QItemDelegate(parent)
{

}

void ItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QStyleOptionViewItem option2 = option;
    QColor color = index.data(Qt::ForegroundRole).value<QColor>();
    if (color.isValid() && color != option.palette.color(QPalette::WindowText)) {
        option2.palette.setColor(QPalette::HighlightedText, color);
    }

    QItemDelegate::paint(painter, option2, index);
}

// Устанавливаем этот делегат для всех ячеек
ui->tableWidget->setItemDelegate(new ItemDelegate);
  1. Иногда нам нужно определить в файле проекта (pro/pri), существует ли в текущем наборе Qt определённый модуль и был ли он уже импортирован. Если модуль существует, он будет импортирован, и мы также хотим определить в коде, был ли импортирован определённый модуль, такой как модуль sql, чтобы выполнить соответствующую обработку после определения.
// Определение в файле проекта
// Если в текущем комплекте есть мультимедийный модуль, импортируем мультимедийный модуль
qtHaveModule(multimedia) {QT += multimedia}
// Модуль уже был импортирован в файл проекта через QT += multimedia
contains(QT, multimedia) {}

// Определение в исходном файле
#ifdef QT_MULTIMEDIA_LIB
    qDebug() << "мультимедийный модуль включён";
#else
    qDebug() << "мультимедийный модуль не включён";
#endif
  1. При установке прозрачного цвета фона для области MDI обнаруживается, что QMdiArea{background:transparent;} не работает, даже если указан цвет QMdiArea{background:#ff0000;} или QMdiArea{background-color:#ff0000;}. Оказывается, нужно использовать механизм слабых свойств. QMdiArea{qproperty-background:transparent;}.

  2. Когда в стиле включён отключённый стиль *:disabled{xxx}, обнаруживается, что дочернее окно MDI не может быть растянуто. Вероятно, это внутренняя ошибка Qt. Решение состоит в том, чтобы просто сбросить стиль границы отключённого класса для MDI SubWindow. QMdiSubWindow:disabled{border:8px solid rgba(0,0,0,0);}.

  3. При использовании QProcess для выполнения команды или запуска исполняемого файла стандартный метод не поддерживает пути с пробелами, такими как Program Files. Необходимо добавить двойные кавычки до и после пути. Возможно, внутренняя обработка приводит к сбою из-за разделения строки пробелами. Обычный путь с кавычками также может работать нормально, поэтому лучше добавить кавычки для надёжности.

QString cmd = "c:/Program Files/a.exe";
// Следующий код может вызвать сбой выполнения
``` ```
QProcess::startDetached(cmd);

// 前后加上引号就可以正常执行
cmd = "\"" + cmd + "\"";
QProcess::startDetached(cmd);
  1. В цикле берётся значение, определение временной переменной лучше стараться делать за пределами цикла, каждый раз определять её внутри цикла будет увеличивать накладные расходы, особенно это касается сложных типов, таких как QString (для базовых типов, например, int/bool разница не так велика), чем больше количество итераций цикла, тем больше разница в производительности.
void MainWindow::on_pushButton_clicked()
{
    QElapsedTimer timer;
    timer.start();

    QString s;
    QString text = "abc";
    for (int i = 0; i < 10000; ++i) {
        s = text.at(0);
    }

    qDebug() << "Способ 1" << timer.nsecsElapsed();
}


void MainWindow::on_pushButton_2_clicked()
{
    QElapsedTimer timer;
    timer.start();

    QString text = "abc";
    for (int i = 0; i < 10000; ++i) {
        QString s = text.at(0);
    }

    qDebug() << "Способ 2" << timer.nsecsElapsed();
}

// В режиме отладки способ 1 быстрее способа 2 в 6 раз+
// В релизной сборке способ 1 быстрее способа 2 в 30 раз+
  1. Механизм свойств в Qt очень мощный, он может использоваться не только для управления таблицей стилей, но и для удобной передачи значений, например, при передаче значений в QML. Иногда мы создаём универсальный класс, который должен уметь выполнять множество задач, но при этом хотим иметь доступ к некоторым специальным переменным. Один из способов — это прямое определение приватных переменных и предоставление функций get/set. Другой, более ленивый способ, заключается в использовании свойств setProperty/property. Qt сам управляет данными на уровне метаобъекта, поэтому нам не нужно писать соответствующие переменные и функции get/set в классе. Однако это, безусловно, снижает производительность, поскольку работает медленнее, чем использование переменных. Поэтому необходимо учитывать конкретные требования. Если вызовы setProperty/property не являются очень частыми, а универсальность важнее, то использование механизма свойств будет более удобным. Я лично рекомендую третий способ: наследовать универсальный класс и добавить в дочерний класс методы set/get.
void MainWindow::on_pushButton_clicked()
{
    QElapsedTimer timer;
    timer.start();

    for (int i = 0; i < 10000; ++i) {
        Test *t = new Test;
        // t->setId(i);
        // t->setName("test");
        t->getName();
    }

    qDebug() << "Способ 1" << timer.nsecsElapsed();
}

void MainWindow::on_pushButton_2_clicked()
{
    QElapsedTimer timer;
    timer.start();

    for (int i = 0; i < 10000; ++i) {
        Test *t = new Test;
        // t->setProperty("id", i);
        // t->setProperty("name", "test");
        t->property("name").toString();
    }

    qDebug() << "Способ 2" << timer.nsecsElapsed();
}

// Результаты сравнительного тестирования не зависят от типа данных и конкретного типа переменной/int и QString
// Производительность метода setProperty хуже, чем у метода setxxx в 3 раза+
// Метод property работает в 1.3 раза медленнее, чем метод getxxx

29: 281-290

  1. По умолчанию, когда вы наведете курсор на окно QDockWidget, появится контекстное меню для отображения или скрытия закреплённого модуля. Если вы хотите его убрать, то обнаружите, что установка Qt::NoContextMenu или перехват событий с помощью фильтра не работают. Необходимо установить dockWidget->setContextMenuPolicy(Qt::PreventContextMenu).

  2. В Qt есть значения по умолчанию для margin (поля) и spacing (расстояния) в макете. Если эти значения не установлены, они будут автоматически подобраны в зависимости от среды выполнения. Например, на компьютерах с разрешением 1080P и 2K эти значения могут отличаться. Важно помнить, что значения, которые вы видите в дизайнере интерфейса, могут не совпадать со значениями во время выполнения на целевой платформе. Если вам нужно контролировать эти значения, вы можете их переопределить. Для этого можно использовать универсальный подход — стиль-посредник. Создайте подкласс QProxyStyle и настройте стиль заново. Этот метод также является мощным инструментом для контроля внешнего вида пользовательского интерфейса. В конечном итоге все стили qss будут отображаться через этот стиль, что означает, что вы можете переопределить и контролировать внешний вид всех компонентов.

// Также можно наследовать встроенные стили Qt, такие как QFusionStyle/QCleanlooksStyle
class QCustomStyle : public QProxyStyle
{
public:
    int pixelMetric(PixelMetric metric, const QStyleOption *option = 0, const QWidget *widget = 0) const {

        if (metric == QStyle::PM_LayoutHorizontalSpacing || metric == QStyle::PM_LayoutVerticalSpacing) {
            // Установить горизонтальное и вертикальное расстояние в макете равным 10
            return 10;
        } else if (metric == QStyle::PM_ButtonMargin) {
            // Установить поля всех кнопок равными 20
            return 20;
        }
        return QProxyStyle::pixelMetric(metric, option, widget);
    }
};

qApp->setStyle(new QCustomStyle);
  1. Очень важно выработать хорошие привычки программирования, особенно в отношении инициализации переменных, включая классы объектов. Не забывайте инициализировать их после определения, иначе значения будут неопределёнными. Значения по умолчанию для int могут различаться в зависимости от режима сборки (debug/release), компилятора и даже места определения (заголовок или функция). Рекомендуется явно инициализировать переменные, например: int i = 0; bool b = false; class a = NULL;.
Версия Место определения Режим int bool
Qt4.7/mingw Заголовок Debug 7077464 true
Qt4.7/mingw Заголовок Release 48 true
Qt4.7/mingw Функция Debug 2162216 false
Qt4.7/mingw Функция Release 0 false
Qt5.7/msvc Заголовок Debug -1 true
Qt5.7/msvc Заголовок Release -1 true
Qt5.7/msvc Функция Debug 1898108572 false
Qt5.7/msvc Функция Release 18872512 true
Qt6.5/mingw Заголовок Debug -1305540880 true
Qt6.5/mingw Заголовок Release -1124044992 true
Qt6.5/mingw Функция Debug 0 false
Qt6.5/mingw Функция Release 0 false
  1. Операции полного выделения, отмены выделения и инвертирования выделения в QTableView. ``` ui->tableView->addAction(actionInvert); ui->tableView->addAction(actionClear); ui->tableView->setContextMenuPolicy(Qt::ActionsContextMenu); }

void frmXXX::doAction() { QAction *action = (QAction *)sender(); QString text = action->text(); if (text == "全部选中") { ui->tableView->selectAll(); } else if (text == "反向选中") { //找到所有选中的行集合 QList rows; QModelIndexList list = ui->tableView->selectionModel()->selectedRows(); int count = list.count(); for (int i = 0; i < count; ++i) { rows << list.at(i).row(); }

    //先清空所有选中
    ui->tableView->clearSelection();
    //不在选中行集合的则选中
    count = ui->tableView->model()->rowCount();
    for (int i = 0; i < count; ++i) {
        if (!rows.contains(i)) {
            ui->tableView->selectRow(i);
        }
    }
} else if (text == "清空选中") {
    ui->tableView->clearSelection();
}

}


```cpp
//表示安卓或者ios平台
android|ios {}

//表示非安卓和非ios平台
!android::!ios {}

//表示非ios系统的mac系统
maxc:!ios {}

//表示非mac系统的unix系统
unix:!macx {}

Многие проекты становятся всё больше и больше, и иногда можно столкнуться с ситуацией, когда код, который раньше работал нормально, вдруг начинает вызывать проблемы после добавления в текущий проект. Когда это происходит, рекомендуется использовать два подхода для решения проблемы:

  1. Метод комментирования: начните с функции main и закомментируйте всё, что не связано с проблемой. Тщательно проверьте процесс выполнения, пока не найдёте код, вызывающий проблему.
  2. Напишите простой пример, демонстрирующий проблему. Это упростит поиск проблемы. Часто после написания простого примера код начинает работать нормально.

Сейчас многие Linux-системы используют Wayland в качестве рабочего стола. Это может привести к проблеме, так как отсутствие системы координат делает невозможным перемещение и позиционирование окон без границ (обычно Qt6 принудительно использует Wayland по умолчанию, а в Qt5 используется xcb). Чтобы решить эту проблему, добавьте следующую строку перед функцией main: qputenv("QT_QPA_PLATFORM", "xcb");

Иногда после экспорта файла вы хотите открыть файл в файловом менеджере и выделить только что открытый файл, чтобы пользователь мог его обработать. Для этого нужно выполнить команду:

QString path = "file:///e:/1.txt";
QProcess::startDetached("explorer.exe", QStringList() << "/select," << path);

В сигналах currentItemChanged у QTreeWidget/QTableWidget при выполнении соответствующего метода clear также активируется этот сигнал, поэтому необходимо обратить на это особое внимание. Два параметра, связанные с этим сигналом, current/previous, обозначают текущий и предыдущий узлы. Значения обоих параметров могут быть пустыми, поэтому при обработке параметров слота для этого сигнала необходимо сначала проверить, являются ли они пустыми указателями. Если этого не сделать, это может привести к сбою программы.

О разнице между += и *= в Qt: += означает добавление без дублирования, а *= — добавление без дублирования. Рекомендуется использовать *=, хотя += также работает нормально.

QT += core gui
QT += core gui
message($$QT) //会打印 core gui core gui

QT *= core gui
QT *= core gui
message($$QT) //会打印 core gui

DEFINES += abc
DEFINES += abc
message($$DEFINES) //会打印 abc abc

DEFINES *= abc
DEFINES *= abc
message($$DEFINES) //会打印 abc

30:291-300

Что касается различения Linux-систем в файле pro, то в комплекте Qt4 нет поддержки тега linux, поэтому нужно использовать unix:!macx для обозначения. Поэтому, если требуется совместимость с Qt4, рекомендуется использовать unix:!macx.

//如果是linux上的Qt4套件则下面只会打印 unix linux
//如果是linux上的Qt5/Qt6套件则下面会打印 linux unix linux
linux {message(linux)}
unix {message(unix)}
unix:!macx {message(linux)}

Для некоторых кроссплатформенных проектов, особенно тех, которые требуют использования сторонних библиотек, необходимо идентифицировать и обрабатывать соответствующие файлы библиотеки в разных системных архитектурах.

#区分不同的系统
path_sys = win
win32 {
path_sys = win
}

linux {
path_sys = linux
}

#Qt4套件不认识linux标记
unix:!macx {
path_sys = linux
}

macx {
path_sys = mac
}

android {
path_sys = android
}

#区分不同的位数 x86_64/amd64/arm64/arm64-v8a
path_bit = 32
contains(QT_ARCH, x.*64) {
path_bit = 64
} else:contains(QT_ARCH, a.*64) {
path_bit = 64
} else:contains(QT_ARCH, a.*64.*) {
path_bit = 64
}

#对应系统和位数的库目录
path_lib = lib$$path_sys$$path_bit
//下面会打印 libwin32/libwin64/liblinux32/liblinux64/libmac32/libmac64/libandroid32/libandroid64
message($$path_lib)

//使用方式
INCLUDEPATH += $$PWD/include
LIBS += -L$$PWD/$$path_lib/ -lxxx

Если во время сборки обнаружено, что некоторые компоненты Qt не соответствуют требованиям текущего проекта, можно отключить проект, чтобы избежать сбоя компиляции.

#禁用项目后整个项目的代码文件是灰色的不可用,编译会跳过。
lessThan(QT_MAJOR_VERSION, 6) {
error("最低要求Qt6才能用")
}

При использовании QButtonGroup для добавления кнопок и необходимости использования сигнала buttonClicked(int), необходимо вручную указать номер кнопки при добавлении. В противном случае номера кнопок по умолчанию будут равны -1, что приведёт к несоответствию ожидаемым значениям при срабатывании сигнала buttonClicked(int).

QButtonGroup *btnGroup = new QButtonGroup(this);
connect(btnGroup, SIGNAL(buttonClicked(int)), ui->stackedWidget, SLOT(setCurrentIndex(int)));
//第二个参数指定按钮编号
btnGroup->addButton(ui->btn1, 0);
btnGroup->addButton(ui->btn2, 1);

Улучшение производительности графиков QCustomplot:

  • Избегайте линий с шириной больше 1 (по умолчанию). Если объём данных большой, не устанавливайте ширину линии больше 1, так как это сильно снизит производительность.
  • Избегайте сложных заливок, например, заливки каналов между графиками с тысячами точек.
  • Во время перетаскивания графика можно установить setNoAntialiasingOnDrag(true) для повышения скорости отклика.
  • Не используйте альфа-цвета (прозрачность) любого типа, особенно при заливке.
  • По возможности не включайте сглаживание, установив setNotAntialiasedElements(true). 301. Избегайте повторной установки полного набора данных, например, используя setData. Если большинство точек данных остаются неизменными, например, во время выполнения измерений, используйте addData вместо этого.
  • Рекомендуется использовать QCPGraph::data() для доступа к существующим данным и работы с ними, это более эффективно.
  • Чтобы включить ускорение OpenGL, сначала добавьте в pro строку DEFINES += QCUSTOMPLOT_USE_OPENGL. Затем свяжите библиотеки opengl, добавив LIBS += -lopengl32 -lglu32. Для некоторых версий Qt может потребоваться явно добавить QT += widgets. Эксперименты показали, что при высокой частоте отрисовки, например, 60 кадров в секунду, включение OpenGL улучшает производительность, снижая нагрузку на CPU. При низкой частоте отрисовки нагрузка на CPU увеличивается. Поэтому рекомендуется подходить к этому вопросу в зависимости от конкретной ситуации. Это не связано с объёмом данных, а скорее со скоростью отрисовки данных.
  • Отключите сглаживание на холсте, установив graph->setAntialiased(false), graph->setAntialiasedFill(false) и graph->setAntialiasedScatters(false). По умолчанию установлено значение true.
  • Включите адаптивную выборку на холсте, установив graph->setAdaptiveSampling(true). По умолчанию уже установлено значение true, поэтому дополнительная настройка не требуется.
  1. После установки текущего каталога с помощью QDir::setCurrent, это повлияет на выполнение всех относительных каталогов в программе, что может привести к непредвиденным результатам. Обычно относительные каталоги по умолчанию являются каталогом исполняемого файла, поэтому если программа временно устанавливает другой относительный каталог с помощью QDir::setCurrent для специальной обработки, рекомендуется вернуться к исходному каталогу после завершения обработки.
QDir::setCurrent("f:/");
QImage img(":/image/bg_novideo.png");
// Результаты изображения сохраняются в f:/1.jpg
img.save("1.jpg", "jpg");
img.save("./1.jpg", "jpg");

// Правильный подход
// Сначала запомните текущий каталог
QString path = QDir::currentPath();
QDir::setCurrent("f:/");
xxxxxxx выполнение задачи

// Вернитесь к исходному каталогу
QDir::setCurrent(path);
// Результаты изображения сохраняются в текущем каталоге
img.save("1.jpg", "jpg");
  1. Примерно начиная с версии Qt 6.4, если в проекте используются как QOpenGLWidget, так и элемент управления веб-браузера QWebEngine, может возникнуть проблема с отображением веб-страниц в QWebEngine. Это проявляется в версиях 6.4.3 и 6.5.3. Согласно документации на официальном сайте https://doc.qt.io/qt-6/qquickwidget.html#graphics-api-support, необходимо добавить дополнительную строку кода.
#include "qquickwindow.h"
int main(int argc, char *argv[])
{
    QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL);
    QApplication a(argc, argv);
}
  1. Теперь новые версии Qt доступны только для онлайн-загрузки. Иногда в процессе загрузки появляется сообщение об ошибке, вероятно, из-за отсутствия упаковки некоторых плагинов-модулей на соответствующих серверах. Обычно это модули, заканчивающиеся на TP. Эти модули обычно не используются, поэтому для успешной установки нужно зайти в раздел выбора версии Qt и плагинов, открыть узел Additional Libraries, снять флажки с ненужных модулей, особенно тех, которые заканчиваются на TP, и продолжить установку.

  2. В Qt можно использовать qputenv и qgetenv для установки и получения системных переменных окружения. Можно установить соответствующие значения в коде или в системных переменных окружения, например, в Windows. Это позволяет гибко настраивать значения переменных. Если код не может быть изменён, можно попробовать установить значение переменной окружения, распознаваемое Qt, чтобы добиться желаемого эффекта. Важно отметить, что изменения в переменных окружения вступят в силу только после перезагрузки системы или перезапуска Qt Creator, возможно, из-за механизма кэширования.

// Устанавливаем отдельные дескрипторы для каждого окна
QApplication a(argc, argv);
a.setAttribute(Qt::AA_NativeWindows);

// Вышеуказанный метод устанавливает через код / иногда уже в исполняемом файле / невозможно изменить код
// После изучения кода стало известно, что предпочтение отдаётся чтению через qgetenv, есть ли флаг QT_USE_NATIVE_WINDOWS
// Если присутствует, то значение присваивается в соответствии со значением переменной QT_USE_NATIVE_WINDOWS
// Недостаток этого метода в том, что он применяется ко всем программам Qt
  1. Параметры функции setContentsMargins для макетов имеют порядок слева сверху справа снизу, в то время как поля в qss задаются в порядке сверху справа снизу слева, что часто вызывает путаницу.

31: 301–310

  1. Начиная с версии Qt 5.2, элемент управления текстовым полем QLineEdit предоставляет функцию setClearButtonEnabled для включения или выключения кнопки очистки в правой части. Этот элемент управления очень распространён; например, можно добавить кнопку поиска. Как это сделать? До версии 5.2 нужно было самостоятельно определять макет и создавать кнопку, размещая её справа от макета. С версии 5.2 появился перегруженный метод addAction, который позволяет добавлять действие перед или после текстового поля, автоматически оставляя поля.
#if (QT_VERSION < QT_VERSION_CHECK(5,2,0))
    // Универсальный метод, совместимый со всеми версиями Qt
    QPushButton *searchButton = new QPushButton;
    // Выполнение соответствующей обработки
    connect(searchButton, SIGNAL(clicked(bool)), this, SLOT(search()));
    searchButton->setMinimumWidth(30);
    searchButton->setIcon(QIcon(":/main.ico"));

    // Создание экземпляра макета для размещения кнопки
    QHBoxLayout *layout = new QHBoxLayout(ui->lineEdit);
    layout->setContentsMargins(0, 0, 1, 0);
    // Добавление кнопки с указанием выравнивания
    layout->addWidget(searchButton, 0, Qt::AlignRight);
    // Установка внешних отступов текста / свободное место для кнопки
    ui->lineEdit->setTextMargins(0, 0, searchButton->minimumWidth() + 3, 0);
#else
    // Рекомендуемый метод проще в использовании
    QAction *searchAction = new QAction(ui->lineEdit);
    // Выполнение соответствующей обработки
    connect(searchAction, SIGNAL(triggered(bool)), this, SLOT(search()));
    searchAction->setIcon(QIcon(":/main.ico"));
    // TrailingPosition указывает на правую сторону / также может быть LeadingPosition для левой стороны
    ui->lineEdit->addAction(searchAction, QLineEdit::TrailingPosition);
#endif
  1. Примерно с версии 6.5 при компиляции отладочной сборки популярного графического виджета QCustomPlot с использованием mingw возникает ошибка «too many sections/file too big». Однако при использовании release-сборки или другого компилятора проблем не возникает. Достаточно добавить в файл pro следующую строку: QMAKE_CXXFLAGS += -Wa,-mbig-obj.

  2. Примерно с начала 2024 года инструмент онлайн-установки Qt по умолчанию не загружает установочные пакеты Qt5. Необходимо отметить опцию Archive в правом верхнем углу, затем отфильтровать результаты, нажав на кнопку Filter, после чего станут видны пакеты Qt5. Вероятно, официальный сайт стремится побудить нас использовать Qt6, постепенно отказываясь от поддержки Qt5. Жаль, что Qt6 не поддерживает Windows 7, хотя у этой операционной системы всё ещё много пользователей.

  3. При использовании функции drawText в Qt для рисования текста, если используются координаты QPoint, важно помнить, что начало координат находится в левом нижнем углу (как указано в документации Qt: «The y-position is used as the baseline of the font»). Это отличается от других фреймворков разработки, таких как C#. При работе с другими платформами рекомендуется использовать функцию void drawText(const QRect &r, const QString &text), которая позволяет указать область для отрисовки. Эту особенность легко упустить из виду, что может привести к катастрофическим последствиям.

  4. При открытии веб-страницы с использованием модуля веб-браузера webengine в Linux некоторые системы могут столкнуться с аварийными сбоями. Даже при запуске встроенного примера браузера simplebrowser страница не открывается должным образом. Причина кроется в мерах безопасности, связанных с песочницей. Чтобы решить эту проблему, необходимо установить переменную окружения. Просто добавьте строку qputenv("QTWEBENGINE_DISABLE_SANDBOX", "1") в начале функции main. 305. После установки флажка в QListWidgetItem иногда требуется сигнал уведомления при переключении флажков, чтобы можно было выполнить обработку. Здесь вы обнаружите, что среди всех сигналов QListWidget такого сигнала нет. Изучив исходный код функции setCheckState класса QListWidgetItem, вы узнаете, что отправляется сигнал dataChanged, который генерируется моделью данных QListWidget, так что всё становится проще.

void Form::on_listWidget_itemPressed(QListWidgetItem *item)
{
    // Переключение состояния выбора при нажатии мыши
    item->setCheckState(item->checkState() == Qt::Checked ? Qt::Unchecked : Qt::Checked);
}

void Form::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
    // Поиск узла через текст, а не через выбранный узел, потому что можно установить флажок перед текущим элементом
    QListWidgetItem *item;
    QString text = topLeft.data().toString();
    int count = ui->listWidget->count();
    for (int i = 0; i < count; ++i) {
        item = ui->listWidget->item(i);
        if (item->text() == text) {
            break;
        }
    }

    // Обработка после нахождения соответствующего узла
}
  1. При удалённом анализе проблем на компьютерах многих пользователей было обнаружено, что данные проблемы имеют общую причину: хотя некоторые файлы базы данных или конфигурационные файлы были явно изменены, при запуске программы старые данные всё равно отображались. Несмотря на тщательное изучение программы, источник проблемы не был найден. В конце концов выяснилось, что программа считывала файл, который не был изменён. Это произошло из-за наличия нескольких копий одного и того же каталога кода в среде пользователя. Открываемый каталог был другой копией, и все изменения не влияли на текущий исполняемый файл программы. В области больших данных и статистики такая ситуация встречается довольно часто, составляя примерно одну пятую часть всех случаев. Наличие нескольких копий кода часто неизбежно, например, для временного резервного копирования кода перед тестированием новых функций. Однако важно помнить о текущем состоянии проекта и проверять правильность каталога при возникновении проблем, чтобы убедиться, что используется правильный каталог кода.

  2. При использовании большого количества сторонних библиотек можно заметить, что некоторые из них зависят от компилятора. Например, OpenCV, скомпилированный с помощью MSVC, может быть использован только с этим компилятором. Если попытаться использовать Mingw, то возникнет ошибка. Для успешного использования Mingw необходимо скомпилировать OpenCV с ним. С другой стороны, библиотеки ffmpeg не имеют такой зависимости, и могут быть использованы как с MSVC, так и с Mingw. Это связано с тем, что ffmpeg является чистым C проектом, в то время как OpenCV — это чистый C++ проект, который включает в себя проблемы ABI, характерные для C++. C++ — это сложный язык программирования, поддерживающий наследование и полиморфизм, поэтому компилятору необходимо определить точные правила вызова функций, включая соглашения о вызовах (различие имён функций, ввод параметров, управление стеком), возвращаемые типы и списки параметров. Разные компиляторы могут иметь разные правила для этих аспектов даже на одной архитектуре. Таким образом, библиотеки, скомпилированные из чистого C проекта, не требуют учёта компилятора, в то время как библиотеки C++ требуют этого. Автор QtAV создал новый проект MDK, написанный на чистом C, который предоставляет библиотеки для Mingw и MSVC. Библиотеки, скомпилированные любым компилятором, совместимы как с Mingw, так и с MSVC. Это преимущество чистого C подхода.

  3. Иногда нам нужно записать файл на диск, но если соответствующий каталог не существует, запись может завершиться неудачно. В этом случае необходимо сначала проверить существование каталога. Если его нет, то создать его. QDir предоставляет два метода для создания каталогов: mkdir и mkpath. Раньше считалось, что эти методы выполняют одинаковые функции, подобно size и length, но это не так. mkdir создаёт только последний каталог в пути. Если родительский каталог не существует, создание завершается неудачей. Mkpath, с другой стороны, проверяет существование каждого уровня пути и создаёт недостающие каталоги. Рекомендуется использовать mkpath для обеспечения успешного создания всего пути.

QDir dir;
// Создание каталога "f:/path/dir"
dir.mkdir("f:/path/dir");
// Сначала будет создан "path", затем "dir"
dir.mkpath("f:/path/dir");
  1. При добавлении JS файла в проект Widget и его компиляции может возникнуть ошибка qmlcache_loader.o:qmlcache_loader.cpp:(.text+0x32). Это происходит потому, что Qt по умолчанию включает qtquickcompiler для предварительной обработки всех JS файлов в ресурсах, ускоряя их загрузку в QML движок. Однако в некоторых случаях мы используем эти файлы не для загрузки в QML, а, например, в QtWebEngine для взаимодействия или выполнения JS функций в Widget для получения результатов. Можно отключить qtquickcompiler в настройках проекта Qt Creator или добавив строку CONFIG -= qtquickcompiler в файл pro. Эта проблема специфична для Qt и не присутствует в версии 5.15.2 и более поздних.

  2. По умолчанию, новый QtCreator использует build директорию внутри исходного кода для компиляции. Ранее использовалась отдельная директория build-xxx на том же уровне, что и исходный код. Лично мне больше нравится предыдущий подход, когда все временные файлы компиляции хранятся отдельно от исходного кода. Их можно легко удалить, когда они не нужны, оставляя исходный код чистым. Qt Creator также предоставляет возможность настроить путь к директории компиляции в настройках. Перейдите в раздел «Настройки» → «Сборка и запуск» → «Свойства сборки по умолчанию» и измените путь к папке компиляции на ../build-%{Project:Name}-%{Kit:FileSystemName}-%{BuildConfig:Name}.

  3. Несколько замечаний о задержках в потоковом мультимедиа.

  • Часто встречаются программы, связанные с потоковым мультимедиа, которые утверждают, что обеспечивают нулевую задержку. Не стоит сомневаться в этом, скорее всего, это преувеличение.
  • В разработке аудио- и видеоприложений ключевым показателем является время отклика, то есть задержка в миллисекундах. Этот вопрос задаётся чаще всего.
  • Аудио- и видеофайлы практически не имеют проблем со временем отклика, только аудио- и видеотрафик имеют показатели времени отклика.
  • Задержка зависит от многих факторов, и важно понимать, как её измерять. Следует ли начинать измерение с момента начала передачи потока или с момента его получения?
  • Многие люди не понимают, что такое задержка, считая, что разница во времени между воспроизведением видео и исходным потоком является задержкой. На самом деле, это серьёзное заблуждение. Задержка — это не просто видимость, многие люди проводят тестирование задержки непрофессионально, не понимая сути процесса.
  • Ниже приведены основные аспекты задержки, описанные автором ZLM.
  • Задержка сбора данных: при сборе данных с камеры или видеокарты из-за ограничений FPS и производительности CPU, скорости копирования памяти и т.д., возникает задержка при преобразовании данных в формат YUV/RGB. Обычно эта задержка составляет несколько миллисекунд. Из-за ограничений кодировщика по входному формату данных, таких как требование YUV420P, преобразование RGB в YUV420P также добавляет задержку (которую можно уменьшить с помощью библиотеки libyuv). Таким образом, общая задержка сбора данных составляет миллисекунды. Если FPS равен 25, задержка сбора данных может составлять около 40 мс (1000 мс / 25 FPS) и более.
  • Кодирование задержки: при передаче данных в кодировщик, кодирование не происходит мгновенно, особенно при использовании B-кадров, требующих ссылки на последующие P-кадры, что увеличивает задержку. Поэтому в чувствительных к задержке ситуациях B-кадры обычно не используются. Задержка кодирования обычно составляет миллисекунды.
  • Задержка передачи данных: после кодирования данные передаются через сеть. Хотя этот процесс включает копирование памяти и вычисления, он добавляет лишь небольшую задержку, которую можно игнорировать. Задержка сильно варьируется в зависимости от качества сети.
  • Преобразование задержки: серверу требуется время на чтение данных из сокета, их анализ и повторное пакетирование. Однако эта задержка обычно невелика. Иногда сервер объединяет пакеты данных для повышения производительности, что может добавить задержку в несколько сотен миллисекунд (около 300 мс в ZLMediaKit, но по умолчанию эта функция отключена).
  • Задержка приёма данных: задержка приёма данных зависит от качества сети и дополнительных настроек, таких как использование MSG_MORE и отключение TCP_NODELAY в ZLMediaKit.
  • Воспроизведение задержки: задержка воспроизведения состоит из сетевой задержки приёма, задержки анализа протокола, декодирования, буферизации и рендеринга. Наибольшую задержку вызывает буферизация, поскольку для обеспечения плавности воспроизведения при нестабильном интернете, плеер увеличивает задержку за счёт увеличения буфера. Также для синхронизации аудио и видео требуется определённый буфер. Общая задержка воспроизведения обычно составляет секунды (около 5 секунд). Некоторые стратегии воспроизведения позволяют минимизировать задержку путём немедленного декодирования и отображения полученных данных. Кэширование с задержкой: потоковый сервер, чтобы обеспечить мгновенное отображение на плеере, обычно кэширует последний I-кадр. Все аудио- и видеоданные после этого I-кадра называются кэшем GOP. Если не кэшировать GOP, то плеер должен дождаться следующего I-кадра для успешного декодирования или отображения, что очевидно приведёт к зависанию экрана. Очевидно, что для улучшения качества воспроизведения этот кэш GOP нельзя удалить. Обычно длительность GOP составляет от 1 до 3 секунд, а в некоторых случаях — несколько десятков секунд, это зависит от настроек кодировщика на стороне сбора данных, и сервер не может это изменить. Однако после получения кэша большинство плееров не отбрасывают слишком много кадров, чтобы гарантировать низкую задержку. Кроме того, плеер также хочет иметь определённый кэш, чтобы обеспечить плавность воспроизведения, поэтому кэш GOP увеличит задержку плеера.

Комплексная задержка: минимальная задержка может составлять 200–300 мс, например, для rtsp-видеопотока, если требуется высокая степень реалтайма, можно обойтись без кэширования и синхронизации аудио и видео, декодировать и воспроизводить сразу после получения. Для hls максимальная задержка обычно составляет около 5 секунд, а для flv — около 3 секунд.

Окончательный вывод: учитывая как реалтайм, так и поддерживаемые форматы аудио и видео, я лично рекомендую использовать потоковую передачу rtsp (поддерживаются самые удобные форматы аудио и видео, например, поддерживается кодек 265), а потоковую передачу в веб — ws-flv (поддерживается множество форматов, нет ограничения на шесть источников). Для потоковой передачи в исполняемый файл рекомендуется использовать rtsp (множество поддерживаемых форматов и наилучший реалтайм, возможна декодирование и воспроизведение с максимальной скоростью), а для веб-страницы хотя webrtc и обеспечивает наилучший реалтайм, но не поддерживает кодек 265, что усложняет задачу.

312. Установка китайского языка для кнопок в диалоговых окнах Qt

// Установка китайского языка в информационном окне
QMessageBox dialog(QMessageBox::Question, "询问", text);
dialog.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
dialog.button(QMessageBox::Yes)->setText("确 定");
dialog.button(QMessageBox::No)->setText("取 消");
return dialog.exec();

// Установка китайского языка в поле ввода
QInputDialog dialog;
dialog.setOkButtonText("确定");
dialog.setCancelButtonText("取消");
return dialog.exec();

// Установка китайского языка в диалоговом окне файла
QFileDialog dialog;
dialog.setOption(QFileDialog::DontUseNativeDialog, true);
QLabel *lookinLabel = dialog.findChild<QLabel*>("lookInLabel");
lookinLabel->setText("文件目录:");

313. Иногда требуется выполнить поиск и фильтрацию отображаемых узлов дерева, отображая совпадающие узлы и скрывая несовпадающие. Эта функция используется во многих местах и инкапсулирована в виде статической функции, которую можно вызвать напрямую.

// Вызов метода QtHelper::search(ui->treeWidget, "测试", 5);
void QtHelper::search(QTreeWidget *treeWidget, const QString &key, int level)
{
    // Найти все совпадающие узлы
    QList<QTreeWidgetItem *> items = treeWidget->findItems(key, Qt::MatchContains | Qt::MatchRecursive);

    // Добавить совпадающие узлы в очередь / родительский узел узла также считается совпадением / иначе родительский узел скроет дочерние узлы, которые также будут скрыты
    QList<QTreeWidgetItem *> itemAll;
    foreach (QTreeWidgetItem *item, items) {
        // Добавить всех родительских узлов текущего узла / несколько циклов соответствуют нескольким уровням
        for (int i = 0; i < level; ++i) {
            // Избегаем дублирования добавления
            if (!itemAll.contains(item)) {
                itemAll << item;
            }
            // Пустой указатель означает отсутствие родительского узла, выход из цикла
            item = item->parent();
            if (!item) {
                break;
            }
        }
    }

    // Пройти по всем узлам / отобразить совпадающие узлы, иначе скрыть их
    QTreeWidgetItemIterator it(treeWidget);
    while (*it) {
        (*it)->setHidden(!itemAll.contains(*it));
        ++it;
    }
}

2 Обновление до Qt6

00: Краткое резюме

  1. Добавлено множество функций, при этом существующие модули стали более детализированными, предположительно для удобства расширения и управления.
  2. Убраны некоторые чрезмерно инкапсулированные функции (например, одна и та же функция имеет несколько реализаций), обеспечивая наличие только одной функции для каждой функции.
  3. Некоторые методы, совместимые с Qt4 в Qt5, были упразднены, требуя использования соответствующих новых функций в Qt5.
  4. В соответствии с требованиями времени добавлены многочисленные новые функции для удовлетворения растущих потребностей клиентов.
  5. Кардинально переписаны некоторые модули и типы обработки, значительно повысив эффективность выполнения.
  6. Произошли изменения в типах параметров, таких как long * на qintptr *, для лучшей совместимости с будущими расширениями и поддержки 32- и 64-битных систем.
  7. Во всём исходном коде тип данных double был заменён на qreal, обеспечивая высокую согласованность и унификацию с внутренними типами данных Qt.
  8. Я тестировал только часть QWidget, quick часть не тестировалась, предполагается, что обновления в quick могут быть более значительными.
  9. Настоятельно рекомендуется временно избегать использования версий между Qt6.0 и Qt6.2, поскольку некоторые модули отсутствуют, и эти версии содержат больше ошибок. Рекомендуется начать официальный переход с версии 6.2.2.

01: 01-10

  1. Универсальный метод: установите версию 5.15, найдите функцию, вызывающую ошибку, переключитесь на исходный файл заголовка и увидите соответствующие подсказки, такие как QT_DEPRECATED_X("Use sizeInBytes"), и новую функцию. Измените код в соответствии с этими подсказками. Некоторые функции были добавлены в версиях Qt5.7, 5.9, 5.10 и т. д., возможно, ваш проект всё ещё использует методы Qt4, но Qt6 полностью совместим с этими старыми методами. До Qt6 необходимо было использовать новые методы. PS: если сама функция является новой функцией в Qt6, этот метод не работает.

  2. Qt6 разделил ядро core на core5compat, поэтому вам нужно добавить соответствующий модуль в pro-файл и включить соответствующий заголовок в коде.

// Добавление модуля в pro файл
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
greaterThan(QT_MAJOR_VERSION, 5): QT += core5compat

// Включение заголовка в код
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
#include <QtWidgets>
#endif
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
#include <QtCore5Compat>
#endif
  1. По умолчанию Qt6 включает поддержку высокого разрешения, интерфейс становится очень большим, шрифт кажется размытым, многие люди могут чувствовать себя некомфортно. Поскольку в этом режиме многие вычисления координат не используют devicePixelRatio, координаты будут неточными на 100%, что вызовет странные проблемы. Чтобы отменить этот эффект, вы можете установить коэффициент масштабирования высокого разрешения.
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
    QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Floor);
#endif
  1. Существующие функции случайных чисел предлагают заменить их на QRandomGenerator. Чтобы обеспечить совместимость со всеми версиями qt, минимальные изменения заключаются в использовании случайных чисел C++, таких как srand вместо qsrand и rand вместо qrand. После проверки исходного кода выясняется, что они фактически инкапсулированы в случайные числа C++. Многие аналогичные инкапсуляции, такие как qSin, инкапсулируют sin.

  2. light и dark в QColor заменены на lighter и darker соответственно, хотя методы lighter и darker всегда существовали.

  3. fm.width в QFontMetricsF заменен на fm.horizontalAdvance начиная с версии 5.11.

  4. Значения перечисления QPalette, Foreground = WindowText, Background = Window, где Foreground и Background больше не доступны, должны быть заменены на WindowText и Window соответственно. Аналогично, setTextColor заменен на setForeground.

  5. delta() в QWheelEvent заменено на angleDelta().y(), а pos() заменено на position().

  6. Модуль svg был разделен на svgwidgets, если вы используете этот модуль, вам нужно добавить QT += svgwidgets в pro. То же самое относится и к модулю opengl, который был разделен на openglwidgets. contentsMargins().left(),когда четыре значения одинаковы, по умолчанию все четыре значения будут одинаковыми. Аналогично, setMargin был удалён, полностью используется setContentsMargins.

02:11-20

  1. Предыдущий QChar c = 0xf105 должен быть заменён на принудительное преобразование QChar c = (QChar)0xf105, неявное преобразование больше не поддерживается, иначе компиляция выдаст ошибку error: conversion from 'int' to 'QChar' is ambiguous.

  2. Некоторые функции, такие как qSort, были заменены на std::sort из C++.

#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
    std::sort(ipv4s.begin(), ipv4s.end());
#else
    qSort(ipv4s);
#endif
  1. Qt::WA_NoBackground был заменён на Qt::WA_OpaquePaintEvent.

  2. Класс QMatrix был упразднён, вместо него используется QTransform, функции класса в основном аналогичны, класс QTransform существовал в Qt4.

  3. QTime для отсчёта времени был исключён, его следует заменить на QElapsedTimer, этот класс также присутствовал в Qt4.

  4. Функция QApplication::desktop() была удалена, вместо неё используется QApplication::primaryScreen().

#if (QT_VERSION > QT_VERSION_CHECK(5,0,0))
#include "qscreen.h"
#define deskGeometry qApp->primaryScreen()->geometry()
#define deskGeometry2 qApp->primaryScreen()->availableGeometry()
#else
#include "qdesktopwidget.h"
#define deskGeometry qApp->desktop()->geometry()
#define deskGeometry2 qApp->desktop()->availableGeometry()
#endif
  1. Получение текущего индекса экрана и размера требует отдельной обработки.
// Получение текущего индекса экрана
int QtHelper::getScreenIndex()
{
    // Необходимо обработать несколько экранов
    int screenIndex = 0;
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
    int screenCount = qApp->screens().count();
#else
    int screenCount = qApp->desktop()->screenCount();
#endif

    if (screenCount > 1) {
        // Нахождение экрана, на котором находится курсор
        QPoint pos = QCursor::pos();
        for (int i = 0; i < screenCount; ++i) {
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
            if (qApp->screens().at(i)->geometry().contains(pos)) {
#else
            if (qApp->desktop()->screenGeometry(i).contains(pos)) {
#endif
                screenIndex = i;
                break;
            }
        }
    }
    return screenIndex;
}

// Получение области экрана
QRect QtHelper::getScreenRect(bool available)
{
    QRect rect;
    int screenIndex = QtHelper::getScreenIndex();
    if (available) {
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
        rect = qApp->screens().at(screenIndex)->availableGeometry();
#else
        rect = qApp->desktop()->availableGeometry(screenIndex);
#endif
    } else {
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
        rect = qApp->screens().at(screenIndex)->geometry();
#else
        rect = qApp->desktop()->screenGeometry(screenIndex);
#endif
    }
    return rect;
}
  1. Класс QRegExp был перемещён в модуль core5compat, требуется активное включение заголовочного файла #include "QRegExp".
    // Установка ограничения на ввод только цифр и десятичных знаков
    QString pattern = "^-?[0-9]+([.]{1}[0-9]+){0,1}$";
    // Настройка проверки IP-адреса
    QString pattern = "(2[0-5]{2}|2[0-4][0-9]|1?[0-9]{1,2})";

    // Точное выражение QRegularExpression QRegularExpressionValidator доступно с версии 5.0 5.1
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
    QRegularExpression regExp(pattern);
    QRegularExpressionValidator *validator = new QRegularExpressionValidator(regExp, this);
#else
    QRegExp regExp(pattern);
    QRegExpValidator *validator = new QRegExpValidator(regExp, this);
#endif
    lineEdit->setValidator(validator);
  1. Параметры конструктора QWheelEvent и соответствующая функция вычисления ориентации изменились.
// Имитация колёсика мыши
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
QWheelEvent wheelEvent(QPoint(0, 0), -scal, Qt::LeftButton, Qt::NoModifier);
#else
QWheelEvent wheelEvent(QPointF(0, 0), QPointF(0, 0), QPoint(0, 0), QPoint(0, -scal), Qt::LeftButton, Qt::NoModifier, Qt::ScrollBegin, false);
#endif
QApplication::sendEvent(widget, &wheelEvent);

// Прямое изменение значений колёсика мыши
QWheelEvent *whellEvent = (QWheelEvent *)event;
// Угол прокрутки, *8 — расстояние прокрутки мыши
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
int degrees = whellEvent->delta() / 8;
#else
int degrees = whellEvent->angleDelta().x() / 8;
#endif
// Количество прокрутки,*15 — угол прокрутки мышью
int steps = degrees / 15;
  1. qVariantValue был заменён на qvariant_cast, а qVariantSetValue(v, value) заменено на v.setValue(val). Это эквивалентно возвращению к исходному методу, просмотр исходного кода qVariantValue показывает, что он инкапсулирован в qvariant_cast.

03:21-30

  1. init в QStyleOption был заменён на initFrom.

  2. QVariant::Type был заменён на QMetaType::Type, предыдущий QVariant::Type фактически инкапсулировал QMetaType::Type.

  3. Все версии QStyleOptionViewItemV2 V3 V4 и т. д. были исключены, временно можно использовать QStyleOptionViewItem в качестве замены.

  4. Одна из перегруженных функций resolve в QFont была заменена на resolveMask.

  5. Метод setIniCodec в QSettings был удалён, по умолчанию используется utf8, настройка не требуется.

  6. Сигналы activated(QString) и currentIndexChanged(QString) в qcombobox были удалены, используется версия с параметром int index, затем значение получается через индекс. Лично я считаю, что удалять их не было необходимости. Модуль qtscript полностью исчез, хотя ещё с поздних версий Qt5 он был объявлен устаревшим и продолжал существовать до тех пор, пока его официально не упразднили в Qt6. Все виды анализа данных JSON были заменены на анализ класса qjson.

  7. Многие перегруженные функции QString параметров методов append, indexOf, lastIndexOf и других подобных для QByteArray были упразднены. Чтобы передать QByteArray напрямую, нужно добавить .toUtf8() к исходной основе параметров. Это видно при просмотре исходного кода, где прежние параметры QString также преобразуются в .toUtf8(), а затем используются для сравнения.

  8. Функции преобразования времени QDateTime toTime_t и setTime_t были переименованы в toSecsSinceEpoch и setSecsSinceEpoch соответственно. Эти два метода были добавлены в Qt5.8.

  9. Функция pixmap для QLabel ранее была указателем (*pixmap()), теперь она заменена ссылкой (pixmap()).

04:31-40

  1. В методе sortByColumn класса QTableWidget был удалён метод сортировки по умолчанию в порядке возрастания, теперь необходимо указать второй параметр, чтобы обозначить, будет ли сортировка по возрастанию или убыванию.

  2. В модуле qtnetwork (для сокетов TCP/UDP) сигнал об ошибке error был заменён на errorOccurred. Однако в случае с websocket название осталось прежним — error.

#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
    connect(udpSocket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), this, SLOT(error()));
    connect(tcpSocket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), this, SLOT(error()));
#else
    connect(udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error()));
    connect(tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error()));
#endif

// Особенно обратите внимание, что в websocket всё ещё используется error
connect(webSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error()));
  1. Модуль XmlPatterns больше не существует, вместо этого используется модуль xml для повторного анализа.

  2. Изменился тип параметра nativeEvent.

#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result);
#else
bool nativeEvent(const QByteArray &eventType, void *message, long *result);
#endif
  1. Во всех функциях сигнала buttonClicked для QButtonGroup, которые принимают int в качестве параметра, имя было изменено на idClicked.
    QButtonGroup *btnGroup = new QButtonGroup(this);
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
    connect(btnGroup, SIGNAL(idClicked(int)), ui->xstackWidget, SLOT(setCurrentIndex(int)));
#else
    connect(btnGroup, SIGNAL(buttonClicked(int)), ui->xstackWidget, SLOT(setCurrentIndex(int)));
#endif
  1. QWebEngineSettings::defaultSettings() в Qt6 был изменён на QWebEngineProfile::defaultProfile()->settings(). Просмотр исходного кода показывает, что QWebEngineSettings::defaultSettings() был инкапсулирован в QWebEngineProfile::defaultProfile()->settings(), поскольку Qt6 удалил множество избыточно инкапсулированных функций.
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
    QWebEngineSettings *webSetting = QWebEngineProfile::defaultProfile()->settings();
#else
    QWebEngineSettings *webSetting = QWebEngineSettings::defaultSettings();
#endif
  1. В Qt6 параметр enterEvent был изменён с QEvent на QEnterEvent без предупреждения. Такие изменения не вызывают ошибок компиляции.
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
    void enterEvent(QEnterEvent *);
#else
    void enterEvent(QEvent *);
#endif

// После подсказки от JasonWong, рекомендуется использовать override для всех виртуальных функций, переопределённых в дочернем классе.
// Это приведёт к ошибке компиляции, если функция или параметры родительского класса будут изменены, вместо того чтобы компилироваться, но работать некорректно, обвиняя Qt.

// Родительская функция
virtual void enterEvent(QEvent *event);
// Дочерний класс должен использовать override
void enterEvent(QEvent *event) override;
  1. В Qt6 несколько классов были объединены. Например, QVector теперь является псевдонимом QList, что означает, что эти два класса являются одним и тем же классом без каких-либо различий. Возможно, Qt объединил преимущества обоих внутри и попытался переписать алгоритмы или другие процессы, чтобы избежать недостатков. Аналогично, QStringList теперь является псевдонимом для QList, это один и тот же класс без отдельного существования.

  2. По умолчанию родительский класс конструктора QWidget в Qt4 равен 0, в Qt5 — Q_NULLPTR, а в Qt6 используется стандартный nullptr из C++ стандарта, а не пользовательское определение Qt Q_NULLPTR (то же самое относится и к Q_DECL_OVERRIDE, который был заменен на override и т. д.). Возможно, это сделано для того, чтобы окончательно избавиться от исторического бремени и принять будущее.

// Ниже приведены записи для Qt4/5/6
MainWindow(QWidget *parent = 0);
MainWindow(QWidget *parent = Q_NULLPTR);
MainWindow(QWidget *parent = nullptr);

// Просмотр исходного кода Qt показывает, что Q_NULLPTR определяется на основе компилятора
#ifdef Q_COMPILER_NULLPTR
# define Q_NULLPTR         nullptr
#else
# define Q_NULLPTR         NULL
#endif

// Qt поддерживает более старые версии, такие как Qt5/6, используя запись *parent = 0.
  1. Что касается стиля индикатора выполнения делегата, свойства класса QStyleOptionProgressBar в Qt4 нельзя установить горизонтальный или вертикальный стиль, по умолчанию используется горизонтальный стиль, и для установки ориентации необходимо использовать другой QStyleOptionProgressBarV2. Начиная с Qt5, были добавлены свойства orientation и bottomToTop. В Qt6 свойство orientation было полностью удалено, осталось только свойство bottomToTop, и прогресс по умолчанию является вертикальным, что очень раздражает, теоретически прогресс должен быть горизонтальным, большинство сценариев прогресса являются горизонтальными. Как с этим справиться? Текущая логика обработки была изменена, ориентация по умолчанию вертикальная, если вы хотите установить горизонтальную ориентацию, вы можете установить styleOption.state |= QStyle::State_Horizontal, этот метод настройки работает, а направление по умолчанию до Qt6 получается через значение ориентации, State_Horizontal существует с Qt4, после Qt6 вам нужно активно устанавливать его, чтобы он был горизонтальным.

05:41-50

  1. Поддержка мультимедийного модуля была добавлена в версии Qt6.2, но при компиляции с помощью mingw-компилятора всё ещё возникают проблемы. Проблема была решена только в версии 6.2.2. Согласно официальному объяснению, проблема связана с тем, что версия mingw-компилятора не поддерживается, а версия 6.2.2 использует новый mingw900_64, который поддерживает эту версию. Поэтому рекомендуется начать использовать новую версию Qt6 с версии 6.2.2.

  2. Метод setCodec в QTextStream был заменён на setEncoding, параметры изменились, а функциональность стала более мощной. ``` stream.setEncoding(QStringConverter::Utf8); stream.setEncoding(QStringConverter::System); #endif


43. Функция поиска дочернего узла child у QModelIndex была удалена, но функция поиска родительского узла parent осталась. После изучения кода стало ясно, что предыдущая функция child была обёрткой функции model->index(row, column, QModelIndex).

```cpp
// Эти две функции эквивалентны. Если требуется совместимость с Qt456, используйте следующий метод:
QModelIndex index = indexParent.child(i, 0);
QModelIndex index = model->index(i, 0, indexParent);

// Эти две функции эквивалентны. Если требуется совместимость с Qt456, используйте следующий метод:
QModelIndex indexChild = index.child(i, 0);
QModelIndex indexChild = model->index(i, 0, index);
  1. Статические функции grabWindow и grabWidget в классе QPixmap были полностью упразднены и заменены на использование QApplication::primaryScreen()->grabWindow. Фактически, это рекомендуется использовать начиная с Qt5.
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
    QPixmap pixmap = QApplication::primaryScreen()->grabWindow(widget->winId());
#else
    QPixmap pixmap = QPixmap::grabWidget(widget->winId());
#endif
  1. Метод start в QProcess ранее напрямую поддерживал передачу полной команды. В Qt6 требуется разделение последующих параметров.
// Qt6 ранее поддерживал выполнение полной команды
QProcess p;
p.start("wmic cpu get Name");
// В Qt6 необходимо использовать следующий метод, который также совместим с Qt4, 5 и 6
p.start("wmic", QStringList() << "cpu" << "get" << "Name");
  1. В qss перечисление значений свойств было заменено числовыми представлениями в Qt6 (требуется просмотр определения перечислений для нахождения соответствующих значений), что является значительным изменением. Необходимо перейти на новый формат, учитывая, что он несовместим с Qt5.
// Qt4/5 Установка выравнивания текста метки через таблицу стилей
ui->label->setStyleSheet("qproperty-alignment:AlignRight;");
// Qt4/5 Установка центрирования текста метки через таблицу стилей
ui->label->setStyleSheet("qproperty-alignment:AlignHCenter|AlignVCenter;");

// Qt6 Установка выравнивания текста метки вправо вверх через таблицу стилей (пересмотреть значение AlignRight = 2)
ui->label->setStyleSheet("qproperty-alignment:2;");
// Qt6 Центрирование текста метки через таблицу стилей (найти значение AlignHCenter|AlignVCenter = 0x04|0x80 = 0x84 = 132)
ui->label->setStyleSheet("qproperty-alignment:132;");
  1. Классы мультимедийного модуля в Qt6 претерпели значительные изменения. Некоторые из них связаны с изменениями имён классов, например, аудиовыход (также называемый воспроизведением) ранее был QAudioOutput, теперь он называется QAudioSink, а аудиовход (также называемый записью) ранее был QAudioInput, теперь он называется QAudioSource. Наборы устройств аудиовхода и аудиовывода по умолчанию ранее были QAudioDeviceInfo::defaultInputDevice() и QAudioDeviceInfo::defaultOutputDevice(), теперь они называются QMediaDevices::defaultAudioInput() и QMediaDevices::defaultAudioOutput(). Кажется, новые названия не так точно отражают суть.
#if (QT_VERSION >= QT_VERSION_CHECK(6,2,0))
#define AudioInput QAudioSource
#define AudioOutput QAudioSink
#else
#define AudioInput QAudioInput
#define AudioOutput QAudioOutput
#endif
// Для использования достаточно new
AudioInput *input = new AudioInput(format, this);

#if (QT_VERSION >= QT_VERSION_CHECK(6,2,0))
#define QAudioInput QAudioSource
#define QAudioOutput QAudioSink
#endif
// Для использования достаточно new
QAudioInput *input = new QAudioInput(format, this);
  1. Начиная с Qt6, CMake используется по умолчанию, поэтому в новых версиях Qt Creator при создании проекта по умолчанию выбирается CMake. Многие новички, впервые использующие его, могут быть озадачены тем, что структура сгенерированных проектов внезапно изменилась. Поэтому в процессе создания проекта рекомендуется выбрать qmake, и после первого выбора он будет использоваться по умолчанию.

  2. В версии Qt6.4 функции count для классов QString и QByteArray были упразднены, вместо них используются функции size и length, что, вероятно, более точно.

  3. Версия Qt6.4.1 содержит множество ошибок (BUG), которые настоятельно не рекомендуется использовать, например, отсутствие звука при использовании QAudioSink для воспроизведения звука https://bugreports.qt.io/browse/QTBUG-108383 и серьёзное искажение при масштабировании DPI https://bugreports.qt.io/browse/QTBUG-108593. Эти ошибки отсутствуют в версиях 6.4.0 и 6.5.0.

  4. В версии Qt6.5 был удалён конструктор по умолчанию для класса QVariant. Теперь для предотвращения предупреждений необходимо использовать QVariant(QVariant::Invalid) вместо return QVariant(). При печати значения обнаруживается, что QVariant() сам по себе равен QVariant(QVariant::Invalid), поэтому для совместимости с Qt456 рекомендуется использовать QVariant(QVariant::Invalid).

3 Опыт разработки под Android

01:01-05

  1. Добавьте модуль расширения Android в файл pro: QT += androidextras.
  2. Укажите каталог для упаковки Android в файле pro: ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android. Здесь можно указать специфический каталог для Android, включая иконки программ, переменные, цвета, файлы Java-кода, jar-библиотеки и т.д.
  • Файл AndroidManifest.xml является уникальным глобальным конфигурационным файлом для каждого приложения. Он содержит данные в формате XML, указывающие поддерживаемые версии Android, расположение иконок, ориентацию экрана, разрешения и т. д. Этот файл крайне важен, и если его нет, Qt создаст его автоматически.
  • Каталоги drawable-hdpi, drawable-xxxhdpi и другие в каталоге android/res содержат иконки приложений.
  • Файлы макетов хранятся в каталоге android/res/layout.
  • В файле libs.xml в каталоге values/ хранятся некоторые переменные.
  • Jar-файлы библиотек хранятся в каталоге libs в android/.
  • Исходные файлы Java хранятся в каталоге src в android/, где можно создать подкаталоги на основе пакетов или поместить их непосредственно в src/.
  • Рекомендуется самостоятельно изучить спецификации каталогов Android.
  • Можно изменить указанный каталог Android на любой другой, например, если ваши файлы для упаковки Android находятся в каталоге xxoo, вы можете написать ANDROID_PACKAGE_SOURCE_DIR = $$PWD/xxoo.
  1. Имя класса Java должно точно соответствовать имени файла, чувствительному к регистру.
  2. Классы Java должны находиться в каталоге android/src, иначе они не будут упакованы в APK. Можно создать подкаталог, например com/qt в android/src/.
  3. В коде Qt, используя QAndroidJniObject, укажите имя пакета Java. Оно должно точно совпадать с именем пакета в исходном файле Java, иначе приложение может аварийно завершиться при попытке найти соответствующий класс. ``` android/scr/com/example/MainActivity.java 顶部 package com.example.qandroid; 则代码中必须是 QAndroidJniObject javaClass("com/qandroid/example/MainActivity");
  • 总之这个包名是和代码中的package后面一段吻合,而不是目录路径。为了统一管理方便查找文件,建议包名和目录路径一致。

02:06-10

  1. Qt只能干Qt内部类的事情,做一些简单的UI交互还是非常方便,如果涉及到底层操作,还是需要熟悉java会如虎添翼,一般的做法就是写好java文件调试好,提供静态方法给Qt调用,这样通过QAndroidJniObject这个万能胶水可以做到Qt程序调用java中的函数并拿到执行结果,也可以接收java中的函数。
  2. pro中通过 OTHER_FILES += android/AndroidManifest.xml OTHER_FILES += android/src/JniMessenger.java 引入文件其实对整个程序的编译打包没有任何影响,就是为了方便在QtCreator中查看和编辑。
  3. 在Qt中与安卓的java文件交互都是用万能的QAndroidJniObject,可以执行java类中的普通函数、静态函数,可以传类对象jclass、类名className、方法methodName、参数,也可以拿到执行结果返回值。 (I)V括号中的是参数类型,括号后面的是返回值类型,void返回值对应V,由于String在java中不是数据类型而是类,所以要用Ljava/lang/String;表示,其他类作为参数也是这样处理。
  • 调用实例方法:callMethod、callObjectMethod。
  • 调用静态方法:callStaticMethod、callStaticObjectMethod。
  • 不带Object的函数名用来执行无返回值或者常规返回值int、float等的方法。
  • 如果返回值是String或者类则需要用带Object的函数名来执行,返回QAndroidJniObject类型再转换处理拿到结果,比如toString拿到字符串。
  1. 各种参数和返回值示例。
package org.qt;
import org.qt.QtAndroidData;

public class QtAndroidTest
{
    //需要通过实例来调用 测试发现不论 private public 或者不写都可以调用 我擦
    private void printText()
    {
        System.out.println("printText");
    }

    public static void printMsg()
    {
        System.out.println("printMsg");
    }

    public static void printValue(int value)
    {
        System.out.println("printValue:" + value);
    }

    public static void setValue(float value1, double value2, char value3)
    {
        System.out.println("value1:" + value1 + " value2:" + value2 + " value3:" + value3);
    }

    public static int getValue()
    {
        return 65536;
    }

    public static int getValue(int value)
    {
        return value + 1;
    }

    public static void setMsg(String message)
    {
        System.out.println("setMsg:" + message);
    }

    public static String getMsg()
    {
        return "hello from java";
    }

    public static void setText(int value1, float value2, boolean value3, String message)
    {
        System.out.println("value1:" + value1 + " value2:" + value2 + " value3:" + value3 + " message:" + message);
    }

    public static String getText(int value1, float value2, boolean value3, String message)
    {
        //同时演示触发静态函数发给Qt
        QtAndroidData.receiveData("message", "你好啊 java");

        //下面两种办法都可以拼字符串
        return "value1:" + value1 + " value2:" + value2 + " value3:" + value3 + " message:" + message;
        //return "value1:" + String.valueOf(value1) + " value2:" + String.valueOf(value2) + " value3:" + String.valueOf(value3) + " message:" + message;
    }
}
#include "androidtest.h"

//java类对应的包名+类名
#define className "org/qt/QtAndroidTest"

void AndroidTest::test()
{
    jint a = 12;
    jint b = 4;
    //可以直接调用java内置类中的方法
    jint max = QAndroidJniObject::callStaticMethod<jint>("java/lang/Math", "max", "(II)I", a, b);

    //jclass javaMathClass = "java/lang/Math";
    jdouble value = QAndroidJniObject::callStaticMethod<jdouble>("java/lang/Math", "random");

    qDebug() << "111" << max << value;
}

void AndroidTest::printText()
{
    QAndroidJniEnvironment env;
    jclass clazz = env.findClass(className);
    QAndroidJniObject obj(clazz);
    obj.callMethod<void>("printText");
}

void AndroidTest::printMsg()
{
#if 0
    //查看源码得知不传入jclass类的函数中内部会自动根据类名查找jclass
    QAndroidJniEnvironment env;
    jclass clazz = env.findClass(className);
    QAndroidJniObject::callStaticMethod<void>(clazz, "printMsg");
#else
    //没有参数和返回值可以忽略第三个参数
    QAndroidJniObject::callStaticMethod<void>(className, "printMsg");
    //QAndroidJniObject::callStaticMethod<void>(classNameTest, "printMsg", "()V");
#endif
}

void AndroidTest::printValue(int value)
{
    QAndroidJniObject::callStaticMethod<jint>(className, "printValue", "(I)I", (jint)value);
}

void AndroidTest::setValue(float value1, double value2, char value3)
{
    QAndroidJniObject::callStaticMethod<void>(className, "setValue", "(FDC)V", (jfloat)value1, (jdouble)value2, (jchar)value3);
}

int AndroidTest::getValue(int value)
{
    //java类中有两个 getValue 函数 一个需要传参数
    //jint result

``` Если библиотека представляет собой библиотеку Java (Java Library), то в результате компиляции получится файл jar, а имя пакета по умолчанию  com.example.lib.MyClass. Рекомендуется выбрать библиотеку Java, чтобы после компиляции не искать jar-файл среди множества других файлов.

- Третий шаг: придумайте название библиотеки, затем выберите минимальную версию SDK в соответствии с требованиями проекта > готово.
- Четвёртый шаг: в только что созданном библиотечном проекте mylibrary найдите подпапку src/main/java/com.example.mylibrary и щёлкните правой кнопкой мыши для создания нового класса. Помните, что это не узел java или любой другой узел.
- Пятый шаг: напишите свои методы класса и функции.
```cpp
package com.example.mylibrary;
public class Test {
    public static int add(int a, int b) {
        return a + b;
    }
}
  • Шестой шаг: выделите библиотечный проект mylibrary, в меню выполните компиляцию (build) —> скомпилируйте библиотеку (make module xxx).
  • Седьмой шаг: теперь в каталоге mylibrary/build есть каталоги outputs и intermediates. В каталоге outputs/aar находится двоичный архивный файл созданного Android-библиотечного проекта, который содержит все ресурсы, классы и файлы ресурсов res. Иногда нам нужен только jar-файл, содержащий только class-файлы и список файлов, без ресурсов, таких как изображения и другие файлы в res. Перейдите в каталог intermediates/aar_main_jar/debug, вы увидите classes.jar. Скопируйте его для использования. Конечно, вы также можете разархивировать только что созданный aar-файл с помощью программы для распаковки и увидеть classes.jar — это тот же файл. Прочее: вызвать jar-пакет очень просто, нужно только поместить jar-файл в каталог libs вашего проекта. Соответствующее имя пакета и функции обычно предоставляются поставщиком jar-пакетов. Если нет, то в Android Studio можно создать пустой проект, переключиться на представление Project, найти каталог libs, щелкнуть правой кнопкой мыши в самом низу, чтобы добавить пакет динамической библиотеки в качестве зависимости к проекту. После импорта он будет автоматически отображаться в каталоге libs. Дважды щелкните только что импортированный пакет, и автоматически отобразятся соответствующие классы и функции.
  1. Использование jar-пакета в Qt для Android.
  • Первый шаг: поместите classes.jar в каталог android/libs. Почему этот каталог? Потому что таково соглашение в Android, этот каталог предназначен для хранения файлов библиотек, и файлы, помещенные в этот каталог, будут автоматически упакованы и скомпилированы в apk-файл.
  • Второй шаг: прежде чем вызывать jar-файл, необходимо знать подробную информацию о функциях в jar-файле. Обычно поставщик jar-файлов предоставляет руководство. Если код не запутан, вы можете дважды щелкнуть по нему в Android Studio, чтобы просмотреть конкретные функции.
  • Третий шаг: если функции в jar-файле просты, можно напрямую написать класс Qt для их вызова. Если они сложны, рекомендуется сначала создать java-класс для обработки, а затем передать его в Qt. Конечно, автор jar может инкапсулировать функции как можно больше и предоставить простейший интерфейс для возврата необходимых данных. Например, для возврата данных изображения можно сделать так, чтобы jar хранил изображение внутри, а затем возвращал путь к изображению. В противном случае преобразование некоторых данных будет довольно хлопотным.
  • Четвертый шаг: написать окончательную функцию вызова.
int AndroidJar::add(int a, int b)
{
#ifdef Q_OS_ANDROID
    const char *className = "com/example/mylibrary/Test";
    jint result = QAndroidJniObject::callStaticMethod<jint>(className, "add", "(II)I", (jint)a, (jint)b);
    return result;
#endif
}
  1. Qt6 внес большие изменения в поддержку Android, но она еще не завершена. Если ваш проект не связан с взаимодействием с Java, его можно перенести без проблем. Однако проекты, связанные с Java, пока не рекомендуется переносить на Qt6. Подождите, пока все классы будут завершены, и тогда принимайте решение.
  • Плагин Android extras был удален, а некоторые функции были перемещены в модуль core, поэтому дополнительная загрузка не требуется.
  • Имена классов изменились, например, QAndroidJniObject стал QJniObject, а QAndroidJniEnvironment стал QJniEnvironment. Возможно, это сделано для унификации классов мобильных платформ и уменьшения влияния Android.
  • Для соответствующего JDK для Android требуется jdk11, а не jdk1.8. Оба поддерживаются в Qt5.15, но рекомендуется использовать jdk11.
  • Пакеты java классов, которые были упакованы, больше не имеют префикса qt5. org.qtproject.qt5.android.bindings.QtActivity стал org.qtproject.qt.android.bindings.QtActivity, а org.qtproject.qt5.android.bindings.QtApplication стал org.qtproject.qt.android.bindings.QtApplication.
  • Требуется минимальная версия Android SDK, поэтому при настройке файла AndroidManifest.xml не указывайте минимальную версию.
  • Содержимое файла AndroidManifest.xml имеет требования. Файлы, созданные для Qt5 Android, нельзя использовать в Qt6 Android. Смотрите файлы в примере для конкретных требований.
  • Пример demo находится в папке C:\Qt\Examples\Qt-6.3.0\corelib\platform. Раньше он находился в C:\Qt\Examples\Qt-5.15.2\androidextras. Сейчас есть только один пример, возможно, потому что другие классы еще не перенесены.
  • Описание модуля Android в Qt6 можно найти здесь: https://doc.qt.io/qt-6/qtandroidprivate.html
  1. Если вы хотите скрыть верхнюю панель состояния на весь экран в Android, вы можете изменить show на showFullScreen в основной функции. Или вы можете использовать метод Java и добавить следующую строку в функцию onCreate: getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN).

06:25-30

  1. Распознавание переключения между горизонтальной и вертикальной ориентацией экрана в Qt будет отражено в событии resizeEvent. Вы можете прочитать текущую ориентацию экрана (горизонтальную или вертикальную) после изменения размера и внести соответствующие изменения в интерфейс. Например, если элементы расположены сверху вниз, измените их расположение на слева направо.

  2. Поскольку формат файла конфигурации AndroidManifest.xml для разных версий Qt отличается, шаблоны для высоких и низких версий несовместимы. Поэтому рекомендуется использовать файл AndroidManifest.xml, созданный для вашей версии Qt. Если вы используете пользовательский java-файл запуска, вам нужно заменить android:name="org.qtproject.qt5.android.bindings.QtActivity" в файле AndroidManifest.xml.

  3. Если вы поместили jar-файл, скомпилированный в Android Studio, в каталог libs Qt-проекта и компиляция не удалась, выдавая сообщение об ошибке типа com.android.dx.cf.iface.ParseException: bad class file magic, это может быть связано с несоответствием версий JDK. Вам может потребоваться изменить версию JDK в проекте Android Studio. Например, если вы используете NDK r14, вам следует выбрать JDK 1.6 или 1.7. Как правило, более новые версии JDK совместимы со старыми, поскольку старые версии NDK не поддерживают JDK 1.8. Позже было обнаружено, что если вы создаете новую библиотеку Java (Java Library), такой проблемы не возникает. Но если вы выберете Android library, такая проблема может возникнуть.

  4. Файл конфигурации проекта Android имеет фиксированное имя AndroidManifest.xml. Изменение его на другое имя не будет распознано. Не думайте, что изменение имени на другое приведет к тому, что оно не будет распознано должным образом.

  5. Поле package="org.qtproject.example" в файле AndroidManifest.xml является именем пакета, которое также является уникальным идентификатором всего приложения apk. Если два apk имеют одинаковое имя пакета, они будут перезаписывать друг друга. Поэтому важно убедиться, что разные приложения имеют уникальные имена пакетов. Это имя пакета также определяет имя пакета для импорта в java-файлах, например import org.qtproject.example.R. Если имена пакетов отличаются, компиляция завершится неудачно.

07:31-35

  1. Создание среды разработки Android с использованием новой версии qtc стало очень простым, в отличие от ранних версий, когда требовалось много времени и усилий для загрузки и установки различных компонентов. Теперь достаточно установить файл JDK (jdk_8.0.1310.11_64.exe), и все будет настроено по умолчанию за один шаг. Затем в настройках Qt для Android укажите путь установки JDK. Откройте файлы D:\Qt\Qt6\Tools\QtCreator\share\qtcreator\android\sdk_definitions.json и C:\Users\Administrator\AppData\Roaming\QtProject\qtcreator\android\sdk_definitions.json. Измените строку cmdline-tools;latest на cmdline-tools;6.0. Этот шаг очень важен, так как использование latest приведет к неполной загрузке SDK/NDK при автоматическом скачивании. После внесения изменений настройте каталог сохранения SDK, нажав кнопку Set Up SDK справа. Это автоматически загрузит множество файлов, включая каталог openssl внизу. Эти файлы можно легко скачать онлайн, и кнопка для скачивания доступна справа. После завершения загрузки вы сможете начать увлекательное путешествие по разработке приложений для Android.

  2. Что касается взаимодействия со строковыми типами, вам нужно использовать Ljava/lang/String;. Помните, что точка с запятой в конце обозначает конец текущего объекта. ``` QJniObject result = QJniObject::callStaticObjectMethod(className, "getVersion", "()Ljava/lang/String;"); ,里面的分号必不可缺,要是换成 QJniObject result = QJniObject::callStaticObjectMethod(className, "getVersion", "()Ljava/lang/String") 执行是失败的。

  3. 可以通过 QJniObject::isClassAvailable("com/example/lib/MyClass") 来判断类是否可用。

  4. 在安卓的java类中,如果是独立的一个java类,常规的参数int/float/string之类的,在Qt中都非常容易传递参数,唯独窗体上下文context、活动窗体activity、两种参数比较麻烦,很多人就困在这里。其实Qt就提供了函数获取对应的实例,在Qt5中是QtAndroid::androidActivity()/QtAndroid::androidContext(),在Qt6中是QNativeInterface::QAndroidApplication::context().但是有没有发现,如果每次都这么传,如果很多函数都需要这个参数,感觉重复代码不少,所以强烈推荐第二个方法,新建的java类中,搞个private static Context context,然后提供一个public static setContext(Context context)函数,在主窗体activity开始创建的onCreate函数中,赋值xxx.setContext即可。这样就相当于在java这边传递好了对应的上下文或者窗体参数,然后暴露出来的接口就无须context这些参数了,省去了不少麻烦。

//java类中函数原型
public static void getBattery(Context context)
//qt5类中调用
QAndroidJniObject::callStaticMethod<void>("org/qt/QtAndroidReceiver", "getBattery", "(Landroid/content/Context;)V", QtAndroid::androidActivity().object());
//qt6类中调用
QJniObject::callStaticMethod<void>("org/qt/QtAndroidReceiver", "getBattery", "(Landroid/content/Context;)V", QNativeInterface::QAndroidApplication::context());

//方法2通过启动窗体赋值再调用
public class abc
{
  private static Context context;
  public static void setContext(Context context)
  {
    abc.context = context;
  }

  public static void getBattery() 
  {
    abc.context.xxx;
  }
}

//下面这个窗体作为启动窗体
public class QtAndroidActivity extends QtActivity
{
  //必须在窗体创建的时候下面赋值
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    abc.setContext(getApplicationContext());
  }
}

//qt类中调用就方便了/如果有很多个函数都需要传入context则效率可以大大提升
QJniObject::callStaticMethod<void>("org/qt/QtAndroidReceiver", "getBattery", "()V");

4 ffmpeg业余经验

  1. ffmpeg的库有链接顺序要求,如果不按照顺序来,也许在windows上没有问题,但是到了unix系统很可能有问题,报错提示云里雾里的找不到原因。顺序参照ffmpeg自带示例中的编译链接顺序即可。正确顺序是 LIBS += -L/ -lavformat -lavfilter -lavcodec -lswresample -lswscale -lavutil -lavdevice 。不是所有的库都是必须的,比如avdevice库,如果代码中没用上也没引用,可以不需要。
  2. ffmpeg解码中,av_find_best_stream第五个参数传入AVCodec的话,就直接获取到了值,而不用avcodec_find_decoder来处理。
  3. ffmpeg在解码的时候,avcodec_alloc_context3的参数AVCodec不是必须的,如果这里是NULL,则下面avcodec_open2的时候就必须传入。编码的时候在avcodec_alloc_context3的时候必须传入,否则下面打开失败。
  4. 解码阶段,每次av_read_frame后,使用完对应的packet数据,必须调用av_packet_unref,否则内存泄漏。编码阶段,每次av_write_frame后,里面会自动调用av_packet_unref。
  5. avpacket表示压缩的视音频数据(解码前和编码后),avframe表示未压缩的视音频数据(解码后和编码前),视音频文件以及传输都是使用压缩的数据,收到数据解码后才是未压缩的数据,才能直接绘制和播放。
  6. 解码对应av_read_frame/avcodec_send_packet/avcodec_receive_frame,编码对应avcodec_send_frame/avcodec_receive_packet/av_write_frame。可以看到命名非常规整,编码刚好和解码顺序相反。
  7. avcodec_send_packet和avcodec_receive_frame并不是一一对应的调用关系,而是一个avcodec_send_packet的调用,可能会对应多个avcodec_receive_frame函数的调用。因为解码器内部是有缓存和参考帧的,并不是每送进去一个数据包就能解码出一帧数据,可能出现送进去几个数据包,但是暂时没有数据帧解码输出的情况,也可能会出现某个时间点送进去一个数据包,然后会输出多个数据帧的情况。但是实际使用过程中你会发现你遇到过的99.9%的视音频文件或者流都是一对一关系,一个avcodec_send_packet就对应一次avcodec_receive_frame。
  8. 在ffmpeg函数接口中,有不少带数字结尾的函数,比如avcodec_alloc_context3、avcodec_decode_video2、avcodec_decode_audio4,这种一般就是不断迭代的结果。比如早期版本很可能有个函数avcodec_alloc_context,但是后面又新增了更完善的函数,又希望用户能够快速找到该函数,所以直接后面加个数字用以区分。这基本上都是程序员的惯例。如果是大版本的更新,可能旧的函数会主键废弃,甚至移除。一般建议用新的函数接口。
  9. 众多的音视频格式,是为了各种应用场景需求产生的,也是随着时代的发展需要应运而生的,主要就是为了方便压缩和传输,经过各种各样的算法标准。在ffmpeg中,通用的解码做法都是将任意视频格式解码转换成yuv(软解码)或者nv12(硬解码)数据,任意音频格式转换成pcm数据,yuv/nv12/pcm因为是原始的数据,所以体积非常大,优点就是非常通用,可以直接显示和播放。
  10. 要做音视频格式的转换,通用做法就是视频解码成yuv,音频解码成pcm,然后再由yuv编码成其他视频格式(对应转换对象SwsContext),pcm编码成其他音频格式(对应转换对象SwrContext),万变不离其中,唯一区别就是不同格式对应的各种参数不一样。音频转换这块可能还需要重采样。
  11. 无论是ffmpeg源码本身还是提供的示例demo,会发现里面大部分的代码都是在做判断,比如对象是否分配ok,大部分都提供返回值,负数表示失败,每一步执行后都需要判断是否ok再继续。其实这种写法是非常规范而且有必要的,可以避免程序意外崩溃段错误,而且就算出错也可以非常清晰的知道具体在哪一步,况且还增加了代码行数,何乐而不为。那有些人又要担心这会减慢程序运行速度,毕竟绝大部分的判断都是不会出现的,这里可以很负责任的告诉你,这个if判断,占用时间忽略不计,至少在纳秒级别。而且为了提高程序的健壮性稳定性,很有必要,尤其是C/C++这类程序。
На самом деле это возможно, но из-за того, что компоненты должны быть независимыми, различные библиотеки и интерфейсы разделены. Это сделано для различных сценариев использования. Например, при декодировании нужно сначала получить информацию о потоке через avformat_find_stream_info, затем скопировать codecpar из информации потока в AVCodecContext. После чтения данных через av_read_frame, когда вызывается декодирование avcodec_send_packet/avcodec_receive_frame, нужен AVCodecContext, и этот процесс всегда одинаков. Почему ffmpeg не обрабатывает копирование внутри себя? Потому что AVCodecContext также может использоваться для кодирования, а параметры можно настраивать. Он также независим от AVStream и других компонентов, поскольку может выполнять кодирование и декодирование без AVStream, если ему предоставлена необходимая информация. По мере углубления использования вы поймёте, что такая конструкция — лучшая.

### 5 Qt-дизайн
**Опыт, собранный из книги «C++ Qt-дизайн», прочитанной вместе с официальной книгой «Программирование GUI Qt4 на C++».**
1. Обычно рекомендуется включать файлы заголовков Qt после файлов заголовков, не относящихся к Qt, так как Qt определяет множество символов, что упрощает избежание конфликтов имён и поиск файлов.
```cpp
#include "frminput2019.h"
#include "ui_frminput2019.h"

#include "qdatetime.h"
#include "qdebug.h"

#include "input2019.h"
#include "inputnumber.h"
  1. Хорошая практика программирования — использовать const сущности вместо встроенных числовых констант (иногда называемых «магическими числами»). Если позже потребуется изменить их значение, это обеспечит гибкость. Как правило, изолирование констант улучшает поддержку кода.
// Не рекомендуется
for (int i = 0; i < 100; ++i) {
    ...
}

// Рекомендуется
const int count = 100;
for (int i = 0; i < count; ++i) {
    ...
}
  1. Управление памятью даёт разработчикам мощные возможности, но «с большой силой приходит большая ответственность».
  2. Следует использовать списки вместо массивов, где это возможно. Например, использовать QList вместо int []. В C++ массивы считаются «злыми».
  3. При использовании Qt для написания программ редко используются умные указатели из-за отношений владения и наследования между родительскими и дочерними элементами в Qt. Умные указатели следует рассматривать, когда необходимо вызвать delete или установить указатель на 0.
  4. Невозможно точно определить, улучшит ли многопоточность производительность программы. Увеличение количества потоков может привести к снижению производительности из-за конкуренции за ресурсы. Эффективный алгоритм в однопоточной среде может оказаться неэффективным в многопоточной. Поэтому для улучшения производительности программы следует использовать разные реализации и сравнивать их производительность.
  5. В исходном коде для путей к файлам лучше использовать /, так как он понятен Qt на любой платформе. Однако для отображения пути пользователю лучше отображать его в соответствии с платформой приложения.
  6. Если требуется обработать большое количество данных, создание отдельного потока для каждого элемента данных может вызвать накладные расходы. Вместо этого можно создать несколько вспомогательных потоков, каждый из которых обрабатывает группу данных.

6 Зона экспертов Qt

6.1 Эксперт Kuma-NPC

WeChat: Kuma-NPC

  1. Объяснение распространения событий Qt:
  • В Win32-программах сообщения мыши направляются непосредственно в окно с указанным дескриптором. Если окно не обрабатывает сообщение, оно передаётся родительскому окну. Вы можете получать события мыши даже в родительском окне, щёлкнув по обычному текстовому ярлыку.
  • Qt направляет все сообщения в главное окно, поэтому логика распространения событий обрабатывается самостоятельно. Главное окно получает события мыши, а Qt перенаправляет их указанному дочернему элементу. QEvent имеет флаги ignore или accept, указывающие, обработано ли событие. Например, если событие клика не обработано, данные пересчитываются и отправляются в родительское окно. Координаты событий, полученные родительским окном, относятся к его собственному окну. Для работы с eventFilter необходимо самостоятельно рассчитать координаты.
  • Например, при использовании QDialog и размещении QLineEdit с фокусом, нажатие Esc приведёт к автоматическому закрытию QDialog. Это происходит потому, что QLineEdit не обрабатывает события нажатия клавиши Esc, которые передаются QDialog.

6.2 Эксперт Little Bean

  1. Независимо от того, изучаете ли вы Qt, Java, Python или другие языки, важно понять одну вещь: отбросьте своё любопытство и не пытайтесь разобраться, как реализованы сторонние классы или инструменты. Это часто приносит мало пользы. Вам нужно только освоить их интерфейсы и понять назначение классов. Избегайте процедурного подхода и старайтесь не углубляться в детали. Помните, ваша цель — заставить эти инструменты служить вам, чтобы вы могли создавать что-то новое.
  2. Настоящие сердцевины Qt: система метаобъектов, система свойств, модель объектов, дерево объектов, механизм сигналов и слотов. Усердно изучайте эти пять основных характеристик и постепенно внедряйте их в свои проекты. Практика их использования принесёт неожиданные результаты.
  3. Просите совета у других и постоянно рефакторите свой код. Хотя кто-то может указать вам направление, настоящий прогресс зависит от вас самих. Когда вы пройдёте этот путь, ваш уровень кодирования значительно повысится. Возможно, код, который другие пишут в 1000 строк, вы сможете сделать в несколько десятков. В этом и заключается прелесть Qt.
  4. Читая документацию Qt, сосредоточьтесь и не упускайте ни одной детали, особенно в начале каждого раздела.

7 Другие советы

  1. Проблемы с кодировкой китайских символов в мире Qt, выбор установочного пакета из-за множества версий, упаковка и публикация программ — три большие проблемы в мире Qt!
  2. При изучении Qt полезно ознакомиться с заголовочными файлами соответствующих классов. Если нужная функция отсутствует в заголовочном файле класса, можно поискать её в файле родительского класса. Если ничего не найдено, стоит обратиться к файлу дедушкиного класса. Изучая заголовочные файлы, вы обнаружите, что многие функции уже упакованы Qt для вас. Также можно изучить их реализацию.
  3. Примеры в каталоге Examples в папке установки Qt стоит изучить и освоить. Освоение этих примеров может принести доход начиная от 20 тысяч в месяц. Изучение и использование функций в заголовочных файлах часто используемых классов Qt также могут увеличить вашу зарплату до 30 тысяч в месяц.
  4. Во время разработки Qt не поддерживает китайские каталоги (хотя во время выполнения программы это возможно). Важно помнить, что при установке интегрированной среды разработки Qt и компилятора необходимо использовать английские каталоги. Каталог проекта Qt также должен быть на английском языке, иначе могут возникнуть проблемы. Рекомендуется использовать стандартные пути установки.
  5. Если программа вылетает или выдаёт сегфолт, в 90% случаев это связано с выходом за пределы массива (например, попытка доступа к элементу за пределами границ массива) или использованием неинициализированных переменных (например, использование указателя без выделения памяти через new или освобождение памяти, а затем попытка использовать этот указатель снова).
  6. Существует несколько сотен версий Qt. Я обычно использую четыре версии: для совместимости с Qt4 — 4.8.7, последнюю версию, поддерживающую XP — 5.7.0, новейшую версию с долгосрочной поддержкой — 5.15, и самую новую версию — 5.15.2. Не рекомендую использовать версии до 4.7 и между 5.0 и 5.3, так как они содержат много ошибок и проблем со стабильностью и совместимостью. Если нет исторических ограничений, лучше использовать 5.15.2. Текущую версию Qt6 не рекомендую использовать, так как она ещё находится в процессе интеграции, и многие классы и модули ещё не включены. Лучше дождаться версии 6.2.2. Учитывая производительность qss и наличие встроенного драйвера MySQL, я предпочитаю использовать Qt5 версии 5.12.3, Qt4 версии 4.8.7 и Qt6 версии 6.5.x.
  7. Распространённые комбинации Qt и компиляторов MSVC включают Qt5.7+VS2013, Qt5.9+VS2015, Qt5.12+VS2017, Qt5.15+VS2019 и Qt6.2+VS2019. Эти комбинации обеспечивают наличие большинства необходимых модулей. Если выбран Qt5.12 и MSVC2015, некоторые модули могут быть недоступны, поэтому лучше выбрать Qt5.12+MSVC2017. Если необходимо использовать MSVC2015 вместо MSVC2017, можно выбрать Qt5.9+MSVC2015 или перекомпилировать исходный код самостоятельно (что может быть очень сложно для новичков).
  8. Qt обычно предоставляет соответствующие версии для разных версий VS. Например, для Qt4.8 подходит VS2010, для Qt5.6 — VS2013, для Qt5.9 — VS2015, для Qt5.12 — VS2017, для Qt5.15 — VS2019, и для Qt5.15 и выше — VS2019. Важно использовать оригинальные версии, предоставляемые Qt.
  9. Для разработки на Qt рекомендуется использовать Windows 10, особенно для новых версий Qt, выпущенных после 2021 года, таких как Qt5.12.12, Qt5.15.2 и Qt6.2.2. Новые версии QtCreator обычно поставляются с последними версиями Qt. Qt6 больше не поддерживает Windows 7, и могут возникать проблемы при использовании старых версий Qt на этой операционной системе. Поэтому лучше всего использовать Windows 10.
  10. При установке новых версий Qt необходимо заполнить регистрационные данные. Чтобы избежать этого, можно отключить интернет-соединение перед запуском установщика. Начиная с Qt5.15, доступны только онлайн-установщики, требующие заполнения пользовательских данных.
  11. Если вы столкнулись с проблемой и не можете найти решение в документации Qt, попробуйте использовать ключевые слова, связанные с другими языками программирования, такими как Java, C#, Android. Вы найдёте множество решений, разработанных для других языков и платформ. Если Qt сможет приложить усилия в нескольких направлениях, то у него будет больше перспектив для развития.
  • QWidget поддерживает CSS3, имеет множество крутых эффектов, на данный момент поддерживается CSS2.
  • QWidget поддерживает рендеринг GPU, можно выбрать переключение между CPU и GPU, чтобы повысить эффективность рендеринга, используя современное мощное оборудование.
  • Qml бесшовно поддерживает js, можно использовать различные js-инструменты, экспоненциально расширяя область применения qml проектов.
  • Поддерживается преобразование программ в веб-формат, например, в cgi-программы. На данный момент Qt for WebAssembly очень ограничен, функциональность крайне ограничена, не поддерживаются sql/network/локальный доступ, скорость загрузки крайне медленная, большинство классов Qt также не поддерживаются.
  1. Qt начиная с версии 4.7 представил QML. С этого момента разработка Qt разделилась на два направления: одно использует традиционный C++ для разработки, другое — язык QML. Это привело к ожесточённым спорам в сообществе Qt о том, что лучше: QML или Widget. Многие новички задаются этим вопросом. Вот несколько пунктов для размышления:
  • Widget относится к традиционной разработке интерфейсов и похож на разработку с использованием VB/VC/Delphi, использует рендеринг CPU, максимально совместим с существующим оборудованием, включая относительно старые и менее производительные устройства.
  • QML является продуктом новой эпохи, начиная примерно с 2010 года, и похож на фреймворки для веб-разработки, такие как flutter/Electron, а также на фреймворки для мобильной разработки. Он предназначен для адаптации к различным разработкам мобильных устройств и обеспечения плавного анимационного взаимодействия, полностью используя возможности современных GPU.
  • Чем выше производительность оборудования, тем мощнее GPU, тем более QML превосходит Widget, и наоборот, сравнение проводится на экспоненциальном уровне. За исключением области крайне дешёвых встраиваемых систем или отечественных процессоров, производительность оборудования в других областях стремительно растёт.
  • Widget в основном используется в таких областях, как финансы, военная промышленность, безопасность, аэрокосмическая отрасль, судостроение и образование. QML в основном применяется в автомобильной промышленности, приборной панели, механизмах автомобилей и прямых трансляциях.
  • В настоящее время в Китае widget более популярен, чем qml, за рубежом может быть наоборот. Это нетрудно заметить, поскольку популярные фреймворки для мобильных устройств в основном разрабатываются за границей.
  • Можно предвидеть, что в течение следующих десяти лет эти два подхода будут сосуществовать в долгосрочной перспективе, официальные представители практически перестанут обновлять widget и будут активно продвигать qml. Это означает, что будущая оптимизация производительности qml будет только улучшаться, и тенденция будет склоняться в сторону qml.
  • Для новичков без опыта программирования изучение qml обходится дешевле, в то время как специалисты, перешедшие из традиционных сред разработки программного обеспечения, таких как VB/VC, лучше подходят для изучения widget.
  • Иногда невольно задаёшься вопросом, зачем нужен widget, если есть qml? Выбор и затраты на обучение становятся ещё более разнообразными. Фактически, это философия мира: мир одновременно прост и сложен. Всё создано для того, чтобы соответствовать различным потребностям.
  • Короче говоря, независимо от того, выберете ли вы qml или widget, лучший выбор — тот, который подходит именно вам. Используйте то, в чём вы хороши.
  • Если вы ещё не знаете, что вам больше подходит, изучайте оба варианта. В процессе обучения вы получите личный опыт и сможете сравнить их. Умение быстро и эффективно выполнять задачи начальника, чтобы заработать деньги, — это путь к успеху.
  • Пользователь сети добавил: если ваше программное обеспечение в конечном итоге будет управляться пальцами, используйте qml, если мышью — выберите widget.
  1. При написании программы, если возникают проблемы, особенно в экстремальных условиях, рекомендуется найти их причину. Иногда неизбежно возникает подозрение, что проблема связана с самим Qt. Подозрения часто оказываются верными, но в 99,9% случаев проблема в конечном счёте связана с плохим кодом. Если сроки поджимают, можно временно решить проблему перезагрузкой или сбросом настроек. Например, можно установить таймер, потоки, сетевую коммуникацию для проверки состояния программы или определённых модулей и функций. Если что-то не работает, перезагрузите программу или компьютер. Во встроенных системах можно применить более радикальные методы, такие как перезапуск системы или отключение питания.

  2. При написании программ важно учитывать несовместимость 32-битных и 64-битных библиотек. Проблемы возникают, когда 32-битные программы используют 64-битные библиотеки или наоборот. В Windows 64-разрядной системе можно запускать 32-разрядные программы, так как система предоставляет 32-разрядную среду выполнения. Обычно 32-разрядные библиотеки находятся в каталоге Program Files (x86). Чтобы определить разрядность своих Qt-библиотек, нужно проверить информацию в Qt Creator или посмотреть в комплекте сборки Qt. Начиная с Qt 5.14, 32-битные версии предоставляются реже, а в Qt 6.0 по умолчанию доступны только 64-битные версии. Это соответствует современным тенденциям, ведь в ближайшем будущем (по прогнозам, до 2030 года) доля 32-битных систем составит менее 1%. Поэтому рекомендуется использовать 64-битные библиотеки, отказавшись от устаревших 32-битных версий и систем XP.

  3. О динамических и статических библиотеках в программах Qt:

  • В Qt-программах используются динамические и статические версии Qt.
  • По умолчанию официальные пакеты представляют собой динамическую версию Qt. При самостоятельной компиляции используется параметр -shared.
  • Статическая версия Qt требует самостоятельной компиляции с параметром -static. Теоретически, использование статической версии Qt должно оплачиваться, так как после компиляции статически связанные библиотеки не видны.
  • Программы, использующие динамическую библиотеку Qt, могут компилироваться и генерировать как динамические, так и статические библиотеки (CONFIG += staticlib).
  • Динамически связанные программы Qt могут ссылаться на динамические библиотеки (при ссылке LIB += , при запуске требуются файлы динамических библиотек, такие как .dll .so).
  • Программы Qt, использующие динамические библиотеки, могут ссылаться на статические библиотеки (при ссылке LIB += , при запуске файлы библиотек не требуются, можно рассматривать как объединение с исполняемым файлом, недостатком является увеличение размера исполняемого файла).
  • Сравнивая количество и размер сгенерированных файлов, можно увидеть, что статические библиотеки объединяют все необходимые для выполнения файлы в один, тогда как динамические библиотеки разделяют их на два: один для компиляции, другой для запуска.
  • Эти правила применимы и к статическим библиотекам.
  • Вероятно, эти правила применимы к другим языковым фреймворкам.
  • Многие люди, включая меня несколько лет назад, ошибочно полагают, что для создания статических библиотек в Qt необходимо использовать статическую версию Qt, хотя динамическая версия также позволяет создавать статические библиотеки, которые просто не генерируют динамические файлы.
  • Чтобы скомпилировать Qt-программу в статический исполняемый файл (один файл без зависимостей), используемые Qt-библиотеки должны быть статическими.
  1. В последних версиях Qt, начиная примерно с 5.15, офлайн-версии больше не предоставляются, требуется онлайн-установка через установщик. Поскольку серверы по умолчанию расположены за границей, многие пользователи сообщают о медленном скачивании. Для ускорения процесса можно использовать Fiddler5 (желательно китайскую версию для лучшего понимания), ввести urlreplace download.qt.io mirrors.ustc.edu.cn/qtproject/ в поле ввода внизу программы и нажать Enter. Затем можно установить Qt через онлайн-установщик, и мир внезапно станет прекрасным.

  2. Qt — это потрясающий проект с огромным исходным кодом, разделённым на модули. Те, у кого достаточно времени, могут изучить конкретную реализацию в исходном коде. Если времени мало, рекомендуется обратить внимание на следующие классы: QObject, QWidget, QPainter, QString, QColor, QList, QVariant, QAbstractButton, QAbstractItemModel и qnamespace.h. Изучение их методов и свойств может значительно помочь в программировании.

  3. И последнее: берегите свою жизнь и держитесь подальше от программирования. Желаю всем густой шевелюры, хорошего сна, стабильного настроения и финансового благополучия!

8 Разное

8.1 Рекомендуемые страницы с открытым исходным кодом

Название URL
Qt/C++ 学习高级群 751439350
QtWidget 开源demo集合 https://gitee.com/feiyangqingyun/QWidgetDemo
QtQuick/Qml 开源demo集合 https://gitee.com/jaredtao/TaoQuick
QtQuick/Qml 开源demo集合 https://gitee.com/zhengtianzuo/QtQuickExamples

8.2 Рекомендуемые главные страницы сайтов

Название URL
qtcn http://www.qtcn.org
豆子的空间 https://www.devbean.net
yafeilinux http://www.qter.org
feiyangqingyun https://blog.csdn.net/feiyangqingyun
Qt作品大全 https://qtchina.blog.csdn.net/article/details/97565652
龚建波 https://gongjianbo1992.blog.csdn.net/
乌托邦2号 http://blog.csdn.net/taiyang1987912
foruok http://blog.csdn.net/foruok

Комментарии ( 0 )

Вы можете оставить комментарий после Вход в систему

Введение

Собственные выводы, основанные на более чем десятилетнем опыте разработки с использованием Qt, а также электронные книги по секретам мастерства в области Qt будут постоянно обновляться и дополняться. Приглашаем всех оставлять комментарии, чтобы добавлять новый контент или вносить предложения, спасибо! Публичный аккаунт: Qt в действии/Qt для на... Развернуть Свернуть
MulanPSL-1.0
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/feiyangqingyun-qtkaifajingyan.git
git@api.gitlife.ru:oschina-mirror/feiyangqingyun-qtkaifajingyan.git
oschina-mirror
feiyangqingyun-qtkaifajingyan
feiyangqingyun-qtkaifajingyan
master