Собственные виджеты в Qt Designer [Qt, C++]

В статье я расскажу как быстро и легко накидать интерфейс в Qt Designer, а также, как использовать при этом свои собственные виджеты.

В статье описано:

  • как создать в Qt Designer форму, а затем использовать ее в программе (есть несколько вариантов сделать это, я расскажу только про один);
  • как использовать свой собственный виджет в Qt Designer. При этом я хотел взять виджет из предыдущей статьи, но нашел его унылым, поэтому тут, также, описывается виджет типа двоичного счетчика.

Снимок окна программы, которая получилась при написании статьи приведен на рис. 1.

рис. 1 пример окна, созданного в Qt Designer

рис. 1 пример окна, созданного в Qt Designer

В нижней части рисунка находится счетчик, значение которого увеличивается с заданной периодичностью. Счетчик можно запустить или остановить при помощи соответствующих кнопок.

Раньше я никогда дизайнером не пользовался, т.к. думал, что он недостаточно гибок (это верно, отчасти), и позволяет использовать лишь станадртные элементы управления. Однако, недавно выполнял несколько заказов, в которых требовалось наклепать бешено много окошек, кнопочек и т.п. (все элементы управления были стандартными), поэтому я решил использовать Qt Designer. Я остался очень доволен дизайнером, но решил попробовать использовать в нем кастомные виджеты (есть несколько способов добиться такого результата, я расскажу лишь про один из них), и написать про это статью :).

1 Виджет счетчика

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

На листинг 1 приведен исходный код файла заголовка счетчика.

#ifndef COUNTER_H
# define COUNTER_H
# include <QWidget>
# include <QVector>

//!< двоичный счетчик
class Counter :public QWidget {
  Q_OBJECT
public:
  Counter(QWidget* parent);
  virtual ~Counter();
  void set(int capacity, int delay);
              //!< установка разрядности и задержки таймера
protected slots:
  void on_tick();
              //!< обработка сигнала таймера
  void on_start();
              //!< обработка сигнала запуска
  void on_stop();
              //!< обработка сигнала остановки
protected:
  virtual void paintEvent(QPaintEvent *);

  int m_cap;
              //!< количество разрядов
  QVector<bool> m_val;
              //!< отображаемое значение
  QTimer *m_timer;
              //!< таймер
};

#endif // COUNTER_H

На листинг 1 все достаточно просто, я отмечу лишь один момент. Мы вынужденно написали метод set, для установки параметров счетчика, хотя, очевидно, что такую настройку можно было внести в конструктор. Дело в том, что конструктор классов, которые мы собираемся использовать в Qt Designer должны принимать лишь указатель на QWidget.

Реализация методов класса Counter дана на листинг 2, при этом, стоит отметить, что при создании таймера, в качестве родительского элемента передается 0, а не this (6 строка) – это, также, связано с тем, что наш счетчик будет использоваться Qt Designer-ом (он не должен содержать дочерних элементов). В связи с этим, деструктор счетчика должен освобождать из под таймера память (автоматическая сборка мусора не сработает).

#include "counter.h"
#include <QPainter>
#include <QTimer>

Counter::Counter(QWidget *parent)
  :QWidget(parent), m_cap(0), m_timer(0) {
  m_timer = new QTimer(0);
  connect(m_timer, SIGNAL(timeout()), this, SLOT(on_tick()));
}

Counter::~Counter() { delete m_timer; }

void Counter::set(int capacity, int delay) {
  m_timer->stop();

  m_cap = capacity;

  m_val.clear();
  m_val.fill(false, m_cap);

  m_timer->start(delay);
}

void Counter::on_tick() {
  for (int i = m_cap - 1; i >= 0; --i) {
    if (false == m_val[i]) {
      m_val[i] = true;
      break;
    }
    m_val[i] = false;
  }
  repaint();
}

void Counter::on_start() { m_timer->start(); }

void Counter::on_stop() { m_timer->stop(); }

/*virtual*/ void Counter::paintEvent(QPaintEvent *) {
  QPainter painter(this);
  if (0 == m_cap) return;

  const int size = qMin(height(), width() / m_cap) - 1;

  for (int i = 0; i < m_cap; ++i) {
    painter.setBrush(QBrush(QColor(255, 0, 0, m_val[i] ? 255 : 0)));
    painter.drawEllipse(i * size, 0, size, size);
  }
}

Теперь у нас есть счетчик, который мы встроим в форму, созданную Qt Designer.

2 Qt Designer

Процесс создания формы очень прост – мы создаем чистую форму (при этом, даем ей имя), перетаскиваем на нее 2 кнопки и QWidget, устанавливаем их на нужных нам позициях, устанавливаем размеры и, главное, имена (objectName).

Если мы назовем форму MainUI – то будет автоматически создан файл с именем ui_mainui.h, содержащий класс MainUI. В свою очередь, класс будет содержать поля, имена которых совпадают с заданными в Qt Designer oojectName.

Теперь необходимо сообщить дизайнеру, что вместо виджета в окошке должен отображаться счетчик, для этого надо в настройках виджета выбрать “преобразовать в…”, в появившемся окне добавить виджет Counter (как показано на рис.2), при этом, окно будет автоматически связано с файлом counter.h.

рис. 2 окно преобразования виджета

рис. 2 окно преобразования виджета

Теперь у нас есть форма, содержащая нужный нам интерфейс, остается только использовать ее…

3 Использование формы, созданной в Qt Designer

