Суббота, 12 декабря 2020 20:51

Делегат для QComboBox - Работа с моделями в Qt5 для отображения данных в виджетах

Россия
Оцените материал
(0 голосов)

В прошлой статье мы рассмотрели создание модели для QComboBox. Сегодня мы рассмотрим создание Делагата для QComboBox. Что такое Делегаты и для чего они нужны мы рассмотрим в отдельном материале, сегодня же я просто продемонстрирую, как для QComboBox добавить иконки перед каждым пунктом и выровнять надписи по правому краю.

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

Для этого примера я буду использовать код из предыдущей статьи.

Так же вам понадобятся иконки с флагами государств, скачать архив вы можете отсюда – ссылка на Яндекс.Диск - https://yadi.sk/d/_N4v7pySleb07A или взять с Github.

Создание Делегата

Делегаты предназначены для переопределения механизмов, которые отвечают в виджете за отрисовку и редактирование данных.

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

Обратите внимание, что Делегат изменяет только содержимое окна с выпадающим списком, чтобы изменить текущий выбранный пункт QComboBox вам понадобиться вносить изменения в сам виджет!

Создадим класс Делегата - ComboboxItemDelegate

Заголовок:

#ifndef COMBOBOXITEMDELEGATE_H
#define COMBOBOXITEMDELEGATE_H

#include <QItemDelegate>

class ComboboxItemDelegate : public QItemDelegate
{
    Q_OBJECT
public:
    ComboboxItemDelegate();

    QSize sizeHint(const QStyleOptionViewItem &option,
                       const QModelIndex &index) const override;

    void paint(QPainter *painter, const QStyleOptionViewItem &option,
                  const QModelIndex &index) const override;

};

#endif // COMBOBOXITEMDELEGATE_H

 Реализация:

#include "comboboxitemdelegate.h"

#include <QPainter>
#include <QDebug>

ComboboxItemDelegate::ComboboxItemDelegate()
{
}

QSize ComboboxItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    return QItemDelegate::sizeHint(option, index);
}

void ComboboxItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QString data = index.model()->data(index, Qt::DisplayRole).toString();


    QStyleOptionViewItem myOption = option;
            myOption.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;


    QString icon = ":/img/" + index.model()->data(index, Qt::DecorationRole).toString();

    QPixmap pixmap2(icon);

    drawDisplay(painter, myOption, myOption.rect, data);
    drawFocus(painter, myOption, myOption.rect);

   painter->drawPixmap(myOption.rect.x(),myOption.rect.y(),13,13, pixmap2);
}

Метод SizeHint() необходим для того, чтобы строки умещались в размеры виджета.

Метод paint() просто отрисовывает строку QComboBox заданную индексом Index. Опции отрисовки, такие как выравнивание и положение на экране, заданы в параметре option.

Рассмотрим, что происходит внутри метода paint():

Сначала мы получаем значение строки с ролью Qt::DisplayRole;

Затем создаем локальную копию option и изменяем выравнивание текста – справа, по центру;

Получаем строку именем файла иконки для данной строки из модели и записываем в переменную icon путь к этому файлу;

Создаем иконку с помощью QPixmap;

Далее мы используем два метода:

drawDisplay() - отрисовывает строку со значением;

drawFocus() - отрисовывает рамки фокуса;

И, наконец, используем метод класса QPainter – для отрисовки изображения иконки:

painter->drawPixmap(myOption.rect.x(),myOption.rect.y(),13,13, pixmap2)

Обратите внимание myOption уже содержит координаты текущего элемента в поле myOption.rect.

Таким образом, если мы хотим нарисовать иконку в самом начале строки, нам достаточно указать её координаты:

myOption.rect.x(), myOption.rect.y()

Далее указываются размеры иконки:

13,13

Передается сам экземпляр класса нашей иконки.

Обратите внимание - метод paint() вызывается для каждой строки QComboBox!

Создание ресурса с иконками

Далее нам необходимо создать ресурс с файлами иконок, по сути указатель для Qt5 на то, где лежат эти файлы.

Распакуйте архив с иконками в папку img в корень проекта

Самый простой способ - создать файл resource.qrc в его корне.

Запишем в него следующее содержимое:

<RCC>
    <qresource prefix="/">
        <file>img/belarus.png</file>
        <file>img/china.png</file>
        <file>img/ecuador.png</file>
        <file>img/license.pdf</file>
        <file>img/mongolia.png</file>
        <file>img/russia.png</file>
        <file>img/slovakia.png</file>
        <file>img/slovenia.png</file>
        <file>img/japan.png</file>
    </qresource>
</RCC>

