Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Множественный выбор в QComboBox

Anna | 24.06.2014 | нет комментариев
Изредка, достаточно комфортным бывает вероятность множественного выбора в виджете QComboBox. В этом маленьком туториале будет показано, как это cделать.

Основная идея состоит в том, что элементам модели, применяемой в QComboBox, нужно поднять флажокQt::ItemIsUserCheckable, таким образом сделав их подмечаемыми. А также позаботится о итоге списка отмеченых элементов на виджете.Объявим класс MultiListWidget (качество и соответствующие способы checkedItems дают доступ к списку элементов, которые мы заранее установили либо которые подметил пользователь, а способ collectCheckedItems сберегает подмеченные элементы модели в mCheckedItems):

class MultiListWidget
	: public QComboBox
{
	Q_OBJECT

	Q_PROPERTY(QStringList checkedItems READ checkedItems WRITE setCheckedItems)

public:
	MultiListWidget();
	virtual ~MultiListWidget();

	QStringList checkedItems() const;
	void setCheckedItems(const QStringList &items);

private:
	QStringList mCheckedItems;

	void collectCheckedItems();

};

В модели QComboBox есть несколько необходимых нам сигналов:

  • rowsInserted(const QModelIndex &parent, int start, int end) — при добавлении элементов в модель (вызов способов addItem, insertItem и т.д.)
  • rowsRemoved(const QModelIndex &parent, int start, int end) — при удалении элементов из модели (вызов способа removeItem)

Также сгодится itemChanged(QStandardItem *item), тот, что испускается при установке и снятии флажка (пользователем либо программно).

Объявим слоты для этих сигналов:

private slots:
	void slotModelRowsInserted(const QModelIndex &parent, int start, int end);
	void slotModelRowsRemoved(const QModelIndex &parent, int start, int end);
	void slotModelItemChanged(QStandardItem *item);

И свяжем сигналы со слотами в конструкторе (обратите внимание, что model() возвращает указатель на QAbstractItemModel, а сигнал itemChanged испускается в QStandardItemModel, следственно здесь нужно приведение):

MultiListWidget::MultiListWidget()
{
	connect(model(), SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(slotModelRowsInserted(QModelIndex,int,int)));
	connect(model(), SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(slotModelRowsRemoved(QModelIndex,int,int)));

	QStandardItemModel *standartModel = qobject_cast<QStandardItemModel*>(model());

	connect(standartModel, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotModelItemChanged(QStandardItem*)));
}

MultiListWidget::~MultiListWidget()
{
}

Сейчас, реализуем способы checkedItems() и setCheckedItems(const QStringList &items):

QStringList MultiListWidget::checkedItems() const
{
	return mCheckedItems;
}

void MultiListWidget::setCheckedItems(const QStringList &items)
{
	// нужно приведение
	QStandardItemModel *standartModel = qobject_cast<QStandardItemModel*>(model());

	// отсоединяемся от сигнала, на время установки элементам флажков
	disconnect(standartModel, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotModelItemChanged(QStandardItem*)));

	for (int i = 0; i < items.count();   i)
	{
		// ищем индекс элемента
		int index = findText(items.at(i));

		if (index != -1)
		{
			// устанавливаем элементу флажок
			standartModel->item(index)->setData(Qt::Checked, Qt::CheckStateRole);
		}
	}

	// присоединяемся к сигналу
	disconnect(standartModel, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotModelItemChanged(QStandardItem*)));

	// обновляем список подмеченных элементов
	collectCheckedItems();
}

Внутри способа collectCheckedItems() всё легко — пробегаемся по элементам модели, если он подмечен, добавляем в список:

void MultiListWidget::collectCheckedItems()
{
	QStandardItemModel *standartModel = qobject_cast<QStandardItemModel*>(model());

	mCheckedItems.clear();

	for (int i = 0; i < count();   i)
	{
		QStandardItem *currentItem = standartModel->item(i);

		Qt::CheckState checkState = static_cast<Qt::CheckState>(currentItem->data(Qt::CheckStateRole).toInt());

		if (checkState == Qt::Checked)
		{
			mCheckedItems.push_back(currentItem->text());
		}
	}
}

При вставке нового элемента в модель нам нужно указать, что он будет подмечаемым пользователем и первоначально со снятым флажком:

void MultiListWidget::slotModelRowsInserted(const QModelIndex &parent, int start, int end)
{
	// Дабы компилятор не ругался
	(void)parent;

	QStandardItemModel *standartModel = qobject_cast<QStandardItemModel*>(model());

	disconnect(standartModel, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotModelItemChanged(QStandardItem*)));

	for (int i = start; i <= end;   i)
	{
		standartModel->item(i)->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
		standartModel->item(i)->setData(Qt::Unchecked, Qt::CheckStateRole);
	}

	connect(standartModel, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotModelItemChanged(QStandardItem*)));
}

При удалении элементов из модели, также необходимо удалить их из mCheckedItems. Воспользуемся collectCheckedItems():