Я дал своей форме имя MainUI, виджету – имя m_counter, а кнопка – m_start и m_stop, соответственно. При этом, рядом с файлом проекта появился файл ui_mainui.h, в пространстве имен Ui которого расположен класс MainUI. Есть 2 варианта использовать этот класс:

  • наследование. При этом, должно использоваться множественное наследование (например, от классов QWidget и Ui::MainUI). Наследование от Ui::MainUI должно быть закрытым для решения проблемы ромбовидного наследования;
  • агрегация. Класс агрегат хранит указатель на форму, созданную в Qt Designer. Мне этот вариант кажется более естественным, хотя, обращаться к элементам формы приходится через указатель (в отличии от варианта с наследованием).

Файл заголовка формы, используемой в программе приведен на листинг 3, реализация – на листинг 4.

#ifndef MAINUI_H
# define MAINUI_H
#include <QDialog>

namespace Ui {
  class MainUI;
}

class MainDialog :public QDialog {
  Q_OBJECT
public:
  explicit MainDialog(QWidget *parent = 0);
  ~MainDialog();
public slots:
private:
  Ui::MainUI *m_ui;
};

#endif // MAINUI_H

#include "mainui.h"
#include "ui_mainui.h"

MainDialog::MainDialog(QWidget *parent)
  : QDialog(parent), m_ui(new Ui::MainUI) {
  m_ui->setupUi(this);

  m_ui->m_counter->set(6, 200);

  connect(m_ui->m_start, SIGNAL(clicked()), m_ui->m_counter, SLOT(on_start()));
  connect(m_ui->m_stop, SIGNAL(clicked()), m_ui->m_counter, SLOT(on_stop()));
}

MainDialog::~MainDialog() {
  delete m_ui;
}

В 6 строке реализации форма, созданная дизайнером устанавливается для текущего диалога (this) при помощи метода setupUI.

Таким образом, практически одной мышью можно создать пользовательский интерфейс и использовать при этом нестандартные виджеты. Однако, при описании класса Counter отмечены особенности создания виджетов, используемых в Qt Designer (такой виджет не может содержать дочерних элементов, а его конструктор принимает единственный аргумент – указатель на родительский диалог).

Исходй код проекта статьи: Виджет счетчика[Qt] (исходный код)

8 thoughts on “Собственные виджеты в Qt Designer [Qt, C++]

    1. admin Post author

      На официальном сайте библиотеки Qt его можно скачать бесплатно: http://qt-project.org/downloads
      Скачивать Вам надо не QtDesigner, а библиотеку Qt, в стандартную поставку входит и среда разработки (QtCreator), частью которой является QtDesigner.

      При этом Qt можно использовать и без QtCreator, ведь QtCreator – это лишь IDE (среда разработки, примерно такая же как и Microsoft Visual Studio, но бесплатная, кроссплатформенная и с открытым исходным кодом). QtCreator можно использовать без Qt (Вы можете разрабатывать с ее помощью любые приложения), но с Qt ее очень удобно использовать, она специально под библиотеку заточена.

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

      Библиотека Qt бесплатна, QtCreator тоже. Единственное ограничение, которое накладывает лицензия – нельзя изменять код библиотеки и QtCreator. С другой стороны, мало кому это нужно, ведь у Microsoft Visual Studio код закрыт и его вообще невозможно изменять, но никто не жалуется по этому поводу.

      До версии Qt 4.7 запрещена была статическая сборка, но уже давно разрешена. Тем не менее, ряд не очень образованных товарищей до сих пор верит что статическая сборка запрещена (поэтому при распространении программы возле исполняемого файла должны лежать файлы библиотеки) и пытается играть на этом факте. Не верьте им :).

  1. Reef

    В общем начнем с файла *.pro

    #DEPENDPATH += .
    #INCLUDEPATH += .
    

    Зачем нужны эти строки? Я их закомментировал без этого у меня ничего не собиралось.
    Насколько я понимаю, здесь мы указываем путь к папке “INCLUDE” с библиотеками.

    У меня при сборке приложения выходит ошибка – компилятор не находит функцию set:

     m_ui->m_counter->set(6, 200);
    

    в файле mainui.cpp

  2. VAK

    Ценная статья, информация актуальная, пример хорошо подобранный, но у меня та же ошибка:
    m_ui->m_counter->set(6, 200), что затрудняет разбор программы.
    Смотрел ui_mainui.h, увидел только, что m_counter имеет тип QWidget, а нам нужен Counter.
    В диалоге преобразования виджета нет виджета Counter (как показано на рис.2), а кнопка Добавить пассивна?!
    Как дальше размотать этот клубок?

    1. admin Post author

      Спасибо, что рассказали решение проблемы – у меня бы руки до этого вопроса ближайшую неделю вряд-ли бы дошли. Кстати, расскажите если не сложно что вы сейчас пишите с использованием Qt и что было бы интересно почитать по этой теме?

  3. VAK

    Вы меня просто “сразили” своим интересом к моей скромной личности. Сейчас пишу программу управления намоточным станком. Ничего интересного, просто немного кода в области линейной трехмерной графики. Реализован редактор скрипта управления работой станка, на этой неделе стоит задача реализовать интерпретатор команд в виде отдельного виджета. Поэтому случайно наскочил на Ваш блог.

  4. VAK

    Кому интересно, сообщаю, что виджет, собранный из несколько стандартных виджетов при помощи QT Дизайнера, создан на основе методов, изложенных у Боровского и Бланшет/Саммерфильда. Но отправной точкой была данная статья.

Добавить комментарий