0

Создание пользовательских виджетов

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

Настройка виджетов:

Видео урок по настройке виджетов Qt путем создания подкласса:

В некоторых случаях у нас появляется необходимость в доработке стандартных виджетов Qt, когда нам попросту не хватает стандартных настроек, которые представляет Qt Designer при реализации данного виджета. В данных ситуациях как раз и появляется необходимость в дополнительных настройках виджета. Для того чтобы реализовать дополнительные настройки какого то из базовых виджетов входящих в комплектацию Qt. Для расширения настроек мы просто создаем подкласс данного виджета с полной его адаптацией под наши требования. А при дальнейшей необходимости его в использовании – мы просто создаем объект данного виджета уже через имя подкласса данного виджета.

Для того чтобы продемонстрировать как все это реализовывается – мы создаем подкласс QSpinBox для дополнения возможностей надстройки наборному счетчику. По умолчанию QSpinBox счетчик может работать только с десятичной системой исчисления данных. Нашей же задачей будет являться доработать его таким образом – чтобы он мог принимать значения вместо десятеричной системы в шестнадцатеричной системе исчисления. Благодаря тому что мы создадим подкласс данного виджета HexSpinBox с наследованием QSpinBox как базовый класс. В дальнейшем просто создавая объекты подкласса HexSpinBox, сможем создавать уже настроенные под шестнадцатеричную систему счисления наборные счетчики HexSpinBox.

Шестнадцатеричный наборный счетчик HexSpinBox наследует большую часть функциональности от QSpinBox. Он содержит обычный конструктор и переопределяет три виртуальных метода класса QSpinBox.

У нашего наборного счетчика мы устанавливает диапазон по умолчанию от 0 до 255(от 0x00 до 0xFF), данный диапазон лучше всего соответствует шестнадцатеричному наборному счетчику, чем диапазон от 0 до 99, принимаемый по умолчанию в QSpinBox.

Пользователь может модифицировать текущее значение наборного счетчика, щелкая по верхней или нижней стрелке, или путем ввода значения в строке редактирования наборного счетчика. В последнем случае мы хотим, чтобы пользователь мог вводить только правильные шестнадцатеричные числа. Для достижения этого мы используем QregExpValidator, который принимает один или несколько символов, которые все должны входить в диапазоны («0», …, «9», «A» … «F», «a» … «f»).

Этот метод вызывается в QSpinBox для проверки корректного значения вводимое в QSpinBox. Результат может иметь одно из трех значений:

  • Invalid – Данное значение возвращается в случае не соответствия текста введенного в наборный счетчик.
  • Intermediate – (текс, вероятно, является частью допустимого значения)
  • Acceptable – Допустимое регулярное выражение.

QRegExpValidator имеет очень удобный метод validate(), поэтому мы просто возвращаем результат её выполнения. Теоретически следует возвращать значения Invalid или Intermediate для значений, лежащих за пределами наборного счетчика, но QSpinBox достаточно «умен» и может самостоятельно отследить данную ситуацию.

Метод textFromValue() преобразует целое число в строку, QSpinBox вызывает её для обновления строки в наборном счетчике, когда пользователь нажимает клавиши верхней или нижней стрелки наборного счетчика. Мы используем статический метод QString::number(), задавая 16 в качестве второго аргумента для преобразования значения в представленном нижнем регистре шестнадцатеричное число, и вызываем метод QString::toUpper() для преобразования результата в верхний регистр.

Метод valueFromText() выполняет обратное преобразование из строки в целое число. Она вызывается в QSpinBox, когда пользователь вводит значение в строку редактирования наборного счетчика и нажимает клавишу Enter. Мы используем метод QString::toInt() для попытки преобразования текущего текстового значения (возвращаемого QSpinBox::text()) в целое число, вновь используя 16 в качестве базы. Если строка не является правильным шестнадцатеричным числом, переменная ok будет установлена по ссылке на значение false и toInt() вернет значение 0. Здесь нет необходимости рассматривать такую возможность, поскольку контролирующий метод (validator) позволяет вводить только корректные значения. Вместо передачи адреса переменной ok мы могли бы задать нулевой указатель в первом аргументе метода toInt().

Этим мы завершили создание шестнадцатеричного наборного счетчика. Настройка других виджетов Qt происходит по подобной схеме: подобрать подходящий виджет Qt, создать его подкласс и переопределить несколько его виртуальных методов для переопределения режима его работы. Если все, что нам нужно, — это настроить внешний облик и методы существующего виджета, мы можем применить таблицу стилей или реализовать пользовательский стиль, не задавая подкласс виджета. Об этом мы поговорим в последующих статьях и уроках при более глубоком рассмотрении возможностей Qt.

Создание виджета: Редактор иконок!