Затем нужно средствами QtCreator добавить этот файл к проекту.

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

Теперь всё что нужно – сменить Делегат для виджета, делается это просто:

Добавим в конструктор главного окна строку:

this->ui->comboBox->setItemDelegate(new ComboboxItemDelegate);

Правим класс CountryFlag

Допустим, концепция снова поменялась, и теперь нам не нужно название столицы в каждой строке QComboxBox, но нужны иконки с флагами. Не будем удалять поле с именем столицы из класса CountryFlag оно нам пригодятся в следующих статьях. Добавим еще одно поле для имени файла с иконкой, в результате класс прием вид:

Заголовок:

#ifndef COUNTRYFLAG_H
#define COUNTRYFLAG_H

#include <QString>



class CountryFlag
{
public:
    CountryFlag();
    CountryFlag(int id, QString name, QString capital);

    QString getName() const;
    void setName(const QString &value);

    int getId() const;
    void setId(int value);

    QString getCapital() const;
    void setCapital(const QString &value);

    QString getIcon() const;
    void setIcon(const QString &value);
    
private:
    int id;
    QString name;
    QString capital;
    QString icon;
};

#endif // COUNTRYFLAG_H

Реализация:

#include "countryflag.h"

CountryFlag::CountryFlag()
{

}

CountryFlag::CountryFlag(int id, QString name, QString icon)
{
    this->id = id;
    this->name = name;
    this->icon = icon;
}

QString CountryFlag::getName() const
{
    return name;
}

void CountryFlag::setName(const QString &value)
{
    name = value;
}

int CountryFlag::getId() const
{
    return id;
}

void CountryFlag::setId(int value)
{
    id = value;
}

QString CountryFlag::getCapital() const
{
    return capital;
}

void CountryFlag::setCapital(const QString &value)
{
    capital = value;
}

QString CountryFlag::getIcon() const
{
    return icon;
}

void CountryFlag::setIcon(const QString &value)
{
    icon = value;
}

 Правим модель

Теперь нам нужно внести изменения в метод data() нашей модели, чтобы он возвращал данные роли Decoration для каждого значения поля icon:

QVariant QComboBoxModel::data( const QModelIndex &index, int role ) const
{

    QVariant value;

        switch ( role )
        {
            case Qt::DisplayRole: //string
            {
                value = this->values->at(index.row()).getName() + " - " + this->values->at(index.row()).getCapital();
            }
            break;

            case Qt::DecorationRole: //data            
            {
                value = this->values->at(index.row()).getIcon();
            }
            break;
            
            case Qt::UserRole: //data            
            {
                value = this->values->at(index.row()).getId();
            }
            break;

            default:
                break;
        }

    return value;
}

 Правим главное окно

Внесем изменения в конструктор главного окна:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);


    values = new QList<CountryFlag>;

    values->append(CountryFlag(-1, "Select flag", ""));
    values->append(CountryFlag(1," Russia", "russia.png"));
    values->append(CountryFlag(11, "Belarus", "belarus.png"));
    values->append(CountryFlag(22, "Slovakia", "slovakia.png"));
    values->append(CountryFlag(33, "Slovenia", "slovenia.png"));
    values->append(CountryFlag(44, "China", "china.png"));
    values->append(CountryFlag(55, "Mongolia","mongolia.png"));


    model = new QComboBoxModel();
    model->populate(values);
    this->ui->comboBox->setModel(model);

    model->insertAt(4,CountryFlag(66, "Japan", "japan.png"));

    this->ui->comboBox->setItemDelegate(new ComboboxItemDelegate);

    newidx=0;

}

Соответственно изменим слоты кнопок формы:

void MainWindow::on_pushButton_clicked()
{
    newidx++;
    QString strIdx = QString().number(newidx);

    model->append(CountryFlag(newidx, "new country " + strIdx, "russia.png"));
}

void MainWindow::on_pushButton_2_clicked()
{
    model->update(ui->comboBox->currentIndex(), CountryFlag(66, "Japan", "japan.png"));
}

void MainWindow::on_pushButton_3_clicked()
{
    model->deleteRow(0);
}

void MainWindow::on_pushButton_4_clicked()
{
    model->insertAt(0, CountryFlag(66, "Japan", "japan.png"));
}

Запустим проект:

2020-12-11_15-23-12.png

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

Заключение

Сегодня мы рассмотрели создание простейшего Делегата, для вывода иконок в QComboBox.

Был создан класс для Делегата и назначен новый для QComboBox.

В следующей статье будут подробно рассмотрены Делегаты.

Исходный код проекта вы можете найти на Github.

Прочитано 656 раз Последнее изменение Среда, 13 января 2021 12:14