© EVILEG 2015-2018
Рекомендует хостинг
TIMEWEB
2 июля 2018 г. 12:04

Рефакторинг кода

Qt

Добрый день!

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

Есть несколько форм, которые отображаются в главном окне методом setCentralWidget() в зависимости от выбранного пользователем пункта меню (элементы управления на всех разные).

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

Код функции:

void MainWindow::createView(TableForm::Type type, int rowIndex, int id)
{
    switch (type) {
    case TableForm::users:
        userForm = new CreateUserForm(this);
        setCentralWidget(userForm);
        delete mainForm;
        this->prewView = userForm;

        if (rowIndex > -1) {
            userForm->setRowIndex(rowIndex);
        }

        connect(userForm, &CreateUserForm::sygnalBack, this, [this, type]() { setMainView(type); });
        connect(userForm, &CreateUserForm::sygnalSubmit, this, [this, type]() { setMainView(type); });
        break;
    case TableForm::logo:
        logoForm = new CreateLogoForm(this);
        setCentralWidget(logoForm);
        delete mainForm;
        this->prewView = logoForm;
		
		if (rowIndex > -1) {
            userForm->setRowIndex(rowIndex);
        }

        connect(logoForm, &CreateLogoForm::sygnalBack, this, [this, type]() { setMainView(type); });
        connect(logoForm, &CreateLogoForm::sygnalSubmit, this, [this, type]() { setMainView(type); });
        break;
     case

/*  ******  Дальше похожий код  ******  */

    default:
        break;
    }
}


***********

enum Type {
none = 0,
users,
logo,
/* и т.д. */
};

***************

// Форма основного представления

connect(mainForm, &TableForm::createData, this, &MainWindow::createView);


// Главная форма

connect(ui->createButton, &QToolButton::clicked, this, [this] {
emit createData(this->viewType); // Create new data
});


// Объявления в разных формах
void createData(Type type, int row=-1, int id=-1);

void createView(TableForm::Type type, int rowIndex, int id);

В принципе, повторяющийся код можно переписать на лямбду, потребуется использовать стандарт C++14



void MainWindow::createView(TableForm::Type type, int rowIndex, int id)
{
    auto initForm = [this](auto form) {
        setCentralWidget(form);
        delete mainForm;
        this->prewView = form;

        if (rowIndex > -1) {
            form->setRowIndex(rowIndex);
        }

        connect(form, &CreateUserForm::sygnalBack, this, [this, type]() { setMainView(type); });
        connect(form, &CreateUserForm::sygnalSubmit, this, [this, type]() { setMainView(type); });
    };
	
    switch (type) {
    case TableForm::users:
        userForm = new CreateUserForm(this);
	initForm(userForm);
        break;
    case TableForm::logo:
        logoForm = new CreateLogoForm(this);
	initForm(logoForm);
        break;
     case

/*  ******  Дальше похожий код  ******  */

    default:
        break;
    }
}
Также я рекомендовал бы использовать абстрактную фабрику для создания форм, если можете выделить базовый класс для всех форм (как минимум должен быть QWidget), хотя конечно если вам необходимо использовать именованые поля для каждой своей формы, то возможно, что абстрактная фабрика будет лишней.

Для Django рекомендую VDS-хостинг TIMEWEB

На каждой форме расположены поля ввода и др. элементы связанные с данными в таблицах базы данных. Связь через QDataWidgetMapper. Подходит ли такое под абстрактную фабрику?

Мне это особо ничего не говорит, если честно.

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

Для Django рекомендую VDS-хостинг TIMEWEB

Все формы имеют базовый класс QWidget, это можно сделать через абстрактную фабрику, но не уверен с QDataWidgetMapper, там уже нужно детали кода смотреть, как минимум сократите код с помощью лямбды, а там смотрите, нужно ли вам ещё что-то делать, возможно, что вам и этого хватит.

Для Django рекомендую VDS-хостинг TIMEWEB

  • #
  • 2 июля 2018 г. 12:32