Видео урок: создание QWidget.

Для начала начнем с рассмотрения заголовочного файла нашего редактора:

Класс IconEditor использует макрос Q_PROPERTY() для трех пользовательских свойств: penColor, iconImage и zoomFactor. Каждое свойство имеет тип данных, метод «чтения» и необязательный метод «записи». Например, свойство penColor имеет тип QColor и может считываться и записываться при помощи метода penColor() и setPenColor().

Когда мы используем виджет в Qt Designer, пользовательские свойства появляются в редакторе свойств Qt Designer ниже свойств, унаследованных от QWidget. Свойства могут иметь любой тип, поддерживаемый QVariant. Макрос Q_OBJECT необходим для классов, в которых определяются свойства.

IconEditor переопределяет три защищенные метода QWidget и имеет несколько закрытых методов и переменных. В трех закрытых переменных содержатся значения трех свойств.

Теперь пришло время рассмотреть файл реализации редактора иконок. Файл реализации класса начинается с конструктораIconEditor:

В конструкторе имеется несколько тонких моментов, связанных с применением атрибута Qt::Wa_StaticContents и вызовом метода setSizePolicy(). Вскоре мы обсудим их.

Устанавливается красный цвет пера. Коэффициент масштабирования изображения (zoom factor) устанавливается на 8, то есть каждый пиксель пиктограммы представляется квадратом 8 х 8.

Данные пиктограммы хранятся в переменной-члене image, и доступ к ним может осуществляться при помощи метода setIconImage() и iconImage(). Программа редактирования пиктограмм обычно вызывает метод  setIconImage()при открытии пользователем файла пиктограмм из памяти, когда пользователь хочет её сохранить. Переменная image имеет тип QImage. Мы инициализируем её областью 16 х 16 пикселей и на 32-битовый формат RGB, который поддерживает полупрозрачность. Мы очищаем данные изображения, устанавливая признак прозрачности.

Способ хранения изображения в классе QImage не зависит от оборудования. При этом его глубина может устанавливаться на 1, 8 или 32 бита. Изображение с 32-битовой глубиной используют по 8-бит на красный, зеленый и синий компоненты пикселя. В остальных 8 битах хранится альфа-компонент пикселя (уровень его прозрачности). Например, компоненты красный, зеленый и синий «чистого» красного цвета и альфа-компонент имеют значение 255, 0, 0 и 255. В Qt этот цвет можно задавать так:

QRgb red = qRgba(255, 0, 0, 255);

или так (поскольку этот цвет непрозрачен);

QRgb red = qRgb(255, 0, 0);

Тип QRgb просто синоним unsigned int, созданного при помощи директивы typedef, а qRgb() и qRgba() являются встроенными методами (то есть со спецификатором inline), которые преобразуют свои аргументы в 32-битовое целое число ARGB. Допускается также запись QRgb red = 0хFFFF0000; Где первые FF соответствуют альфа-компоненту, а вторые FF – красному компоненту. В конструкторе класса IconEditir мы делаем QImage прозрачным, используя 0 в качестве значения альфа-компонента.

В Qt для хранения цветов предусмотрено два типа: qRgb и qColor. В то время как qRgb всего лишь определяется в QImage ключевым словом typedef для представления пикселей 32-битовым значением, QColor является классом, который имеет много полезных методов и широко используется в Qt для хранения цветов. В виджете IconEditor мы используем QRgb только при работе с QImage; мы применяем QColor во всех остальных случаях, включая свойство цвета пера penColor.

Метод sizeHint() класса QWidget переопределяется и возвращает «идеальный» размер виджета. Здесь мы размер изображения умножаем на маштабный коэффициент и в случае, когда масштабный коэффициент равен или больше 3, добавляем еще один пиксель по каждому направлению для размещения сетки. (Мы не показываем сетку при масшабном коэффициенте 1 или 2, поскольку в этом случае едва ли найдется место для пикселей пиктограммы.)

Идеальный размер виджета играет очень заметную роль при размещении виджетов. Менеджеры компоновки Qt стараются максимально учесть идеальный размер виджета при размещении дочерних виджетов. Для того чтобы IconEditor был удобен для менеджера компоновки, он должен сообщить свой идеальный размер.

Кроме идеального размера виджет имеет «политику размера», которая говорит системе компоновки о желательности или нежелательности растяжения или сжатия виджета. Вызывая в конструкторе метод setSizePolicy() со значением QSizePolicy::Minimum в качестве горизонтальной и вертикальной политики, мы указываем менеджеру компоновки, который отвечает за размещение этого виджета, на то, что идеальный размер является фактически его минимальным размером. Другими словами, при необходимости виджет может растягиваться, но он никогда не должен сжиматься до размеров меньших, чем идеальный. Политику размера можно изменять в Qt Designer путем установки свойств виджета sizePolicy. Более подробно о политеке размеров мы поговорим в следующей статье.

