Проект Simple Tracker. Часть 6: сервер. Слоты сервера

C++, Qt, Iscander Che, Simple Tracker

Слоты, имеющие отношение непосредственно к серверу, мы рассмотрим в связке с клиентом. Пока же сосредоточимся на тех слотах, которые относятся к управлению проектами и задачами и общих слотах приложения.

Начнём с соединений. Их я вынес в отдельный метод void TrackerServer::createConnections() .

// trackerserver.cpp

// Метод создания подключений
void TrackerServer::createConnections()
{
    // Установка текущего проекта
    connect(projectView, &QListView::clicked, this, &TrackerServer::slotSetCurrentProject);
    connect(projectView, &QListView::activated, this, &TrackerServer::slotSetCurrentProject);

    // Подключение сигнала о смене статуса к обработчику
    connect(statusDelegate, &ComboBoxDelegate::commitData, this, &TrackerServer::slotChangeTaskStatus);

    // Создание новой задачи
    connect(newTaskButton, &QPushButton::clicked, this, &TrackerServer::slotNewTask);

    // Редактирование существующей задачи
    connect(editTaskButton, &QPushButton::clicked, this, &TrackerServer::slotEditTask);
    connect(taskView, &QTableView::doubleClicked, this, &TrackerServer::slotEditTask);

    // Удаление существующей задачи
    connect(deleteTaskButton, &QPushButton::clicked, this, &TrackerServer::slotDeleteTask);

    // Создание нового проекта
    connect(newProjectButton, &QPushButton::clicked, this, &TrackerServer::slotNewProject);

    // Архивация существующего проекта
    connect(archiveProjectButton, &QPushButton::clicked, this, &TrackerServer::slotArchiveProject);

    // Открытие архива проектов
    connect(openArchiveButton, &QPushButton::clicked, this, &TrackerServer::slotOpenArchive);

    // Открытие окна трекера
    connect(actionOpenWindow, &QAction::triggered, this, &TrackerServer::slotOpenTrackerWindow);

    // Открытие окна трекера по двойному клику на иконке в трее
    connect(trayIcon, &QSystemTrayIcon::activated, this, &TrackerServer::slotDoubleClickOpenTrackerWindow);

    // Завершение работы трекера
    connect(actionQuit, &QAction::triggered, qApp, &QApplication::quit);

    // Открытие окна настроек приложения
    connect(actionSettings, &QAction::triggered, this, &TrackerServer::slotOpenSettingsWindow);
}

Для упрощения работы с элементами списка проектов определяем перечисление для Qt::DisplayRole и дополнительных ролей Qt::UserRole .

// trackerserver.h

enum ProjectProperty
{
    VisibleName = Qt::DisplayRole,
    Name = Qt::UserRole + 1,
    Id = Qt::UserRole + 2,
    Manual = Qt::UserRole + 3,
    Arch = Qt::UserRole + 4
};
// trackerserver.cpp

// Слот установки текущего проекта
void TrackerServer::slotSetCurrentProject(QModelIndex index)
{
    // Получаем элемент модели, соответствующий полученному индексу
    QStandardItem* item = projectListModel->itemFromIndex(index);
    // Получаем id текущего проекта
    projectCurrentId = item->data(ProjectProperty::Id).toInt();
    // Устанавливаем условие фильтрации таблицы задач по текущему id проекта
    taskFilterModel->setFilterFixedString(QString("%1").arg(projectCurrentId));

    // Обновляем представление таблицы задач
    updateTaskView();

    // Считаем количество строк в модели задач
    int rowNumber = taskFilterModel->rowCount();

    // Если количество строк больше нуля
    if(rowNumber > 0)
    {
        // Получаем индекс нулевого элемента модели задач
        int row = 0;
        QModelIndex index = taskFilterModel->index(row, CustomSortFilterProxyModel::E_COLUMN_NUMBER);
        // Устанавливаем в представлении текущий элемент с этим индексом
        taskView->setCurrentIndex(index);
        // Делаем доступными кнопки редактирования и удаления задач
        editTaskButton->setEnabled(true);
        deleteTaskButton->setEnabled(true);
    }
    else
    {
        // Иначе блокируем кнопки редактирования и удаления задач
        editTaskButton->setDisabled(true);
        deleteTaskButton->setDisabled(true);
    }

    // Делаем доступной кнопку создания новой задачи
    newTaskButton->setEnabled(true);

    // Проверяем, закрыты ли все задачи текущего проекта
    if(database->isClosedAllTasks(projectCurrentId))
        // Если да, то делаем доступной кнопку архивирования проекта
        archiveProjectButton->setEnabled(true);
    else
        // Если нет, то блокируем кнопку архивирования проекта
        archiveProjectButton->setDisabled(true);

    // Устанавливаем в виде текущим элемент с полученным индексом
    projectView->setCurrentIndex(index);
}