А можно как-то через лямбду это выразить?

Или это врядли получится?

void TableForm::loadDataFromDB()
{
    // Load data

    QStringList headers;

    switch (this->viewType) {
        case TableForm::groups:
            mainModel = new QSqlRelationalTableModel(this);
            this->table = "groups";
            mainModel->setTable(this->table);

            mainModel->setSort(0, Qt::AscendingOrder);
            mainModel->select();
            ui->mainTableView->setModel(mainModel);
            ui->mainTableView->setColumnHidden(0, true); // Hide

            headers << trUtf8("id") << trUtf8("Group name") << trUtf8("Group description");

            // Columns size
            for (int i = 0; i < ui->mainTableView->horizontalHeader()->count(); i++) {
                ui->mainTableView->horizontalHeader()->setSectionResizeMode(i, QHeaderView::Stretch);
                mainModel->setHeaderData(i, Qt::Horizontal, headers.at(i));
            }
            break;

         case TableForm::document_family:
            mainModel = new QSqlRelationalTableModel(this);
            this->table = "document_family";
            mainModel->setTable(this->table);

            mainModel->setSort(0, Qt::AscendingOrder);
            mainModel->select();
            ui->mainTableView->setModel(mainModel);

            ui->mainTableView->setColumnHidden(0, true);    // Hide column

            // Columns size
            for (int i = 0; i < ui->mainTableView->horizontalHeader()->count(); i++) {
                ui->mainTableView->horizontalHeader()->setSectionResizeMode(i, QHeaderView::Stretch);
            }
            break;

case TableForm::security_questions:
mainModel = new QSqlRelationalTableModel(this); this->table = "data_sources"; mainModel->setTable(this->table); mainModel->setSort(0, Qt::AscendingOrder); mainModel->select(); ui->mainTableView->setModel(mainModel); // Select mainModel->setRelation(4, QSqlRelation("function_type", "id", "func_type")); mainModel->setRelation(5, QSqlRelation("direction_type", "id", "direct_type")); ui->mainTableView->setItemDelegate(new QSqlRelationalDelegate(ui->mainTableView)); mainModel->select(); // Columns size for (int i = 0; i < ui->mainTableView->horizontalHeader()->count(); i++) { ui->mainTableView->horizontalHeader()->setSectionResizeMode(i, QHeaderView::Stretch); if (i < 1 || i == 3 || i > 5) ui->mainTableView->setColumnHidden(i, true); // Hide columns } break; default: break; } ui->mainTableView->setSelectionBehavior(QAbstractItemView::SelectRows); ui->mainTableView->setSelectionMode(QAbstractItemView::SingleSelection); ui->mainTableView->setEditTriggers(QAbstractItemView::NoEditTriggers); }
Все формы имеют базовый класс QWidget, это можно сделать через абстрактную фабрику, но не уверен с QDataWidgetMapper, там уже нужно детали кода смотреть, как минимум сократите код с помощью лямбды, а там смотрите, нужно ли вам ещё что-то делать, возможно, что вам и этого хватит.

Спасибо! Попробую покопать в эту сторону, а то форм таких многовато.

Ну как минимум эту часть кода можно зашаблонить

this->table = "groups";
mainModel->setTable(this->table);

mainModel->setSort(0, Qt::AscendingOrder);
mainModel->select();
ui->mainTableView->setModel(mainModel);

Для Django рекомендую VDS-хостинг TIMEWEB

Если я правильно понял, то это должно выглядеть так ( ? ) :

void TableForm::loadDataFromDB()
{
	QStringList headers;
	
	this->table = "groups"; // Не совсем понимаю как зашаблонить
	///this->table = "....";
	//this->table = ".....";
	
	mainModel = new QSqlRelationalTableModel(this);
	mainModel->setTable(this->table);
	mainModel->setSort(0, Qt::AscendingOrder);
        mainModel->select();
        ui->mainTableView->setModel(mainModel);
        ui->mainTableView->setColumnHidden(0, true); // Hide

	// Columns size
        for (int i = 0; i < ui->mainTableView->horizontalHeader()->count(); i++) {
           ui->mainTableView->horizontalHeader()->setSectionResizeMode(i, QHeaderView::Stretch);
           mainModel->setHeaderData(i, Qt::Horizontal, headers.at(i));
        }

	switch (this->viewType) {
        case TableForm::groups:
		headers << trUtf8("id") << trUtf8("Group name") << trUtf8("Group description");
	   break;
	case TableForm::document_family:
		headers << ..........;
	   break;
	case TableForm::security_questions:
	        mainModel->setRelation(4, QSqlRelation("function_type", "id", "func_type"));
                mainModel->setRelation(5, QSqlRelation("direction_type", "id", "direct_type"));
                ui->mainTableView->setItemDelegate(new QSqlRelationalDelegate(ui->mainTableView));
                mainModel->select();
			
		// Columns size
               for (int i = 0; i < ui->mainTableView->horizontalHeader()->count(); i++) {
                 if (i < 1 || i == 3 || i > 5)
                    ui->mainTableView->setColumnHidden(i, true);    // Hide columns
               }
          break;
			
	/* ***** */
			
	default:
           break;
    }
    	
    ui->mainTableView->setSelectionBehavior(QAbstractItemView::SelectRows);
    ui->mainTableView->setSelectionMode(QAbstractItemView::SingleSelection);
    ui->mainTableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
}

А как это зашаблонить:
this->table = "groups";

Создать их список и потом в зависимости от
this->viewType

а выбирать:

QStringList view;

/***************/

this->table = view.at(/*viewType*/) /* Примерно так */

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

Для Django рекомендую VDS-хостинг TIMEWEB

Спасибо! Статью обязательно прочту.

Компилятор clang выдал:

error: 'auto' not allowed in lambda parameter
auto initForm = [this](auto form) {
                                       ^~~~
В CMakeList.txt

set (CMAKE_CXX_FLAGS                "-Wall -std=c++14")

Перепишите так

auto initForm = [this](auto form)  -> void 
{
    // Some code
}

Я забыл, что нужно ещё указывать тип возвращаемого значения в данном случае.

Для Django рекомендую VDS-хостинг TIMEWEB

  • #
  • 2 июля 2018 г. 13:36
Ничего не изменилось:

error: 'auto' not allowed in lambda parameter
auto initForm = [this](auto form)  -> void {
                                      ^~~~
error: variable 'rowIndex' cannot be implicitly captured in a lambda with no capture-default specified
form->setRowIndex(rowIndex);    // Edit existing data (current selected row index)
                                     ^
note: candidate function not viable: no known conversion from 'CreateUserForm *' to 'int' for 1st argument
auto initForm = [this](auto form)  -> void {
                            ^

Так, захватить rowIndex написано в ошибке и что-то с указателем, не уверен, но попробуйте так

auto initForm = [this, &rowIndex](auto* form) {
        setCentralWidget(form);
        delete mainForm;
        this->prewView = form;

        if (rowIndex > -1) {
            form->setRowIndex(rowIndex);
        }

        connect(form, &CreateUserForm::sygnalBack, this, [this, type]() { setMainView(type); });
        connect(form, &CreateUserForm::sygnalSubmit, this, [this, type]() { setMainView(type); });
    };

Для Django рекомендую VDS-хостинг TIMEWEB

И ещё, стандарт в CMake устанавливается так

set(CMAKE_CXX_STANDARD 14)

Для Django рекомендую VDS-хостинг TIMEWEB

Ругался на type добавил получилось такое:

auto initForm = [this, &rowIndex, &type](auto form) { ... }

Пишет:

error: no matching member function for call to 'connect'
connect(form, &CreateUserForm::sygnalBack, this, [this, type]() { setMainView(type); });
^~~~~~~

note: in instantiation of function template specialization 'MainWindow::createView(TableForm::Type, int, int)::(anonymous class)::operator()<CreateGroupForm *>' requested here
initForm(groupForm);
                ^
C:/msys64/mingw64/include/QtCore/qobject.h:314:13: note: candidate function [with Func1 = void (CreateUserForm::*)(), Func2 = (lambda at mainwindow.cpp:157:58)] not viable: no known conversion from 'CreateGroupForm *' to 'const typename QtPrivate::FunctionPointer<void (CreateUserForm::*)()>::Object *' (aka 'const CreateUserForm *') for 1st argument
connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, const QObject *context, Func2 slot,
^


Спасибо добавил:
set(CMAKE_CXX_STANDARD 14)

В принципе, если у вас везде один и тот же класс в этих формах используется, то тогда так


auto initForm = [this, &rowIndex, &type](CreateUserForm* form) { ... }

Для Django рекомендую VDS-хостинг TIMEWEB

Классов несколько, ошибки однотипные, поэтому скопировал одну.

У вас эти куски кода отличаются?

        connect(logoForm, &CreateLogoForm::sygnalBack, this, [this, type]() { setMainView(type); });
        connect(logoForm, &CreateLogoForm::sygnalSubmit, this, [this, type]() { setMainView(type); });

Для Django рекомендую VDS-хостинг TIMEWEB

Да, для каждой формы свой

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

Для Django рекомендую VDS-хостинг TIMEWEB

Имена сигналов одинаковые, разные только названия классов.


Можно пример общего класса, а то я не совсем представляю как это сделать с классами Форм в Qt.

class A ....

class B: public A ...







Хорошо, что у меня было время сегодня. Вот материал вам на изучение о наследовании форм от кастомного класса.

Для Django рекомендую VDS-хостинг TIMEWEB

Огромное спасибо!  Наследование обычное, а вот про добавление информации о родителей в ui файл потомка я не знал.


И такой вопрос,  а если написать это наследование от базового класса и дописать ещё один класс типа:

class Base {
public :
Base (BaseForm *form) : frm(form) {}
~Base() {delete from; }

private :
BaseForm *frm;
};

И затем использовать:
Base *base = new Base(new FirstForm);
Base *base = new Base(new SecondForm);

Чтобы уйти от лямбды, которая не компилируется. Или это слишком?




















Смысла не вижу в этом классе Base.

Вы же можете присваивать указатель на потомка указателю базового класса. Этого должно быть достаточно.
BaseForm* form = new FirstForm();
form = new SecondForm();

Для Django рекомендую VDS-хостинг TIMEWEB

Написал базовый класс BaseForm.

Переписал код так:


    auto initForm = [this, &rowIndex, &type, &id](auto form) {
        setCentralWidget(form);
        delete mainForm;
        this->prewView = form;  // Save current view

        if (rowIndex > -1) {
            docFamilyForm->setRowIndex(rowIndex, id);   // Edit existing data (current selected row index)
        }
        else {
            docFamilyForm->setRowIndex(rowIndex, -1);
        }

        connect(form, &BaseForm::sygnalBack, this, [this, type]() { setMainView(type); });
        connect(form, &BaseForm::sygnalSubmit, this, [this, type]() { setMainView(type); });
    };

    switch (type) {
    case TableForm::groups:
        groupForm = new CreateGroupForm(this);
        initForm(groupForm);
        break;
    case TableForm::users:
        userForm = new CreateUserForm(this);
        initForm(userForm);
        break;
..........................
    default:
        break;
    }
}

Все скомпилировалось только при попытке открытия одной из форм программа вылетает.

Код формы (заголовочный файл):

#include <QWidget>

class BaseForm : public QWidget
{
Q_OBJECT

public:
    explicit BaseForm(QWidget *parent = nullptr);

    virtual void setRowIndex(int rowIndex, int) = 0;

    virtual void submitChanges() = 0;

signals:
    virtual void sygnalBack()= 0;
    virtual void sygnalSubmit()= 0;
};


Файл реализации:

#include "BaseForm.h"

BaseForm::BaseForm(QWidget *parent) : QWidget(parent)
{

}

Сигналы-то зачем виртуальные? Причем чисто виртуальные без реализации. Да и насчёт submitChanges и setRowIndex я что-то сомневаюсь. Нужно ли их было делать чисто виртуальными, да и переопределяете ли вы правильно эти методы в наследниках.

Для Django рекомендую VDS-хостинг TIMEWEB

Ошибка в этом была :

if (rowIndex > -1) {
            docFamilyForm->setRowIndex(rowIndex, id);   // Edit existing data (current selected row index)
        }
        else {
            docFamilyForm->setRowIndex(rowIndex, -1);
        }

Должно быть так:


if (rowIndex > -1) {
            form->setRowIndex(rowIndex, id);   // Edit existing data (current selected row index)
        }
        else {
            form->setRowIndex(rowIndex, -1);
        }

Ошибка копипаста :(


Теперь все работает (setRowIndex - отрабатывает хорошо), кроме сигналов. Возможно я с ними погорячился или не так описал.

Код переопределения:


void setRowIndex(int rowIndex, int id) override;

void submitChanges() override;

signals:
    void sygnalBack() override;
    void sygnalSubmit() override;

Вот это не нужно

signals:
    void sygnalBack() override;
    void sygnalSubmit() override;

Сигналы нет смысла переопределять, это объявления методов без реализации. Значит их нужно только объявить в базовом классе.

Для Django рекомендую VDS-хостинг TIMEWEB

  • #
  • 3 июля 2018 г. 8:31

Спасибо большое! вы абсолютно правы, если определить только в базовом классе и не переопределять, то все работает.

  • #
  • 3 июля 2018 г. 8:48
Можете помочь с этим участком кода. Как его в лямбду переписать.

void TableForm::loadDataFromDB()
{
	QStringList headers;
	
	this->table = "groups"; // Не совсем понимаю как зашаблонить
	///this->table = "....";
	//this->table = ".....";
	
	mainModel = new QSqlRelationalTableModel(this);
	mainModel->setTable(this->table);
	mainModel->setSort(0, Qt::AscendingOrder);
        mainModel->select();
        ui->mainTableView->setModel(mainModel);
        ui->mainTableView->setColumnHidden(0, true); // Hide

	// Columns size
        for (int i = 0; i < ui->mainTableView->horizontalHeader()->count(); i++) {
           ui->mainTableView->horizontalHeader()->setSectionResizeMode(i, QHeaderView::Stretch);
           mainModel->setHeaderData(i, Qt::Horizontal, headers.at(i));
        }

	switch (this->viewType) {
        case TableForm::groups:
		headers << trUtf8("id") << trUtf8("Group name") << trUtf8("Group description");
	   break;
	case TableForm::document_family:
		headers << ..........;
	   break;
	case TableForm::security_questions:
	        mainModel->setRelation(4, QSqlRelation("function_type", "id", "func_type"));
                mainModel->setRelation(5, QSqlRelation("direction_type", "id", "direct_type"));
                ui->mainTableView->setItemDelegate(new QSqlRelationalDelegate(ui->mainTableView));
                mainModel->select();
			
		// Columns size
               for (int i = 0; i < ui->mainTableView->horizontalHeader()->count(); i++) {
                 if (i < 1 || i == 3 || i > 5)
                    ui->mainTableView->setColumnHidden(i, true);    // Hide columns
               }
          break;
			
	/* ***** */
			
	default:
           break;
    }
    	
    ui->mainTableView->setSelectionBehavior(QAbstractItemView::SelectRows);
    ui->mainTableView->setSelectionMode(QAbstractItemView::SingleSelection);
    ui->mainTableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
}

Насчёт размеров колонок не знаю, но вот эту часть можно написать так

auto initTable = [this](const QString& tableName)
{
    this->table = tableName;
    mainModel = new QSqlRelationalTableModel(this);
    mainModel->setTable(this->table);
    mainModel->setSort(0, Qt::AscendingOrder);
    mainModel->select();
    ui->mainTableView->setModel(mainModel);
    ui->mainTableView->setColumnHidden(0, true);
}

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



Для Django рекомендую VDS-хостинг TIMEWEB

Спасибо. Только таблиц 20 шт, возможно это будет слишком для каждой свой класс.

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

Для Django рекомендую VDS-хостинг TIMEWEB

Ответы

Только авторизованные пользователи могут отвечать на форуме.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
24 сентября 2018 г. 17:42
edorofeeva

C++ - Тест 001. Первая программа и типы данных

  • Результат 100баллов,
  • Очки рейтинга10
24 сентября 2018 г. 17:37
edorofeeva

C++ - Тест 001. Первая программа и типы данных

  • Результат 66баллов,
  • Очки рейтинга-1
23 сентября 2018 г. 14:38
No Names

C++ - Тест 001. Первая программа и типы данных

  • Результат 60баллов,
  • Очки рейтинга-1
Последние комментарии
24 сентября 2018 г. 15:09
Евгений Легоцкой

Qt Linux - Урок 001. Автозапуск Qt приложения под Linux

А вот здесь у меня есть пример использования supervisor. https://evileg.com/ru/post/3/ Вся статья вам там не интересна, интересен только шаг с настройкой supervisor. Он получается ...
24 сентября 2018 г. 15:00
avovana

Qt Linux - Урок 001. Автозапуск Qt приложения под Linux

Не могли бы дать ссылку на пример? Какое-то рабочее использование. Т.е. у меня есть Qt Gui App, которое я бы хотел запускать при старте системы и в случае, если оно грохнется. Если о чем Вы го...
24 сентября 2018 г. 14:55
Евгений Легоцкой

Qt Linux - Урок 001. Автозапуск Qt приложения под Linux

Если честно, то я не уверен, что это вообще можно реализовать через *.desktop файл. Я сделал предположение на основе того, что вы сказали про *.desktop и рестарт. Все варианты, котор...
24 сентября 2018 г. 14:47
avovana

Qt Linux - Урок 001. Автозапуск Qt приложения под Linux

Просто сейчас правлю сам файл example.desktop. Пытаюсь понять какую пару key=value мне нужно дописать.
24 сентября 2018 г. 14:42
Евгений Легоцкой

Qt Linux - Урок 001. Автозапуск Qt приложения под Linux

Ну я имел ввиду, что дописать в коде вот сюда то, о чём вы говорили про рестарт QString autorunContent("[Desktop Entry]\n" "Type=Application\n" ...
Сейчас обсуждают на форуме
24 сентября 2018 г. 16:47
Евгений_Канусовский@1981

Чтение файлов в python

Добрый вечер Евгений и форумчане! Столкнулся с проблемой чтения файлов в python: файлы с обычным текстом в формате las и txt читаются, например: ~Version information VERS.          ...
24 сентября 2018 г. 13:29
Евгений Легоцкой

Трансляция видео с помощью VLC по RTP

Добрый день! Я не сталкивался, но предположу, что нужно настроить Input Codec в VLC. В настройках есть секция Input Codec, возможно, что там установлено низкое разрешение. ...
21 сентября 2018 г. 8:25
Евгений Легоцкой

Прокси-модель, содержащая на 1 столбец больше, чем модель-источник.

Попробуйте ещё PySide 2 - это официально поддерживаемый пакет привязок Python к Qt, возможно, что там не будет таких проблем.
20 сентября 2018 г. 20:06
Евгений Легоцкой

Qt Installer Framework

Добрый день. Зачем собирать Qt Installer Framework-то из исходников? Я ещё понимаю Qt собирают из исходников статически (хотя тоже считаю по большей части бесполезной тратой времени),...
Присоединяйтесь к нам в социальных сетях