Бесплатная среда программирования Qt: нюансы (21.08.2017). |
![]() |
2017 - Август | |||
21.08.2017 19:50 | |||
В защиту Qt сразу стоит сказать: - Qt позиционирует себя как бесплатная среда разработки даже для организаций (GPL, LGPL - оплачивается только лицензия техподдержки, если есть желание). В связи с этим приобретает все большую популярность. Ежики плакали, но продолжали жрать кактус: потому что он бесплатный; - заявлена кроссплатформенность: полная совместимость исходного кода в разных средах Windows/Linux/Unix. Переход на Qt порождает ломку при миграции: из Linux перенята парадигма "сигналы-слоты", на понимание которой уйдет достаточно много времени. Образно, сигнал - вызываемый метод объекта или функция, слот - метод объекта или функция, которая сработает при вызывании сигнала. Соединяются сигналы и столы функцией connect, для которой тоже не все очевидно. Чтобы только ее было видно в других файлах, нужно постараться. Потом начинаются проблемы с видимостью сигналов и слотов. Потом выясняется, что сигнал и слот должны иметь одинаковое количество и тип входных параметров (согласно справке, нелогичные условия). Любая ошибка приводит к неработоспособности связки "сигнал-слот", компилятор на эту тему ничего не сообщает. Пример корректного выполнения: connect(ui->action_PrintScreen, SIGNAL(triggered(bool)), this, SLOT(vScreenShot())); При нажатии на графическом интерфейсе кнопки "Скриншот" сработает метод triggered, который повлечет за собой выполнение функции vScreenShot. Сразу смущает, что triggered имеет входной параметр bool, а функция не имеет - и при этом все работает. Вот и разногласие со справкой, а в справке попадаются нерабочие примеры примерно каждый сотый. А вот если функции vScreenShot сообщить входной параметр, отличный от bool - функция уже не отработает (не вызовется). В общем, только набиванием шишек эта концепция в голове откладывается правильным образом. Даже если ломка пройдена - впереди ждут много глюков, и именно им посвящена лавина трехэтажного мата (выдержка из готовой программы по работе с платами ввода/вывода): - проект не работает при нахождении в русскоязычной папке; - галка "теневая сборка" при переносе проекта с ПК на ПК включается сама - должна быть выключена вручную после переноса, иначе будут проблемы с QFile; - функции-заплатки для Qt представлены в файле qt.cpp, в т.ч. iMessageBox и dRound - т.к. стандартные работают коряво; - иконка проекта выставляется в файле проекта, в DISTFILES +=: RC_ICONS += Icon.ico; - в том же разделе прописывается версия проекта: VERSION=1.0.0.0 #Мажор, минор, релиз, билд; - проверка на численность: QString::toFloat(&bCorrect); - скрытие кнопки Maximize формы невозможно - решается фиксированными minimumSize, maximumSize; - переименование любых файлов проекта производить только в среде программирования; - жуткие проблемы с ui: чтобы он был виден в сторонних файлах - обязательно функция помещается в класс Form_Main, а "Form_Main::" приписывается как префикс к функции в стороннем файле; - при использовании connect число и тип параметров SIGNAL должно быть равно количеству и типу параметров SLOT. Указываются только типы входных параметров. Из-за этого приходится передавать параметры в виде глобальных переменных или создавать свой класс QConnect с нужными свойствами; - при миграции проекта с Windows на Linux требуется соблюдение условий: - опция "Current Directory" в Projects->Manage Kits->Build & Run->General->Projets Directory должна быть изменена; - имя проекта не должно содержать скобок и должно заключаться в двойные кавычки; - добавление постфикса "-32" в строку Projects->Build Settings->Build Steps->Effective qmake call. Пример: qmake /home/Samokontrol_Linux/Samokontrol_Linux.pro -r -spec linux-g++-32 CONFIG+=debug. - при миграции проекта с Linux на Windows требуется соблюдение условий: - имя проекта не должно содержать точек и должно заключаться в двойные кавычки; - часть библиотек может не скомпилироваться нормально, что ставит под угрозу саму задачу миграции; - Qt - НЕ КРОССПЛАТФОРМЕННАЯ СРЕДА! При миграции с Windows на Linux даже цвета у кнопок и радиобаттонов исчезли! Исправляется записью в main.cpp "qAppl.setStyle("windows");"; - так и не удалось заставить корректно работать валидаторы (проверка диапазона). Частичную работу обеспечивает смена локали: "QLocale::setDefault(QLocale(QLocale::English, QLocale::CyrillicScript, QLocale::UnitedStates));"; Т.к. InputMask некорректна в использовании при присвоении данных - поля остаются без валидации; - QTimer способен срабатывать раньше своего периода! В теле функции таймера приходится его притормаживать; - QTextBrowser не дает возможности корректно проверять количество строк в себе, когда их кол-во 0 или 1; - реализация полос прокрутки нелогична и сложна - вместо нее используется изменение положения формы при евенте скролла. При этом скролл работает с паразитными смещениями - они исправляются в showEvent. В showEvent нельзя закрывать форму. - QTimer срабатывает при вызванном ->start только тогда, когда код вызывающей функции полностью отработал - глюк. Это лишь самые вопиющие из ошибок, не говоря уже о мелких. Все приводит к тому, что приходится потратить много времени, чтобы создать проект-пустышку, чтобы использовать для новых разработок. Qt.cpp, содержащий замену штатным глючным функциям. Необходимые строки в файле проекта и иных местах для обеспечения кросплатформенности ПО. И т.д. Пока данная пустышка не выкладывается, т.к. для очистка существующего проекта потребуется время. Итог: если переломаться и изучить эту среду - возможно, переход на нее даст плоды в будущем за счет той же бесплатности и допиленной кроссплатформенности. Однако когда таймер начал вызываться раньше своего периода - чаша терпения почти переполнилась. (добавлено 23.08.2017) Забыл еще прикольные глюки этой среды, сегодня один такой долбанул: - можно спокойно выйти за пределы статичного массива, не получив никаких сообщений типа Overflow - и в результате перестанет работать какой-нибудь QStringList, не имеющий к массиву никакого отношения; - дебаггер отображает неверную информацию по unsigned long int, когда якобы 2 числа разные - а на деле они одинаковые. В итоге пришлось эти переменные записывать в файл - и только там они отобразились корректно. (добавлено 25.09.2017) Что создание второй формы в проекте, что обмен данными между формами - нетривиален, неочевиден, непонятен. Нигде еще такого гемора не видел. (добавлено 01.11.2017) Сделать программу, написанную в Qt, независимой от самой Qt - невозможно. Перенос библиотек .so в папку /usr/lib приводит лишь к тому, что ПО не запускается из-за отсутствия плагина XCB, входящего в состав Qt и недоступного для отдельного скачивания. Статическая линковка библиотек Qt (некоторые умельцы смогли-таки это сделать), со слов других программистов, является незаконной (неприменима к лицензиям GPL и LGPL). Поэтому на все ПК, использующие ПО на Qt, обязаны иметь установленную Qt. (добавлено 09.02.2018) Обновил список вопиющих глюков. К этому добавляется то, что если connect отработал некорректно, не подключился, или слот неправильно расположен в h-файле - не будет вообще никаких уведомлений. Я даже не буду добавлять в архив полезных исходников написанные за год функции: к черту эту среду с ее псевдокроссплатформенностью. Тут несколько функций размещу, как примеры глюков среды, с которыми пришлось бороться. #include "qmessagebox.h" //Для работы с MessageBox. #include "qtimer.h" //Для создания динамических таймеров. #include "math.h" //Для работы функций fmod и pow. #include "qfile.h" //Работа с файлами. #include "form_main.h" //Для connect. #include "qapplication.h" //Для пути проекта. #include "time.h" //Для vDelay. //Переменные для кнопок iMessageBox. extern const int iMsgBox_OK_Button = pow(2,0); extern const int iMsgBox_Save_Button = pow(2,1); extern const int iMsgBox_SaveAll_Button = pow(2,2); extern const int iMsgBox_Open_Button = pow(2,3); extern const int iMsgBox_Yes_Button = pow(2,4); extern const int iMsgBox_YesToAll_Button = pow(2,5); extern const int iMsgBox_No_Button = pow(2,6); extern const int iMsgBox_NoToAll_Button = pow(2,7); extern const int iMsgBox_Abort_Button = pow(2,8); extern const int iMsgBox_Retry_Button = pow(2,9); extern const int iMsgBox_Ignore_Button = pow(2,10); extern const int iMsgBox_Close_Button = pow(2,11); extern const int iMsgBox_Cancel_Button = pow(2,12); extern const int iMsgBox_Discard_Button = pow(2,13); extern const int iMsgBox_Help_Button = pow(2,14); extern const int iMsgBox_Apply_Button = pow(2,15); extern const int iMsgBox_Reset_Button = pow(2,16); extern const int iMsgBox_RestoreDefaults_Button = pow(2,17); /*Функция MessageBox глючит в Qt - пришлось писать свою функцию. Обязательные параметры: - qsTitle_Message: заголовок сообщения; - qsMain_Message: основной текст сообщения. Необязательные параметры: - iIcon: иконка сообщения (см. условия); - iMode: суммарный код кнопок для пользователя. Для удобства можно складывать переменные кнопок; - iTime: время в секундах, когда автоматически закрыть сообщение с возвращением кода кнопки по умолчанию; - qsUnique_Button_Text: название для уникальной кнопки. При нажатии возвращает 0. Примеры использования: - iResult = iMessageBox("Внимание!", "Ошибка случилась.", 3, iMsgBox_OK_Button + iMsgBox_Help_Button, 30); - iResult = iMessageBox("Сообщение", "Уведомительный текст.", 1, 0, 0, "Моя кнопка", 1024). */ int iMessageBox(QString qsTitle_Message, QString qsMain_Message, int iIcon = 0, int iMode = 1, int iDefault_Button = 0, int iTime = 0, QString qsUnique_Button_Text = "") { QMessageBox qMsgBox; QTimer qTimer_Msgbox_Down; //Таймер закрытия окна автоматически с отработкой кнопки по умолчанию. qMsgBox.setText(qsTitle_Message); qMsgBox.setInformativeText(qsMain_Message); switch (iIcon) { case 0: qMsgBox.setIcon(QMessageBox::NoIcon); break; case 1: qMsgBox.setIcon(QMessageBox::Information); break; case 2: qMsgBox.setIcon(QMessageBox::Warning); break; case 3: qMsgBox.setIcon(QMessageBox::Critical); break; case 4: qMsgBox.setIcon(QMessageBox::Question); break; } QString qsMode = QString::number(iMode, 2); //Можно и через QBitArray. for (int i=qsMode.length()-1; i>=0; i--) { if (qsMode[i]=='1') { switch (qsMode.length()-1-i) { case 0: qMsgBox.addButton(QMessageBox::Ok); break; case 1: qMsgBox.addButton(QMessageBox::Save); break; case 2: qMsgBox.addButton(QMessageBox::SaveAll); break; case 3: qMsgBox.addButton(QMessageBox::Open); break; case 4: qMsgBox.addButton(QMessageBox::Yes); break; case 5: qMsgBox.addButton(QMessageBox::YesToAll); break; case 6: qMsgBox.addButton(QMessageBox::No); break; case 7: qMsgBox.addButton(QMessageBox::NoToAll); break; case 8: qMsgBox.addButton(QMessageBox::Abort); break; case 9: qMsgBox.addButton(QMessageBox::Retry); break; case 10: qMsgBox.addButton(QMessageBox::Ignore); break; case 11: qMsgBox.addButton(QMessageBox::Close); break; case 12: qMsgBox.addButton(QMessageBox::Cancel); break; case 13: qMsgBox.addButton(QMessageBox::Discard); break; case 14: qMsgBox.addButton(QMessageBox::Help); break; case 15: qMsgBox.addButton(QMessageBox::Apply); break; case 16: qMsgBox.addButton(QMessageBox::Reset); break; case 17: qMsgBox.addButton(QMessageBox::RestoreDefaults); break; } } } //В Linux QT не может использовать в case switch переменные. if (iDefault_Button==iMsgBox_OK_Button) qMsgBox.setDefaultButton(QMessageBox::Ok); else if (iDefault_Button==iMsgBox_Save_Button) qMsgBox.setDefaultButton(QMessageBox::Save); else if (iDefault_Button==iMsgBox_SaveAll_Button) qMsgBox.setDefaultButton(QMessageBox::SaveAll); else if (iDefault_Button==iMsgBox_Open_Button) qMsgBox.setDefaultButton(QMessageBox::Open); else if (iDefault_Button==iMsgBox_Yes_Button) qMsgBox.setDefaultButton(QMessageBox::Yes); else if (iDefault_Button==iMsgBox_YesToAll_Button) qMsgBox.setDefaultButton(QMessageBox::YesToAll); else if (iDefault_Button==iMsgBox_No_Button) qMsgBox.setDefaultButton(QMessageBox::No); else if (iDefault_Button==iMsgBox_NoToAll_Button) qMsgBox.setDefaultButton(QMessageBox::NoToAll); else if (iDefault_Button==iMsgBox_Abort_Button) qMsgBox.setDefaultButton(QMessageBox::Abort); else if (iDefault_Button==iMsgBox_Retry_Button) qMsgBox.setDefaultButton(QMessageBox::Retry); else if (iDefault_Button==iMsgBox_Ignore_Button) qMsgBox.setDefaultButton(QMessageBox::Ignore); else if (iDefault_Button==iMsgBox_Close_Button) qMsgBox.setDefaultButton(QMessageBox::Close); else if (iDefault_Button==iMsgBox_Cancel_Button) qMsgBox.setDefaultButton(QMessageBox::Cancel); else if (iDefault_Button==iMsgBox_Discard_Button) qMsgBox.setDefaultButton(QMessageBox::Discard); else if (iDefault_Button==iMsgBox_Help_Button) qMsgBox.setDefaultButton(QMessageBox::Help); else if (iDefault_Button==iMsgBox_Apply_Button) qMsgBox.setDefaultButton(QMessageBox::Apply); else if (iDefault_Button==iMsgBox_Reset_Button) qMsgBox.setDefaultButton(QMessageBox::Reset); else if (iDefault_Button==iMsgBox_RestoreDefaults_Button) qMsgBox.setDefaultButton(QMessageBox::RestoreDefaults); if (iTime!=0) { Form_Main::connect(&qTimer_Msgbox_Down, &QTimer::timeout, &qMsgBox, &QMessageBox::close); qTimer_Msgbox_Down.start(iTime*1000); } if (qsUnique_Button_Text!="") qMsgBox.addButton(qsUnique_Button_Text,QMessageBox::AcceptRole); return qMsgBox.exec()/0x400; } //Сверхточное сравнение для float и double с избавлением от паразитной погрешности. bool bIs_Equal(double dValue1, double dValue2, bool bDouble) { if (bDouble && fabs(dValue1-dValue2)<10e-8f) return true; else return false; if (!bDouble && fabs(dValue1-dValue2)<10e-4f) return true; else return false; } //Округление с погрешностью ~10^-9. double dRound(double dValue, int iSigns, bool bMode = true) { double dTrue_Round = round(dValue * pow(10,iSigns)) / (pow(10,iSigns)); if (bMode) return dTrue_Round; //Обычное округление по правилам алгебры. else if (dValue-dTrue_Round<0) return dTrue_Round - pow(10,-iSigns); else if (dValue-dTrue_Round>0)return dTrue_Round + pow(10,-iSigns); //Обратная логика, для намеренного создания ошибки округления. else return dValue; } void vDelay(unsigned long iMICROSecDelay) { unsigned long int SEC2NANO=1000000000; double diff=0; struct timespec ts1, ts2; clock_gettime(CLOCK_MONOTONIC, &ts1); while(true) { clock_gettime(CLOCK_MONOTONIC, &ts2); diff=(double)(ts2.tv_sec-ts1.tv_sec)*(double)SEC2NANO + ((double)ts2.tv_nsec - (double)ts1.tv_nsec); if (diff/1000>=iMICROSecDelay) return; } } void vScreenShot(QWidget *qWidget, QString qsObject) { QImage qImage(qWidget->size(), QImage::Format_ARGB32_Premultiplied); qWidget->render(&qImage); int iFile_Number = 1; while (QFile::exists("Скриншот " + qsObject + " №" + QString::number(iFile_Number) + ".png")) iFile_Number++; qImage.save("Скриншот " + qsObject + " №" + QString::number(iFile_Number) + ".png"); if (QFile::exists("Скриншот " + qsObject + " №" + QString::number(iFile_Number) + ".png")) iMessageBox("Сохранен файл \"Скриншот " + qsObject + " №" + QString::number(iFile_Number) + ".png\"" + ".", "Скриншот " + qsObject + " сохранен в " + QApplication::applicationDirPath() + "/.",1); else iMessageBox("Ошибка сохранения!", "Скриншот " + qsObject + " не сохранился на диск.",2); } (добавлено 19.04.2022) Те, кто изучал одновременно Qt и Visual Studio 2019, заявляют: Qt - отстой, но бесплатна для организации. |
|||
Обновлено ( 19.04.2022 19:05 ) |