// Слот создания нового проекта
void TrackerServer::slotNewProject()
{
    // Объявляем диалог создания нового проекта
    QSharedPointer<NewProjectDialog> dialog(new NewProjectDialog());
    // Созадаём диалоговое окно
    if(dialog->exec() == QDialog::Accepted)
    {
        // Если в диалоге нажата кнопка "ОК",
        // получаем из диалога наименование проекта
        QString projectName = dialog->getProjectName();
        // Если наименование проекта не пустое
        if(!projectName.isEmpty())
        {
            // Добавляем проект со свойство "без контроля СКВ"
            database->insertNewProject(projectName, ProjectVCS::WithoutVCS);
            // Обновляем модель проектов
            projectTableModel->select();

            // Устанавливаем свойства элемента
            QString visibleProjectName = projectName;
            int row = projectTableModel->rowCount() - 1;
            int projectId =
                projectTableModel->record(row).value(idProjectCol).toInt();
            int projectManual = ProjectVCS::WithoutVCS;
            int projectArch = ProjectArchive::Active;

            visibleProjectName = "(M) " + projectName;

            QStandardItem* item = new QStandardItem();
            // Загружаем данные в элемент
            item->setData(visibleProjectName, ProjectProperty::VisibleName);
            item->setData(projectName, ProjectProperty::Name);
            item->setData(projectId, ProjectProperty::Id);
            item->setData(projectManual, ProjectProperty::Manual);
            item->setData(projectArch, ProjectProperty::Arch);

            // Загружаем элемент в модель представления проектов
            projectListModel->appendRow(item);

            // Устанавливаем текущий id проекта равный id нового проекта
            projectCurrentId = projectId;
            // Получаем индекс элемента нового проекта в модели представления
            int column = 0;
            QModelIndex currentProjectIndex = projectListModel->index(row, column);
            // Устанавливаем условие фильтрации таблицы задач по текущему id проекта
            taskFilterModel->setFilterFixedString(QString("%1").arg(projectCurrentId));
            // Устанвливаем текущий проект по индексу
            slotSetCurrentProject(currentProjectIndex);

            // Поскольку новый проект пустой, блокируем кнопки удаления и редактирования задач,
            // и делаем доступной кнопку создания новой задачи
            editTaskButton->setDisabled(true);
            deleteTaskButton->setDisabled(true);
            newTaskButton->setEnabled(true);
        }
    }
}

// Слот архивации проекта
void TrackerServer::slotArchiveProject()
{
    // Определяем индекс текущего проекта
    QModelIndex currentProjectIndex = projectView->currentIndex();
    int row = currentProjectIndex.row();

    // Скрываем проект в представлении
    projectView->setRowHidden(row, true);

    // Устанавливаем для элемента модели пресдтавления свойство архивации проекта
    QStandardItem* item = projectListModel->item(row);
    item->setData(ProjectArchive::Archive, ProjectProperty::Arch);
    projectListModel->setItem(row, item);

    // Получаем из элемента модели представления id проекта
    int projectId = item->data(ProjectProperty::Id).toInt();
    // Архивируем проект
    database->archiveProject(projectId);

    // Блокируем кнопку архивации проектов
    archiveProjectButton->setDisabled(true);

    // Определяем, остались ли видимые элементы в списке проектов
    int numberRows = projectListModel->rowCount();
    int numberRowsNotHidden = 0;
    int firstRowNotHidden = 0;

    for(int row = 0; row < numberRows; ++row)
    {
        if(!projectView->isRowHidden(row))
        {
            ++numberRowsNotHidden;
            firstRowNotHidden = row;
            break;
        }
    }

    if(numberRowsNotHidden > 0)
    {
        // Если видимые элементы остались, определем индекс первого
        QModelIndex index = projectListModel->index(firstRowNotHidden, 0);
        // Устанавливаем этот элемент текущим проектом
        slotSetCurrentProject(index);
    }
    else
    {
        // Если видимых элементов нет, обнуляем текущий id проекта
        projectCurrentId = 0;
        // Устанавливаем условие фильтрации таблицы задач по текущему id проекта
        taskFilterModel->setFilterFixedString(QString("%1").arg(projectCurrentId));
        // Обновляем представление таблицы задач
        updateTaskView();
    }

}