void MultiListWidget::slotModelRowsRemoved(const QModelIndex &parent, int start, int end)
{
	(void)parent;
	(void)start;
	(void)end;

	collectCheckedItems();
}

В слоте slotModelItemChanged(QStandardItem *item) собираем подмеченные элементы:

void MultiListWidget::slotModelItemChanged(QStandardItem *item)
{
	(void)item;

	collectCheckedItems();
}

Разместим объявление класса и его реализацию в, соответственно, multilist.h и multilist.cpp, и испробуем MultiListWidget в деле (файл main.cpp):

#include "multilist.h"

int main(int argc, char *argv[])
{
	QApplication app(argc, argv);

	MultiListWidget *multiList = new MultiListWidget();

	multiList->addItems(QStringList() << "One" << "Two" << "Three" << "Four");
	multiList->setCheckedItems(QStringList() << "One" << "Three");

	QHBoxLayout *layout = new QHBoxLayout();

	layout->addWidget(new QLabel("Select items:"));
	layout->addWidget(multiList, 1);

	QWidget widget;

	widget.setWindowTitle("MultiList example");
	widget.setLayout(layout);

	widget.show();

	return app.exec();
}

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

QString mDisplayText;
const QRect mDisplayRectDelta;

void updateDisplayText();

Добавим в конструктор инициализацию mDisplayRectDelta:

MultiListWidget::MultiListWidget()
	: mDisplayRectDelta(4, 1, -25, 0)
{
	...
}

Сейчас, разглядим подробнее updateDisplayText():

void MultiListWidget::updateDisplayText()
{
	// определяем границы выводимого текста, mDisplayRectDelta сдвигает текст "вовнутрь" виджета
	// с учётом того, что справа находится кнопка, раскрывающая список
	QRect textRect = rect().adjusted(mDisplayRectDelta.left(), mDisplayRectDelta.top(),
									 mDisplayRectDelta.right(), mDisplayRectDelta.bottom());

	QFontMetrics fontMetrics(font());

	// разделяем запятыми
	mDisplayText = mCheckedItems.join(", ");

	// если текст вылазит за границы
	if (fontMetrics.size(Qt::TextSingleLine, mDisplayText).width() > textRect.width())
	{
		// обрезаем его посимвольно, пока не будет в пределах границы
		while (mDisplayText != "" && fontMetrics.size(Qt::TextSingleLine, mDisplayText   "...").width() > textRect.width())
		{
			mDisplayText.remove(mDisplayText.length() - 1, 1);
		}

		// дополняем троеточием
		mDisplayText  = "...";
	}
}

Для отрисовки текста нужно переопределить воображаемый способ paintEvent(QPaintEvent *event). Также необходимо переопределить способ resizeEvent(QResizeEvent *event), так как границы текста при изменении размера виджета изменятся. Вот объявление этих способов:

protected:
	virtual void paintEvent(QPaintEvent *event);
	virtual void resizeEvent(QResizeEvent *event);

И их реализация:

void MultiListWidget::paintEvent(QPaintEvent *event)
{
	(void)event;

	QStylePainter painter(this);

	painter.setPen(palette().color(QPalette::Text));

	QStyleOptionComboBox option;

	initStyleOption(&option);

	// рисуем базовую часть виджета
	painter.drawComplexControl(QStyle::CC_ComboBox, option);

	// определяем границы выводимого текста
	QRect textRect = rect().adjusted(mDisplayRectDelta.left(), mDisplayRectDelta.top(),
									 mDisplayRectDelta.right(), mDisplayRectDelta.bottom());

	// рисуем текст
	painter.drawText(textRect, Qt::AlignVCenter, mDisplayText);
}

void MultiListWidget::resizeEvent(QResizeEvent *event)
{
	(void)event;

	updateDisplayText();
}

Осталось только позже обновлять выводимый текст позже метаморфозы списка элементов модели. Добавим в конец collectCheckedItems() вызов updateDisplayText() и перерисуем виджет:

void MultiListWidget::setCheckedItems(const QStringList &items)
{
	...
	updateDisplayText();
	repaint();
}

В жанрах GTK и Mac есть баг, при котором не отображаются флажки в развёрнутом списке. Для временного решения этого бага необходимо установить значения combobox-popup в styleSheet виджета (разместите данный код в конструктор):

setStyleSheet("QComboBox { combobox-popup: 1px }");

Изображения:

Начальный код:

multilist.h

#ifndef MULTILIST_H
#define MULTILIST_H

#include <QtGui>

class MultiListWidget
	: public QComboBox
{
	Q_OBJECT

	Q_PROPERTY(QStringList checkedItems READ checkedItems WRITE setCheckedItems)

public:
	MultiListWidget();
	virtual ~MultiListWidget();

	QStringList checkedItems() const;
	void setCheckedItems(const QStringList &items);

protected:
	virtual void paintEvent(QPaintEvent *event);
	virtual void resizeEvent(QResizeEvent *event);

private:
	QStringList mCheckedItems;

	void collectCheckedItems();

	QString mDisplayText;
	const QRect mDisplayRectDelta;

	void updateDisplayText();

private slots:
	void slotModelRowsInserted(const QModelIndex &parent, int start, int end);
	void slotModelRowsRemoved(const QModelIndex &parent, int start, int end);
	void slotModelItemChanged(QStandardItem *item);

};