Метод setPenColor() устанавливает текущий цвет пера. Этот цвет будет использоваться при выводе на экран новых пикселей.

Метод setIconImage() задает изображение для редактирования. Мы вызываем convertToFormat() для установки 32-битовой глубины изображения с альфа-буфером, если вы этого еще не сделали, то в дальнейшем при открытии изображений будет подразумеваться что изображение хранится в формате ARGB 32.

После установки переменной image мы вызываем метод QWidget::update() для запланированной перерисовки виджета с новым изображением. Затем мы вызываем QWidget::updateGeometry(), чтобы сообщить всем содержащим этот виджет менеджерам компоновки об изменениях идеального размера виджета. Размещение виджета затем будет автоматически адаптировано к его новому идеальному размеру.

Метод setZoomFactor() устанавливает масштабный коэффициент изображения. Для предотвращения деления на нуль мы всякое значение меньше, чем 1. Мы опять вызываем метод update() и updateGeometry() для перерисовки виджета и уведомления всех менеджеров компоновки об изменении идеального размера.

Метод penColor(), iconImage() и zoomFactor() реализуются в заголовочном файле как встроенные методы.

Теперь мы рассмотрим программный код метода paintEvent(). Этот метод играет очень важную роль в классе IconEditor. Используемая по умолчанию её реализация в QWidget ничего не делает, оставляя виджет пустым.

Так же как рассмотренный нами ранее метод closeEvent в приложении электронная таблица. Метод paintEvent() является обработчиком события. В Qt предусмотрено много других обработчиков событий, каждый из которых относится к определенному типу события. Обработку событий мы более подробно рассмотрим в следующих статьях.

Существует множество ситуаций, когда генерируется событие рисование (paint) и вызывается метод paintEvent(). К примеру:

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

Мы можем также принудительно сгенерировать событие рисования путем вызова метода QWidget::update() или QWidget::repaint(). Различие между этими методами следующие: repaint() приводит к немедленной перерисовке, а метод update() просто передает событие рисования в очередь событий, обрабатываемых Qt. (Оба метода ничего не будут делать, если виджет не видим на экране.) Если update() вызывается несколько раз, Qt из нескольких следующих друг за другом событий рисования делает одно событие предотвращения мерцания. В классе IconEditor мы всегда используем метод update().

Ниже приводится программный код метода paintEvent():

Мы начинаем с построения объекта QPainter нашего виджета. Если масштабный коэффициент равен или больше 3, мы вычерчиваем с помощью метода QPainter::drawLine() горизонтальные и вертикальные линии сетки.

Вызов метода QPainter::drawLine() имеет следующий формат:

painter.drawLine(x1, x1, x2, y2);

где (x1, y1) задает положение одного конца линии и (x2, y2) задает положение противоположного(другого) конца линии. Существует перегруженный вариант метода, которая принимает два объекта типа QPoints вместо четырех целых чисел.

Пиксель в верхнем левом углу виджета Qt имеет координаты(0, 0), а пиксель в нижнем правом углу имеет координаты (width() – 1, height() – 1). Это напоминает обычную декартовскую систему координат, но только перевернутую сверху в низ. Чуть ниже на рисунке показан пример системы координат.

Мы можем изменить систему координат в QPainter, трансформируя её такими способами, как смещение, масштабирование, вращение и отсечка. Более подробно об этом мы поговорим в следующих статьях.

Перед вызовом в QPainter метода drawLine() мы устанавливаем цвет линии, используя метод setPen(). Мы могли бы жестко запрограммировать цвет (например, черный или серый), но лучше использовать палитру виджета.

Каждый виджет имеет палитру, которая определяет назначение цветов. Например, предусмотрен цвет фона виджетов (обычно светло-серый) и цвет текста на этом фоне (обычно черный). По умолчанию палитра виджета адаптирована под схему цветов оконной системы. Используя цвета из палитры, мы обеспечим в IconEditor учет пользовательских настроек.

Палитра виджета состоит из трех цветовых групп: активная, неактивная, не рабочая. Цветовая группа выбирается в зависимости от текущего состояния виджета:

  • группа Active используется для виджетов текущего активного окна;
  • группа Innactive используется виджетами других окон;
  • группа Disabled используется отключенными виджетами любого окна.

Метод QWidget::palette() возвращает палитру виджета в виде объекта QPalette::QColorGroup. Удобный метод QWidget::colorGroup() возвращает правильную цветовую группу текущего состояния виджета, и поэтому нам редко придется выбирать цвет непосредственно из палитры.