// Слот открытия архива
void TrackerServer::slotOpenArchive()
{
    // Объявляем диалог открытия архива
    QSharedPointer<OpenArchiveDialog> dialog(new OpenArchiveDialog(projectListModel));
    // Если подтверждено извлечение из архива проекта/проектов
    if(dialog->exec() == QDialog::Accepted)
    {
        // Получаем список id извлекаемых из архива проектов
        QList<int> projectIdList = dialog->getListProjectId();
        int lastId = projectIdList.last();
        // Извлекаем проекты из архива
        foreach(int id, projectIdList)
            database->extractProject(id);

        // Обновляем модель списка проектов
        updateProjectListModel();
        // Обновляем виджет списка проектов
        updateProjectView();

        int numberRows = projectListModel->rowCount();

        // Определяем индес последнего извлечённого проекта
        QModelIndex lastIndex;
        for(int row = 0; row < numberRows; ++row)
        {
            QStandardItem* item = projectListModel->item(row);
            int projectId = item->data(ProjectProperty::Id).toInt();
            if(projectId == lastId)
                lastIndex = projectListModel->indexFromItem(item);
        }

        // Устанавливаем текущим последний извлечённый проект
        slotSetCurrentProject(lastIndex);
    }
}

// Слот изменения состояния задачи
void TrackerServer::slotChangeTaskStatus(QWidget* /* editor */)
{
    // Обновляем модель SQL-таблицы задач
    taskSqlTableModel->select();

    if(database->isClosedAllTasks(projectCurrentId))
        // Если все задачи закрыты, делаем доступной кнопку архивации проектов
        archiveProjectButton->setEnabled(true);
    else
        // Если нет, блокируем кнопку архивации проектов
        archiveProjectButton->setDisabled(true);
}

// Слот создания новой задачи
void TrackerServer::slotNewTask()
{
    // Объявляем диалог создания / редактирования задачи
    QSharedPointer<NewEditTaskDialog> dialog(new NewEditTaskDialog());
    // Если создание новой задачи подтверждено
    if(dialog->exec() == QDialog::Accepted)
    {
        // Получаем из диалога тип и описание задачи
        QString type = dialog->getType();
        QString description = dialog->getDescription();
        // Если тип задачи и описание не пустые
        if(!type.isEmpty() && !description.isEmpty())
        {
            // Добавляем новую задачу
            database->insertNewTask(projectCurrentId, type, description);
            // Обновляем модель SQL-таблицы задач
            taskSqlTableModel->select();
            // Обновляем представление задач
            updateTaskView();
            // Определяем номер строки созданной задачи
            int row = taskFilterModel->rowCount() - 1;
            // Определяем индекс новой задачи в модели представления задач
            QModelIndex index = taskFilterModel->index(row, CustomSortFilterProxyModel::E_COLUMN_NUMBER);
            // Устанавливаем эту задачу текущей в представлении
            taskView->setCurrentIndex(index);
            // Делаем доступными кнопки редактирования и удаления задач
            editTaskButton->setEnabled(true);
            deleteTaskButton->setEnabled(true);
        }
    }
}

// Слот редактирования задачи
void TrackerServer::slotEditTask()
{
    // Объявляем диалог создания / редактирования задачи
    QSharedPointer<NewEditTaskDialog> dialog(new NewEditTaskDialog());

    // Извлекаем из текущего индекса задачи id, тип и описание задачи
    QModelIndex currentIndex = taskView->currentIndex();
    QModelIndex idIndex = taskFilterModel->index(currentIndex.row(), CustomSortFilterProxyModel::E_COLUMN_NUMBER);
    QModelIndex typeIndex = taskFilterModel->index(currentIndex.row(), CustomSortFilterProxyModel::E_COLUMN_TYPE);
    QModelIndex descriptionIndex = taskFilterModel->index(currentIndex.row(), CustomSortFilterProxyModel::E_COLUMN_DESCRIPTION);

    QString type = taskFilterModel->data(typeIndex, Qt::DisplayRole).toString();
    QString description = taskFilterModel->data(descriptionIndex, Qt::DisplayRole).toString();
    int id = taskFilterModel->data(idIndex, Qt::DisplayRole).toInt();

    // Передаём в диалог тип и описание задачи
    dialog->setType(type);
    dialog->setDescription(description);

    // Если редактирование задачи подтверждено
    if(dialog->exec() == QDialog::Accepted)
    {
        // Если тип или описание задачи отредактированы
        if(type != dialog->getType() || description != dialog->getDescription())
        {
            // Редактируем задачу в базе
            database->editTask(id, dialog->getType(), dialog->getDescription());
            // Обновляем модель SQL-таблицы задач
            taskSqlTableModel->select();
            // Обновляем представление задач
            updateTaskView();
            // Устанавливаем отредактированную задачу текущей в представлении
            taskView->setCurrentIndex(currentIndex);
        }
    }
}

// Слот удаления задачи
void TrackerServer::slotDeleteTask()
{
    // Объявляем диалог удаления задачи
    QSharedPointer<DeleteTaskDialog> dialog(new DeleteTaskDialog());

    // Извлекаем из индекса id задачи
    QModelIndex currentIndex = taskView->currentIndex();
    QModelIndex idIndex = taskFilterModel->index(currentIndex.row(), CustomSortFilterProxyModel::E_COLUMN_NUMBER);
    int id = taskFilterModel->data(idIndex, Qt::DisplayRole).toInt();

    // Если удаление задачи подтверждено
    if(dialog->exec() == QDialog::Accepted)
    {
        // Удаляем задачу из базы
        database->deleteTask(id);
        // Обновляем модель SQL-таблицы задач
        taskSqlTableModel->select();
        // Обновляем представление задач
        updateTaskView();
        // Определяем, остались ли видимые задачи
        int numberRows = taskFilterModel->rowCount();
        if(numberRows > 0)
        {
            // Если остались, делаем текущей первую
            int row = 0;
            QModelIndex index = taskFilterModel->index(row, CustomSortFilterProxyModel::E_COLUMN_NUMBER);
            taskView->setCurrentIndex(index);
        }
        else if(numberRows == 0)
        {
            // Если нет, блокируем кнопки редактирования и удаления задач
            editTaskButton->setDisabled(true);
            deleteTaskButton->setDisabled(true);
        }
    }
}

// Слот открытия окна трекера
void TrackerServer::slotOpenTrackerWindow()
{
    setVisible(true);
}

// Слот открытия окна трекера по двойному клику на иконке в трее

void TrackerServer::slotDoubleClickOpenTrackerWindow(QSystemTrayIcon::ActivationReason reason)
{
    if(reason == QSystemTrayIcon::DoubleClick)
        setVisible(true);
}

// Слот открытия окна настроек программы
void TrackerServer::slotOpenSettingsWindow()
{
    QSharedPointer<SettingsDialog> dialog(new SettingsDialog());
    dialog->exec();
}

Создание проекта через трекер ограничено только проектами без контроля СКВ. Это сделано для упрощения функционала самого трекера. Проекты под контролем СКВ отслеживаются трекером с помощью следующего кода.

// trackerserver.cpp

// Метод инициализации проектов под СКВ
void TrackerServer::initProjectsWithVCS()
{
    // В переменную записываем путь к репозиториям из объекта settings класса QSettings
    QString pathToProjects = settings.value("PathToProjects", "").toString();
    if(!pathToProjects.isEmpty())
    {
        // Если путь не пустой, формируем из списка репозиториев список проектов под СКВ
        QDir dir(pathToProjects);
        QStringList projectFileList;

        foreach(QFileInfo item, dir.entryInfoList())
        {
            if(item.isDir())
            {
                QString dirPath = item.absoluteFilePath();
                QString dirName = item.fileName();
                // Критерием проекта под СКВ является наличие в репозитории в папке hooks
                // файла log.txt, куда хуки pre-commit и post-commit вносят информацию
                // о номере коммита и текст коммита с номером закрываемой задачи
                QString fileNamePath = dirPath + "/hooks/log.txt";
                QFile file(fileNamePath);
                if(file.exists())
                    projectFileList << dirName;
            }
        }

        if(!projectFileList.isEmpty())
        {
            // Если список проектов под СКВ не пустой, извлекаем из базы данных
            // список проектов и соответствующих им id проектов
            QStringList projectNameList;
            QList<int> projectIdList;

            projectTableModel->select();

            int numberRows = projectTableModel->rowCount();

            for(int row = 0; row < numberRows; ++row)
            {
                int projectId =
                    projectTableModel->record(row).value(idProjectCol).toInt();
                QString projectName =
                    projectTableModel->record(row).value(visibleNameProjectCol).toString();
                int manualFlag =
                    projectTableModel->record(row).value(manualPropertyCol).toInt();
                if(manualFlag == ProjectVCS::WithVCS)
                {
                    projectIdList << projectId;
                    projectNameList << projectName;
                }
            }

            // Формируем список новых проектов, отсутствующих в базе
            QStringList newProjectList;

            foreach(QString file, projectFileList)
            {
                int count = 0;
                foreach(QString project, projectNameList)
                {
                    if(file == project)
                        ++count;
                }
                if(count == 0)
                    newProjectList << file;
            }

            // Формируем список проектов, подлежащих автоматической архивации,
            // если репозитории этих проектов были удалены
            QStringList archiveProjectList;

            foreach(QString project, projectNameList)
            {
                int count = 1;
                foreach(QString file, projectFileList)
                {
                    if(project == file)
                        --count;
                }
                if(count > 0)
                    archiveProjectList << project;
            }

            // Вносим новые проекты по списку
            foreach(QString project, newProjectList)
                    database->insertNewProject(project, ProjectVCS::WithVCS);

            // Архивируем проекты по списку
            foreach(QString project, archiveProjectList)
            {
                for(int i = 0; i < projectNameList.size(); ++i)
                {
                    if(project == projectNameList.at(i))
                    {
                        database->archiveProject(projectIdList.at(i));
                        // Получае перечень задач для данного проекта
                        QList<int> taskIdList = database->getIdListForProject(projectIdList.at(i));
                        foreach(int taskId, taskIdList)
                        {
                            if(!database->isClosed(taskId))
                                // Если задача не закрыта, закрываем её с пометкой "forced"
                                database->closeTask(taskId, "forced");
                        }
                    }
                }
            }

            // Обновляем SQL-модель таблицы задач
            taskSqlTableModel->select();
        }
    }
}
We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

В общем-то достаточно написать так

// trackerserver.h

enum ProjectProperty
{
    VisibleName = Qt::DisplayRole,
    Name = Qt::UserRole + 1,
    Id,
    Manual,
    Arch
};

Все enum после Name будут и так увеличиваться на единицу.

Объявление переменной row не нужно

        int row = 0;
        QModelIndex index = taskFilterModel->index(row, CustomSortFilterProxyModel::E_COLUMN_NUMBER);
        // Устанавливаем в представлении текущий элемент с этим индексом
        taskView->setCurrentIndex(index);

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

        taskView->setCurrentIndex(taskFilterModel->index(0, CustomSortFilterProxyModel::E_COLUMN_NUMBER));

foreach выполняет глубокое копирование , лучше переходите полноценно на современные стандарты C++. То есть вместо этого

        foreach(int id, projectIdList)
            database->extractProject(id);

Можно написать так

        for(int id : projectIdList)
            database->extractProject(id);

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

Там где у вас QModelIndex берутся, также можно переписать в одну строку, как в предыдущем варианте. В конечном счёте и так ясно, что за это индексы по enum CustomSortFilterProxyModel::E_COLUMN_TYPE например.

Думаю, что вот это

int numberRows = taskFilterModel->rowCount();
        if(numberRows > 0)
        {
            // Если остались, делаем текущей первую
            int row = 0;
            QModelIndex index = taskFilterModel->index(row, CustomSortFilterProxyModel::E_COLUMN_NUMBER);
            taskView->setCurrentIndex(index);
        }
        else if(numberRows == 0)
        {
            // Если нет, блокируем кнопки редактирования и удаления задач
            editTaskButton->setDisabled(true);
            deleteTaskButton->setDisabled(true);
        }

Можно переписать так

// rowCount не должен возвращать -1 в принципе, поэтому второе условие просто лишнее
if (taskFilterModel->rowCount() > 0)
{
    // Одной строкой достаточно, и так ясно, что происходит благодаря CustomSortFilterProxyModel::E_COLUMN_NUMBER
    taskView->setCurrentIndex(taskFilterModel->index(0, CustomSortFilterProxyModel::E_COLUMN_NUMBER));
}
else
{
    editTaskButton->setDisabled(true);
    deleteTaskButton->setDisabled(true);
}

Вот это лишнее переусложнение

void TrackerServer::slotOpenSettingsWindow()
{
    QSharedPointer<SettingsDialog> dialog(new SettingsDialog());
    dialog->exec();
}

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

void TrackerServer::slotOpenSettingsWindow()
{
    SettingsDialog dialog;
    dialog.exec();
}

Вот это

QList<int> taskIdList = database->getIdListForProject(projectIdList.at(i));
foreach(int taskId, taskIdList)
{
    if(!database->isClosed(taskId))
        // Если задача не закрыта, закрываем её с пометкой "forced"
        database->closeTask(taskId, "forced");
}