#endif // MULTILIST_H

multilist.cpp

#include "multilist.h"

MultiListWidget::MultiListWidget()
	: mDisplayRectDelta(4, 1, -25, 0)
{
	setStyleSheet("QComboBox { combobox-popup: 1px }");

	connect(model(), SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(slotModelRowsInserted(QModelIndex,int,int)));
	connect(model(), SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(slotModelRowsRemoved(QModelIndex,int,int)));

	QStandardItemModel *standartModel = qobject_cast<QStandardItemModel*>(model());

	connect(standartModel, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotModelItemChanged(QStandardItem*)));
}

MultiListWidget::~MultiListWidget()
{
}

QStringList MultiListWidget::checkedItems() const
{
	return mCheckedItems;
}

void MultiListWidget::setCheckedItems(const QStringList &items)
{
	QStandardItemModel *standartModel = qobject_cast<QStandardItemModel*>(model());

	disconnect(standartModel, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotModelItemChanged(QStandardItem*)));

	for (int i = 0; i < items.count();   i)
	{
		int index = findText(items.at(i));

		if (index != -1)
		{
			standartModel->item(index)->setData(Qt::Checked, Qt::CheckStateRole);
		}
	}

	connect(standartModel, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotModelItemChanged(QStandardItem*)));

	collectCheckedItems();
}

void MultiListWidget::paintEvent(QPaintEvent *event)
{
	(void)event;

	QStylePainter painter(this);

	painter.setPen(palette().color(QPalette::Text));

	QStyleOptionComboBox option;

	initStyleOption(&option);

	painter.drawComplexControl(QStyle::CC_ComboBox, option);

	QRect textRect = rect().adjusted(mDisplayRectDelta.left(), mDisplayRectDelta.top(),
									 mDisplayRectDelta.right(), mDisplayRectDelta.bottom());

	painter.drawText(textRect, Qt::AlignVCenter, mDisplayText);
}

void MultiListWidget::resizeEvent(QResizeEvent *event)
{
	(void)event;

	updateDisplayText();
}

void MultiListWidget::collectCheckedItems()
{
	QStandardItemModel *standartModel = qobject_cast<QStandardItemModel*>(model());

	mCheckedItems.clear();

	for (int i = 0; i < count();   i)
	{
		QStandardItem *currentItem = standartModel->item(i);

		Qt::CheckState checkState = static_cast<Qt::CheckState>(currentItem->data(Qt::CheckStateRole).toInt());

		if (checkState == Qt::Checked)
		{
			mCheckedItems.push_back(currentItem->text());
		}
	}

	updateDisplayText();

	repaint();
}

void MultiListWidget::updateDisplayText()
{
	QRect textRect = rect().adjusted(mDisplayRectDelta.left(), mDisplayRectDelta.top(),
									 mDisplayRectDelta.right(), mDisplayRectDelta.bottom());

	QFontMetrics fontMetrics(font());

	mDisplayText = mCheckedItems.join(", ");

	if (fontMetrics.size(Qt::TextSingleLine, mDisplayText).width() > textRect.width())
	{
		while (mDisplayText != "" && fontMetrics.size(Qt::TextSingleLine, mDisplayText   "...").width() > textRect.width())
		{
			mDisplayText.remove(mDisplayText.length() - 1, 1);
		}

		mDisplayText  = "...";
	}
}

void MultiListWidget::slotModelRowsInserted(const QModelIndex &parent, int start, int end)
{
	(void)parent;

	QStandardItemModel *standartModel = qobject_cast<QStandardItemModel*>(model());

	disconnect(standartModel, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotModelItemChanged(QStandardItem*)));

	for (int i = start; i <= end;   i)
	{
		standartModel->item(i)->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
		standartModel->item(i)->setData(Qt::Unchecked, Qt::CheckStateRole);
	}

	connect(standartModel, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotModelItemChanged(QStandardItem*)));
}

void MultiListWidget::slotModelRowsRemoved(const QModelIndex &parent, int start, int end)
{
	(void)parent;
	(void)start;
	(void)end;

	collectCheckedItems();
}

void MultiListWidget::slotModelItemChanged(QStandardItem *item)
{
	(void)item;

	collectCheckedItems();
}

main.cpp

#include "multilist.h"

int main(int argc, char *argv[])
{
	QApplication app(argc, argv);

	MultiListWidget *multiList = new MultiListWidget();

	multiList->addItems(QStringList() << "One" << "Two" << "Three" << "Four");
	multiList->setCheckedItems(QStringList() << "One" << "Three");

	QHBoxLayout *layout = new QHBoxLayout();

	layout->addWidget(new QLabel("Select items:"));
	layout->addWidget(multiList, 1);

	QWidget widget;

	widget.setWindowTitle("MultiList example");
	widget.setLayout(layout);

	widget.show();

	return app.exec();
}

Спасибо за внимание!

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB