Проект 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();
        }
    }
}
Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

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

// 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 в принципе, поэтому второе условие просто лишнее

Согласен.

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

Понял.

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
Ищу работу?
25,000.00 руб. - 30,000.00 руб.
Разработчик Qt/C++
Barnaul, Altai Krai, Russia

Для зарегистрированных пользователей на сайте присутствует минимальное количество рекламы

СВ
23 октября 2019 г. 1:00
Семен Волох

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:70баллов,
  • Очки рейтинга1
SS
22 октября 2019 г. 14:31
Samantha Smith

Qt - Тест 001. Сигналы и слоты

  • Результат:52баллов,
  • Очки рейтинга-4
МБ
21 октября 2019 г. 1:25
Михаил Булатов

C++ - Тест 002. Константы

  • Результат:16баллов,
  • Очки рейтинга-10
Последние комментарии
17 октября 2019 г. 2:17
Евгений Легоцкой

Используем, там где требуется :)
MP
17 октября 2019 г. 2:15
Mikhail Petrov

Совет: подключайте ресурсы динамически. Используйте Resource Compiler: https://doc.qt.io/qt-5/rcc.html
16 октября 2019 г. 6:45
Евгений Легоцкой

Если это не чистой воды спам, а по делу, то без проблем. Но в таком случае лучше создавайте отдельный вопрос на форуме . При создании вопроса есть поле, в котором можно указать статью…
КК
16 октября 2019 г. 6:39
Кирилл Кирилыч

А тут можно ссылки на сторонний ресурс показывать? Нашёл на habr похожую статью, только там чуток отличается код и про локальный сервер написано, нужно чтоб кто то понимающий посмотрел и своё …
Сейчас обсуждают на форуме
23 октября 2019 г. 4:06
Евгений Легоцкой

Ну если после обновления начало появляться, то тогда откатить драйвера. А вообще, если это жить не мешает и код работает как и раньше, то просто проигнорировать эти сообщения.
22 октября 2019 г. 2:42
Pavel K.

Всем привет , Пытаюсь реализовать класс для работы с блютуз (Bluetooth Handler) для мобилки , с использование QBluetoothDeviceInfo и QBluetoothDeviceDiscoveryAgent . Может у кого е…
22 октября 2019 г. 2:16
Pavel K.

попробуй сделать через свой собственный компонет , те возьми контрол Component, например , переорпедели как свой , в нем что нить типо проперти type : disk1, disk2 (сделай метод в структуре …
Е
22 октября 2019 г. 0:03
Евгений_Канусовский@1981

Этот алгоритм предназначен для того чтобы исключить из обработки строки содержащие буквенные символы. Если Вам не трудно опишите пожалуйста как бы Вы написали этот алгоритм, желательно в коде?
MP
21 октября 2019 г. 7:03
Mikhail Petrov

Зависит от вашей задачи. Можете обратить внимание на этот пример: https://doc.qt.io/qt-5/qtqml-referenceexamples-properties-example.html QQmlListProperty используется мною достаточно ч…
EVILEG
О нас
Услуги
© EVILEG 2015-2019
Рекомендует хостинг TIMEWEB