Можно ак переписать

for (int taskId :  database->getIdListForProject(projectIdList.at(i)))
{
    if (!database->isClosed(taskId))
        database->closeTask(taskId, "forced");
}

P/S/ Вы можете в статье добавлять рекомендуемые статьи, например, список всех статей по Simple Tracker. Тогда пользователи смогу лучше ориентироваться по всем вашим статьям.

Все enum после Name будут и так увеличиваться на единицу.

Это мне самому нужно было, чтобы при переписываниии с Qt::UserRole + n на enum не запутаться. Сам себе проблему придумал, поскольку сразу не ввёл enum.

Там где у вас QModelIndex берутся, также можно переписать в одну строку, как в предыдущем варианте.

Тоже пишу так для себя, поскольку ещё не до окнца освоился с QModelIndex.

lang-cpp for(int id : projectIdList)

Понял. Да, спасибо.

// rowCount не должен возвращать -1 в принципе, поэтому второе условие просто лишнее

Согласен.

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

Понял.

Comments

Only authorized users can post comments.
Please, Log in or Sign up
Donate

Hello, Dear Users of EVILEG!!!

If the site helped you, then support the development of the site financially, please.

You can do it by following ways:

Thank you, Evgenii Legotckoi

AT
Dec. 10, 2019, 8:06 a.m.
Anastasija Troschenkova

C++ - Test 001. The first program and data types

  • Result:60points,
  • Rating points-1
AT
Dec. 10, 2019, 8:02 a.m.
Anastasija Troschenkova

Qt - Test 001. Signals and slots

  • Result:73points,
  • Rating points1
g
Dec. 10, 2019, 4:51 a.m.
gisLu

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:40points,
  • Rating points-8
Last comments
Dec. 9, 2019, 3:41 a.m.
Evgenij Legotskoj

Эта ошибка invalid use of incomplete type ‘class Ui::AnotherWindow’ обычно говорит о том, что не найдено определение класса или структуры. Типичная проблема - не подключён заголовочны…
NB
Dec. 9, 2019, 3:36 a.m.
Nikolaj Batmanov

Ну, не настолько со мной всё полхо...))) Вроде бы. Я ж кнопки отрисовываю.
Dec. 9, 2019, 3:14 a.m.
Evgenij Legotskoj

Добрый день. У вас ui файлов по ходу нет. UI файлы используются для вёрстки в графическом дизайнере.
NB
Dec. 9, 2019, 3:05 a.m.
Nikolaj Batmanov

Здравствуйте! Полностью скопировал ваш пример к себе, чтобы разобраться. А он не хочет запускаться, дает ошибку: invalid use of incomplete type ‘class Ui::AnotherWindow’ ui(new Ui…
Dec. 8, 2019, 7:23 a.m.
Evgenij Legotskoj

У меня здесь есть одна старая статья с примером векторного редактора. Там есть ответы на ваши вопросы. Поизучайте Qt/C++ - Урок 072. Пример векторного редактора на Qt QGraphicsItem, QG…
Now discuss on the forum
MU
Dec. 11, 2019, 8:27 a.m.
Maciej Urmański

Thank you! Now works, and this is solution. num_embed = Embed.objects.filter(added_by=recipe.added_by).count()
Dec. 11, 2019, 8:12 a.m.
Mihailll

Так работает. Взял этот пример https://api-2d3d-cad.com/face_recognition_with_opencv/ void MainWindow::on_pushButton_4_clicked() //фото определение лица{ // Load Face cascade (.xml…
TD
Dec. 10, 2019, 4:14 a.m.
Timur Dosov

Спасибо, работает. А ещё вопрос: как загрузить страницу с динамической подгрузкой контента по скроллингу? Например - [https://ntvplus.ru/tv/]. Пока делаю через костыль - QApplication::s…
Dec. 9, 2019, 7:16 a.m.
qml_puthon_user

Я сделал простой вывод текста по испусканию сигнала... Чего не хватает программе?) Python: # системные библиотекиimport cv2import numpy as npimport sysimport threading# PyQt б…
SK
Dec. 8, 2019, 4:11 p.m.
Semen Kosandjak

інтерфейс qt, приклад того додаю на малюнку, я натискаю на кнопку і у мене з'являється 2 текст лайну в які я вводжу значення, тобто в 1 цифри,у другому випадку це літери, тобто c++, без графічно…
EVILEG
About
Services
© EVILEG 2015-2019
Recommend hosting TIMEWEB