Когда нам нужно получить соответствующую кисть или цвет для рисования, правильный подход связан с применением текущей палитры, полученной методом QWidget::palette(), и соответствующему ролевому методу, например QPalette::foreground(). Каждый ролевой метод возвращает кисть, что обычно и требуется, однако если нам нужно только цвет, его можно извлечь из кисти, как мы это делали в paintEvent(). По умолчанию возвращаемые кисти соответствуют состоянию виджета, поэтому нам не надо указывать цветовую группу.

Метод paintEvent() завершает рисования изображения. Вызов IconEditor::pixelRect() возвращает QRect, который определяет область перерисовки. Мы не перерисовываем пиксели, которые попадают за пределы данной области, обеспечивая простую оптимизацию. Чуть ниже показан пример на рисунке.

Мы вызываем QPainter::fillRect() для вывода на экран масштабируемого пикселя. QPainter::fillRect() принимает QRect и QBrush. Передавая QColor в качестве кисти, мы обеспечиваем равномерное заполнение области цветом. Если цвет не является полностью непрозрачным (значение альфа-канала меньше 255), то сначала мы рисуем белый фон.

Метод pixelRect() возвращает объект QRect, который может использоваться методом QPainter::fillRect(). Параметры i и j являются координатами пикселя в QImage, а не в виджете. Если коэффициент масштабирования равен 1, обе системы координат будут полностью совпадать.

Конструктор QRect имеет синтаксис QRect(x, y, width, height), где (x, y) являются координатами верхнего левого угла прямоугольника, а width и height являются размерами прямоугольника (шириной и высотой). Если коэффициент масштабирования равен не менее 3, мы уменьшаем размер прямоугольника на один пиксель по горизонтали и по вертикали, чтобы не загораживать линии сетки.

Когда пользователь нажимает кнопку мыши, система генерирует событие «клавиша мыши нажата» (mouse press). Путем переопределения метода QWidget::mousePressEvent() мы можем обработать это событие и установить или стереть пиксель изображения, находящийся под курсором мыши.

Если пользователь нажал левую кнопку мыши, мы вызываем закрытый метод setImagePixel() с true в качестве второго аргумента, указывая на необходимость установки цвета пикселя на текущий цвет пера. Если пользователь нажал правую кнопку мыши, мы также вызываем метод setImagePixel(), но передаем false для стирания пикселя.

Метод mouseMoveEvent() обрабатывает событие «перемещения мыши». По умолчанию эти события генерируются только при нажатой пользователем кнопки мыши. Можно изменить этот режим работы с помощью вызова метода QWidget::setMouseTracking(), но нам не нужно это делать в нашем примере.

Как при нажатии левой или правой кнопки мыши устанавливается или стирается пиксель, так и при удерживании нажатой кнопки над пикселем тоже будет устанавливаться или стираться пиксель. Поскольку допускается удерживание нажатой клавиши мыши или сразу несколько нажатий одновременно, возвращаемое методом QMouseEvent::buttons() для кнопок, значение представляет собой результат логической операции поразрядного ИЛИ для кнопок. Мы проверяем нажатие определенной кнопки при помощи оператора & и при наличии соответствующего состояния вызываем метод setImagePixel().

Метод setImagePixel() вызывается из mousePressEvent() и mouseMoveEvent() для установки или стирания пикселя. Параметр pos определяет положение мыши на виджете.

На первом этапе надо преобразовать положение мыши из системы координат виджета в систему координат изображения. Это достигается путем деления координат положения мыши x() и y() на коэффициент масштабирования. Затем мы проверяем попадания точкив нужную область. Это легко сделать при помощи методов QImage::rect() и QRect::contains(). Фактически здесь проверяется попадание значения переменной i в промежуток между 0 и значением image.height – 1.

В зависимости от параметра opaque мы устанавливаем или стираем пиксель в изображении. Для вызова QImage::setPixel() мы должны преобразовать перо QColor в 32-битовое значение ARGB. В конце мы вызываем метод update() с передачей объекта QRect, задающей область перерисовки.

Теперь когда рассмотрены методы-члены, мы вернемся к использованию в конструкторе атрибута WA_StaticContents. Этот атрибут указывает Qt на то, что содержимое виджета не изменяется при изменении его размеров и что его верхний левый угол остается на прежнем месте. Qt использует эту информацию, чтобы лишний раз не переопределять при изменении размеров виджета уже видимые его области.

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

Теперь виджет IconEditor полностью построен. На основе применения приводимых в предыдущих статья сведений и примеров мы можем написать программу, в которой виджет IconEditor будет являться сам окном, используя в качестве центрального виджета в главном окне QMainWindow, в качестве дочернего виджета менеджера компоновки или в качестве дочернего виджета объекта QScroll Area.

Сергей Будейкин

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *