Справочник от Автор24
Поделись лекцией за скидку на Автор24

Написании программ в Qt

  • 👀 302 просмотра
  • 📌 225 загрузок
Выбери формат для чтения
Загружаем конспект в формате docx
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Конспект лекции по дисциплине «Написании программ в Qt» docx
Пролог. qDebug. При написании программ в Qt часто требуется вывести отладочную информацию, например значения переменных или содержимое файла. Конечно можно запустить программу в режиме отладки и используя точки останова и слежение за переменными пошагово пройти по программе, но не всегда это удобно и занимает много времени. Несколько удобнее просто выводить вспомогательную информацию на экран. Конечно для этого можно использовать QMessageBox, но это неудобно и не всегда возможно. Гораздо удобнее выводить информацию в лог Qt используя макрос qDebug(). Для его использования следует подключить заголовочный файл QtDebug. После этого можно использовать его как поток вывода, например: QString str; str ="CapybaraКапибара"; qDebug() <setupUi(this); QListWidgetItem *item; QStringList lst; lst << "Кристофер Экклестон" << "Дэвид Теннант" << "Мэтт Смит" <<"Питер Капальди" << "Джоди Уиттакер"; //список случайно выбранных актёров, который будет добавлен в виджет ui->listWidget->setSelectionMode(QAbstractItemView::MultiSelection); //аключение режима выбора нескольких элементов ui->listWidget->setViewMode(QListView::IconMode); //режим выводв - иконка и текст foreach (QString str, lst) { //для каждого элемента списка lst, берёт его содержимое, сохраняет в str и проводит следующие действия item = new QListWidgetItem(str,ui->listWidget); //создаёт элемент списка виджета и привязывает его к виджету item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsEditable|Qt::ItemIsSelectable|Qt::ItemIsDragEnabled); //устанавливает флаги для элемента item->setIcon(QPixmap(QString::number(i++) +".png")); //добавляет иконку для элемента } } Результат работы программы показан на рисунке 2. Рисунок 2 - Результат работы программы. Как видно основной алгоритм работы с QListWidget прост - создаётся элемент и добавляется в него либо как показано выше, либо через метод виджета addItem(QListWidgetItem * item). Используя метод sortItems(Qt::SortOrder order = Qt::AscendingOrder) можно отсортировать элементы списка внутри виджета. Используя метод item(int row) const можно получить элемент списка в строке row. Метод selectedItems() const позволяет получить выбранные элементы в виде списка. Метод currentItem() const позволяет получить текущий элемент списка. Для QTableWidget и QTreeWidget основной алгоритм работы схож, добавляются только специфические для них методы. Также все эти виджеты испускают сигналы при различных операциях с элементами как то выбор, изменение или нажатие, что позволяет использовать их, привязав к слотам других виджетов. Сигналы QListWidget: currentItemChanged(QListWidgetItem * current, QListWidgetItem * previous) currentRowChanged(int currentRow) currentTextChanged(const QString & currentText) itemActivated(QListWidgetItem * item) itemChanged(QListWidgetItem * item) itemClicked(QListWidgetItem * item) itemDoubleClicked(QListWidgetItem * item) itemEntered(QListWidgetItem * item) itemPressed(QListWidgetItem * item) itemSelectionChanged() Общий недостаток этих виджетов - единство модели и представления, т.е. данные, используемые в одном из виджетов не могут одновременно использоваться в другом, а также не существуют отдельно от виджета, что усложняет работу с большим объёмом данных. Также при использовании одних и тех же данных в разных виджетах возникает помимо проблемы дублирования информации, проблема синхронизации данных между виджетами. Для решения этих проблем и большей универсальности используется технология “Модель - представление”. Модель-представление в Qt. Основная структура работы с данными в Qt изображена на рисунках 3 и 4. Рисунок 3 - Структура Модель-Делегат-Представление Рисунок 4 - Расширенная схема структуры Модель-Делегат-Представление Как видно из схем, основная идея - отделение модели данных от их представления. Обычно такая архитектура называется MVC (Модель-Представление-Контроллер), но в Qt контроллер объединён с представлением.На основе входных данных создаётся модель (например список), который отображается в интерфейсе пользователя с помощью представления. Для осуществления взаимодействия между представлением и моделью используются делегаты. Модель отвечает за управление данными, как правило полученными из внешнего источника и предоставляет интерфейс для работы с ними. Представление - отвечает за представление данных пользователю. Выделение - специальная модель, облегчающая работу с выделенными в представлении данными. Делегат — это компонент, который отображает данные одного элемента модели и обеспечивает их редактирование. Экземпляры делегата создаются для каждого элемента модели и располагаются в представлении, которое по сути является контейнером. Именно делегат решает, как должны отображаться и редактироваться данные конкретного элемента. Рассмотрим все элементы данной архитектуры. Модели в Qt Схема наследования классов моделей представлена на рисунке 5. Рисунок 5 - Иерархия классов моделей в Qt В этой схеме видны основные классы моделей. • QAbstractListModel - от этого класса обычно наследуются модели, реализованные в виде списка. Например от него унаследован стандартный класс QStringListModel, который представляет собой модель, данные в которой хранятся в виде списка строк. • QAbstractTableModel - от этого класса наследуются модели, которые будут предоставлять интерфейс для работы с данными в виде таблицы, т.е. с делением на столбцы и строки. Унаследованные от него стандартные классы предназначены для работы с внешней базой данных (продолжение следует в следующем семестре). • QAbstractProxyModel - от этого класса наследуются модели, которые планируется использовать в качестве промежуточной модели для сортировки или фильтрации данных из оригинальной модели. Унаследованные от него модели можно использовать в частности чтобы имея одну оригинальную модель данных использовать разный набор данных из неё в разных представлениях. Эти три класса объединяет то, что они абстрактные, т.е. создавать объекты этих классов невозможно и их следует использовать исключительно в качестве предка для создаваемых классов моделей. Для создания своих моделей необходимо наследовать класс модели от одного из вышеуказанных (в Qt Creator это можно сделать через пункт меню “создать новый файл”, выбрав в предложенном списке шаблонов “модель элемента Qt”). После этого необходимо добавить реализацию для виртуальных методов предка. При использовании Qt Creator будут автоматически созданы прототипы и шаблоны для них. • QFileSystemModel - стандартная модель, реализующая методы для работы со структурой файловой системы. Позволяет быстро и просто создать приложение для работы с объектами файловой системы. Пример: Создание простой программы для просмотра файловой системы Данное приложение будет представлять собой окно с двумя списками. Левый список представляет собой объект TreeView и будет использован для представления древовидной структуры каталогов. Правый список представляет собой объект TableView и будет использован для представления содержимого каталоге. Данные о файловой системе будут взяты с помощью модели QFileSystemModel. Общий вид формы, созданной в Qt Designer представлен на рисунке 6. Рисунок 6 - Общий вид формы Для TreeView необходимо отдельно описать слот clicked(), для TableView - activated() и для него же связать сигнал activated с его же слотом setRootIndex. Файл mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void on_treeView_clicked(const QModelIndex &index); void on_tableView_activated(const QModelIndex &index); private: Ui::MainWindow *ui; // Модели для работы с каталогами и файлами. Моделей создано две для раздельной фильтрации QFileSystemModel *dirModel; QFileSystemModel *fileModel; }; #endif // MAINWINDOW_H Файл mainwindow.cpp #include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); QString mPath = "C:/"; //Путь к корневому каталогу. Если не задан, то корневым будет корневой каталог файловой системы //Для левого списка dirModel = new QFileSystemModel(this); dirModel->setFilter(QDir::NoDotAndDotDot | QDir::AllDirs); //включённые фильтры (будут описаны далее) ui->treeView->setModel(dirModel); //Соединяет модель к представлению ui->treeView->setRootIndex(dirModel->setRootPath(mPath)); //Установка корневого каталога // Для правого списка fileModel = new QFileSystemModel(this); //Фильтр в данном списке не устанавливается ui->tableView->setModel(fileModel); ui->tableView->setRootIndex(fileModel->setRootPath(mPath)); } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_treeView_clicked(const QModelIndex &index) //при выборе каталога в treeView { QString mPath = dirModel->fileInfo(index).absoluteFilePath(); //Получение полного пути к выбранному каталогу ui->tableView->setRootIndex(fileModel->setRootPath(mPath)); //Установка полученного пути в качестве корневого для tableView } void MainWindow::on_tableView_activated(const QModelIndex &index) //при двойном щелчке на элемент в tableView { QString mPath = fileModel->fileInfo(index).absoluteFilePath(); //Получение полного пути к выбранному каталогу ui->treeView->setCurrentIndex(dirModel->setRootPath(mPath)); //Установка полученного пути в качестве текущего для treeView } Результат работы приложения представлен на рисунке 7. Рисунок 7 - Результат работы программы Далее будут описаны возможные фильтры для объектов файловой системы. QDir::Dirs Только каталоги, соответствующие фильтру QDir::AllDirs Все каталоги, т.е. другие фильтры не применяются QDir::Files Только файлы QDir::Drives Только диски (игнорируются в бездисковых ФС) QDir::NoSymLinks Без символических ссылок QDir::NoDotAndDotDot Без адресов /. и /.. QDir::NoDot Без адреса /. QDir::NoDotDot Без адреса /.. QDir::AllEntries Отобразить всё QDir::Readable Только те файлы и/или директории, для которых есть право на чтение. Необходимо сочетать с Dirs или Files QDir::Writable Только те файлы и/или директории, для которых есть право на запись. Необходимо сочетать с Dirs или Files QDir::Executable Только те файлы и/или директории, для которых есть право на выполнение. Необходимо сочетать с Dirs или Files QDir::Modified Только модифицированные файлы (если поддерживается ФС) QDir::Hidden Только скрытые файлы QDir::System Только системные файлы QDir::CaseSensitive Учитывать регистр • QStandardItemModel - универсальная стандартная модель,может хранить любые типы элементов как в виде списка, так и в виде таблицы, однако данные в неё добавляются напрямую, что не позволяет использовать её при работе с большим объёмом данных. Представления в Qt. Представлениями в Qt считаются виджеты, унаследованные от класса QAbstractItemView. Иерархия классов представлений показана на рисунке 8. Рисунок 8 - Иерархия классов представлений в Qt Как видно из рисунка, основными представлениями являются: • QListView - представление в виде одномерного списка элементов. Поддерживает добавление иконок к элементам списка. • QTableView - представление в виде таблицы, • QTreeView - представление в виде иерархического дерева элементов. • QColumnView - представление в виде нескольких QListView. QHeaderView используется для управления заголовком представления. Также как видно из иерархии классов, упоминаемые ранее QListWidget, QTableWidget и QTreeWidget также являются разновидностью представления, но не имеют возможности работать с внешней моделью. Роли элементов модели в Qt Каждый элемент в модели может содержать набор данных, то, какие данные из набора будут использоваться в данный момент зависит от текущей так называемой роли элемента. В метод data() модели обычно передаётся индекс элемента модели и текущая роль и в зависимости от этого элемент возвращает подходящие текущей роли данные. К примеру если на элемент наведён указатель мыши, то для данных во всплывающей подсказке используются данные получаемые при передаче роли ToolTipRole. В то же время в самом поле демонстрируются данные, получаемые при передаче роли DisplayRole. Чаще всего используются следующие роли: • DisplayRole - текст для показа: • DecorationRole - растровое изображение; • FontRole - шрифт для текста; • ToolTipRole - текст для подсказки • WhatThisRole - текст для подсказки «Что это?» • TextColorRole - цвет текста; • BackgroundColorRole - цвет фона элемента. Полный список ролей тут: https://doc.qt.io/qt-5/qt.html#ItemDataRole-enum Делегаты и выделения в Qt. Для лучшей настройки представления модели в Qt введены понятия выделения элементов и делегат. Выделение представляет собой объект класса QItemSelectionModel, который привязывается к уже существующей модели и позволяет легко синхронизировать информацию о выделенном в модели элементе между разными представлениями. Пример создания собственной модели данных и использования выделения в Qt. В данной программе для наглядности не будет использован Qt Designer. Основной класс программы унаследован от QWidget. Также необходимо добавить класс модели данных, настроив его как показано на рисунке 9. Рисунок 9 - Добавление класса модели Qt Само приложение будет представлять собой окно с тремя представлениями QTreeView, QTableView и QListView. В качестве модели данных будет использована модель, представляющая собой список целых чисел, унаследованная от QAbstractListModel. Также будет создано выделение и подключено ко всем трём представлениям. В результате выбор элемента в одном представлении автоматически выберет его во всех. Под представлениями расположены две кнопки для добавления и удаления элементов модели. Класс основного окна - wgt, класс модели - IntListModel. Файл itemlistmodel.h #ifndef INTLISTMODEL_H #define INTLISTMODEL_H #include class IntListModel : public QAbstractListModel { Q_OBJECT public: explicit IntListModel(const QList& list,QObject *parent = nullptr); // Работа с заголовком: QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; // Основные функции: int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; // Редактируемость: bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex& index) const override; // Добавление строк: bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; // Удаление строк: bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; private: //Структура хранения данных - список данных типа int QList m_list; }; #endif // INTLISTMODEL_H Файл intlistmodel.cpp #include "intlistmodel.h" IntListModel::IntListModel(const QList& list,QObject *parent) : QAbstractListModel(parent), m_list(list) { } QVariant IntListModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) { return QVariant(); } return (orientation == Qt::Horizontal) ? QString("Число"):QString::number(section); } int IntListModel::rowCount(const QModelIndex &parent) const { //у списка нет родительских и дочерних элементов, так что данная проверка должна возвращать 0 if (parent.isValid()) return 0; return m_list.size(); } QVariant IntListModel::data(const QModelIndex &index, int role) const { //Если индекс неверен - возвращается пустой элемент if (!index.isValid()) return QVariant(); //Если индекс вне списка элементов - возвращается пустой элемент if (index.row() <0 || index.row() >= m_list.size()) { return QVariant(); } //Если роль у элемента подразумевает отображение или редактирование, то возвращается значение элемента, иначе - пустой элемет return (role == Qt::DisplayRole || role == Qt::EditRole) ? m_list.at(index.row()): QVariant(); } bool IntListModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (index.isValid() && role == Qt::EditRole) { //Если индекс верен и роль подразумевает редактирование - заменить элемент новым m_list.replace(index.row(), value.value() ); //испустить сигнал об изменении данных emit dataChanged(index, index); return true; } //если не удалось - вернуть ложь return false; } Qt::ItemFlags IntListModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = QAbstractListModel::flags(index); return index.isValid() ? (flags | Qt::ItemIsEditable):flags; } bool IntListModel::insertRows(int row, int count, const QModelIndex &parent) { if (parent.isValid()){ return false; } //стандартный метод, оповещающий все заинтересованные стороны о начале добавления строк beginInsertRows(QModelIndex(), row, row + count - 1); //добавление строк for (int i = 0; i < count; ++i) { m_list.insert(row, 0); } //оповещение интересующихся лиц о завершении добавления строк endInsertRows(); return true; } bool IntListModel::removeRows(int row, int count, const QModelIndex &parent) { if (parent.isValid()) return false; //аналогично но удаление вместо добавления beginRemoveRows(parent, row, row + count - 1); for (int i = 0; i < count; ++i) { m_list.removeAt(row); } endRemoveRows(); return true; } Файл wgt.h #ifndef WGT_H #define WGT_H #include #include #include #include "intlistmodel.h" class wgt : public QWidget { Q_OBJECT public: wgt(QWidget *parent = 0); ~wgt(); private: //Модель IntListModel *model; QTreeView* pTreeView; QListView* pListView; QTableView* pTableView; private slots: void addclicked(); void delclicked(); }; #endif // WGT_H Файл wgt.cpp #include "wgt.h" wgt::wgt(QWidget *parent) : QWidget(parent) { //Создание списка элементов QList List; for(int i=0;i<6;i++) List.append(i); //Создание модели с элементами созданного списка model = new IntListModel(List,this); //Создание трёх представлений pTreeView = new QTreeView; pTreeView->setModel(model); pListView = new QListView; pListView->setModel(model); pTableView = new QTableView; pTableView->setModel(model); //Создание и подключение выделения QItemSelectionModel *selection= new QItemSelectionModel(model,this); pTreeView->setSelectionModel(selection); pListView->setSelectionModel(selection); pTableView->setSelectionModel(selection); QPushButton *add = new QPushButton("Добавить",this); QPushButton *del = new QPushButton("Удалить",this); //Создание компоновщика QHBoxLayout* pHboxLayout = new QHBoxLayout; pHboxLayout->addWidget(pTreeView); pHboxLayout->addWidget(pListView); pHboxLayout->addWidget(pTableView); QVBoxLayout* Layout = new QVBoxLayout; Layout->addLayout(pHboxLayout); Layout->addWidget(add); Layout->addWidget(del); setLayout(Layout); connect(add,SIGNAL(clicked(bool)), this,SLOT(addclicked())); connect(del,SIGNAL(clicked(bool)), this,SLOT(delclicked())); } wgt::~wgt() { } void wgt::addclicked() { //Кнопка для добавления записей // Получение числа строк в модели int row = model->rowCount(); // Добавление одной строки после строки с ранее полученным номером model->insertRows(row,1); // Получение индекса добавленной строки QModelIndex index = model->index(row); pListView->setCurrentIndex(index); pListView->edit(index); } void wgt::delclicked() { model->removeRows(pListView->currentIndex().row(),1); } Результат работы программы представлен на рисунке 10. Рисунок 10 - результат работы программы. Делегатом в Qt называется объект, определяющий как именно будет представлена работа с конкретным элементом модели. К примеру как показано на рисунке 11, для Table View, заполненного числами в качестве делегата по умолчанию используется Spin Box. При необходимости, его можно изменить. Рисунок 11 - Представление Table View с использованием стандартного делегата Пример изменения делегата. В Qt Designer необходимо создать форму, изображённую на рисунке 11. Для кнопки “Выход” реализовать закрытие формы. После этого следует добавить новый класс, унаследованный от QItemDelegate, используя пункт меню “Добавить новый”. Свойства следует выставить в соответствии с рисунком 12. Рисунок 12 - Добавление нового класса Содержимое исходных файлов: Файл testdelegate.h #ifndef TESTDELEGATE_H #define TESTDELEGATE_H #include #include #include #include #include #include class TestDelegate : public QItemDelegate { Q_OBJECT public: explicit TestDelegate(QObject *parent = 0); //Необходимо переопределить стандартные методы родительского класса QItemDelegate //Создание редактора поля QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; // Подключение редактора поля void setEditorData(QWidget *editor, const QModelIndex &index) const; // Передача данных от редактора в модель void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const; //Передача редактору информации о местоположении void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const; signals: public slots: }; #endif // TESTDELEGATE_H Файл testdelegate.cpp #include "testdelegate.h" TestDelegate::TestDelegate(QObject *parent) : QItemDelegate(parent) { } QWidget* TestDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { //В качестве редактора будет выступать Line Edit QLineEdit *editor = new QLineEdit(parent); return editor; } // Передача данных из модели в редактор void TestDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { // Получение данных из модели int value = index.model()->data(index, Qt::EditRole).toInt(); // Передача данных в редактор QLineEdit *textbox = static_cast(editor); textbox->setText(QString::number(value)); } // Передача данных из редактора в модель void TestDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QLineEdit *textbox = static_cast(editor); int value = textbox->text().toInt(); model->setData(index, value, Qt::EditRole); } // Передача редактору данных о местоположении void TestDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const { editor->setGeometry(option.rect); } Файл mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include #include #include "testdelegate.h" namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); private: Ui::MainWindow *ui; // В этот раз используется QStandardItemModel QStandardItemModel *model; //Объявление делегата TestDelegate *myDelegate; }; #endif // MAINWINDOW_H Файл mainwindow.cpp #include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); //создание делегата myDelegate = new TestDelegate(this); model = new QStandardItemModel(4,2,this); //модель с четырьмя строками и двумя столбцами ui->tableView->setModel(model); for(int row = 0; row < 4; row++) //заполнение модели числами { for(int col = 0; col < 2; col++) { QModelIndex index = model->index(row,col,QModelIndex()); model->setData(index,(col+1)*10+row+1); } } ui->tableView->setModel(model); // Связь представления с делегатом ui->tableView->setItemDelegate(myDelegate); } MainWindow::~MainWindow() { delete ui; } Результат работы программы представлен на рисунке 13. Рисунок 13 - Результат работы программы. Использование промежуточных моделей в Qt. Как говорилось ранее, в Qt существует понятие промежуточной модели (Proxy Model). Как правило, она используется для выборки или сортировки данных из изначальной модели без её нарушения. Вместе с этим изменения значений данных в промежуточной модели вызовет изменение данных и в оригинальной модели. Пример: В данном приложении будет использовано два List View. В левом будут отображены все данные из модели String List Model. В правой только отобранные, согласно регулярному выражению. Само регулярное выражение будет задано в исходном тексте программы, но при желании программу можно легко доработать добавив текстовое поле для его ввода. Основной класс программы (widget) унаследован от QWidget. Исходные тексты программы: Файл widget.h #ifndef WIDGET_H #define WIDGET_H #include #include //да, опять:( class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = 0); ~Widget(); private: QStringListModel* model; QSortFilterProxyModel *proxyModel; QListView* pListView1; QListView* pListView2; }; #endif // WIDGET_H Файл widget.cpp #include "widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) { //создание основной модели model = new QStringListModel; //заполнение основной модели списком строк model->setStringList(QStringList() << "Кристофер Экклестон" << "Дэвид Теннант" << "Мэтт Смит" <<"Питер Капальди" << "Джоди Уиттакер" ) ; //создание промежуточной модели-фильтра proxyModel = new QSortFilterProxyModel; proxyModel->setSourceModel(model); //задание регулярного выражения в качестве условия выборки proxyModel->setFilterWildcard("Д*"); //сортировка элементов по возрастанию proxyModel->sort(Qt::AscendingOrder); pListView1 = new QListView; pListView1->setModel(model); pListView2 = new QListView; pListView2->setModel(proxyModel); QHBoxLayout* pHBoxLayout = new QHBoxLayout; pHBoxLayout->addWidget(pListView1); pHBoxLayout->addWidget(pListView2); setLayout(pHBoxLayout); } Widget::~Widget() { } Результат работы программы представлен на рисунке 14. Рисунок 14 - Результат работы программы. .
«Написании программ в Qt» 👇
Готовые курсовые работы и рефераты
Купить от 250 ₽
Решение задач от ИИ за 2 минуты
Решить задачу
Помощь с рефератом от нейросети
Написать ИИ

Тебе могут подойти лекции

Смотреть все 588 лекций
Все самое важное и интересное в Telegram

Все сервисы Справочника в твоем телефоне! Просто напиши Боту, что ты ищешь и он быстро найдет нужную статью, лекцию или пособие для тебя!

Перейти в Telegram Bot