Пример "Icons"
|
Режим | Описание |
---|---|
QIcon::Normal | Выводит на экран растровое изображение, когда пользователь не взаимодействует с пиктограммой, но доступна функциональность, предоставляемая пиктограммой. |
QIcon::Active | Выводимое на экран растровое изображение когда функциональность предоставляемая пиктограммой доступна и пользователь взаимдействует с пиктограммой, например, перемещает мышь поверх неё или щелкает по ней. |
QIcon::Disabled | Выводимое на экран растровое изображение, когда функциональность, предоставляемая пиктограммой, не доступна. |
QIcon::Selected | Выводимое на экран растровое изображение когда пиктограмма выделена. |
Состояния QIcon'а - QIcon::On и QIcon::Off, которые будут выводить растровое изображение, когда виджет находится в соответствующем состоянии. Наиболее общее применение состояний QIcon'а - когда отображаются инструментальные кнопки или пункты меню с отметкой (смотрите QAbstractButton::setCheckable() и QAction::setCheckable()). Когда инструментальная кнопка или пункт меню отмечены, состояние QIcon'а - On, в противном случае - Off. Вы можете использовать состояния QIcon'а, например, для вывода на экран разных растровых изображений в зависимости от того, отмечена инструментальная кнопка или пункт меню, или же не отмечена.
QIcon может генерировать уменьшенные, большие, активные, отключённые и выбранные растровые изображения из заданного набора растровых изображений. Такие растровые изображения используются виджетами Qt для показа пиктограммы, отражающей индивидуального действия.
С помощью приложения Icons вы получите обзор генерированных из пиктограммы растровых изображений, отражающих различные состояния, режимы и размер.
Когда изображение загружается в приложение, оно конвертируется в растровое и становится частью набора растровых изображений, доступных для пиктограмм. Изображение может быть исключено из этого набора снятием отметки у соответствующего флажка. Приложение предоставляет подкаталог, содержащий набор изображений специально спроектированных для иллюстрации того, как Qt отрисовывает пиктограмму в разных режимах и состояниях.
Приложение позволяет вам манипулировать размером пиктограммы с помощью нескольких предопределённых размеров и ползунка. Предопределённые размеры зависят от стиля, но большинство стилей обладают одинаковыми значениями: Только стиль Macintosh отличается использованием 32 пикселей, вместо 16 пикселей, для кнопок панелей инструментов. Используя меню View можно передвигаться между стилями.
Меню View также предоставляет опцию для того, чтобы приложение могло угадать состояние и режим пиктограммы из имени файла изображения. Меню File предоставляет опции добавления изображения и удаления всех изображений. Последние опции доступны также посредством контекстного меню, которое появляется если вы нажмете правую кнопку мыши внутри таблицы файлов изображений. Кроме того, меню File предоставляет опцию Exit, а меню Help предоставляет информацию о примере и о Qt.
На снимке экрана выше показано приложение с одним загруженным файлом изображения. Включён Guess Image Mode/State и установлен стиль Plastique.
Когда QIcon предоставлен только с одним доступным изображением, то это изображение используется для всех состояний и режимов. В данном случае установлен обычный режим пиктограммы растрового изображения, сгенерированные для нормального и активного режима растровые изображения будут выглядеть аналогично. Но в режиме отключения и выделения, Qt сгенерирует немного другое растровое изображение.
Следующий снимок экрана показывает приложение с загруженным дополнительным файлов, предоставившим QIcon с двумя доступными изображениями. Обратите внимание на то, что режим файла нового изображения установлен отключенным. При отрисовке изображений режима Disabled, Qt будет использовать новое изображение. Можно заметить отличие: Сгенерированное изображение на первом снимке экрана слегка темнее, чем растровое изображение с установленным первоначально режимом отключения на втором снимке экрана.
При отрисовке растровых изображений пиктограмм Qt проводит поиск набора доступных растровых изображений по особому алгоритму. Алгоритм приведён в документации по QIcon, но ниже мы опишем некоторые особые случаи.
На приведённом снимке экрана мы установили monkey_on_32x32 в качестве растрового изображения состояния Active/On и monkey_off_64x64 - для состояния Normal/Off. Чтобы отрисовать остальные шесть сочетаний режим/состояние, QIcon использует поисковый алгоритм описанный в таблице ниже:
Запрашиваемое растровое изображение | Предпочитаемые альтернативы (режим/состояние) | ||||||||
---|---|---|---|---|---|---|---|---|---|
Режим | Состояние | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
Normal | Off | N0 | A0 | N1 | A1 | D0 | S0 | D1 | S1 |
On | N1 | A1 | N0 | A0 | D1 | S1 | D0 | S0 | |
Активная | Off | A0 | N0 | A1 | N1 | D0 | S0 | D1 | S1 |
On | A1 | N1 | A0 | N0 | D1 | S1 | D0 | S0 | |
Disabled | Off | D0 | N0' | A0' | D1 | N1' | A1' | S0' | S1' |
On | D1 | N1' | A1' | D0 | N0' | A0' | S1' | S0' | |
Selected | Off | S0 | N0'' | A0'' | S1 | N1'' | A1'' | D0'' | D1'' |
On | S1 | N1'' | A1'' | S0 | N0'' | A0'' | D1'' | D0'' |
В таблице, "0" и "1" соответствуют Off" и "On". Одиночные кавычки указывают на то, что QIcon генерирует отключенную ("grayed out") версию растрового изображения; аналогично, двойные кавычки указывают на то, что QIcon генерирует выбранную ("blued out") версию растрового изображения.
Используемые в вышеприведённом снимке экрана альтернативы выделены полужирным. Например, растровое изображение Disabled/Off получено затенением растрового изображения Normal/Off (monkey_off_64x64).
На следующих снимках экрана мы загружаем полный набор изображений обезъянки. Отмечая или снимая отметку имён файлов в списке изображений, получаем разные результаты:
![]() | ![]() |
Для любого данного сочетания режим/состояние можно задать несколько изображений с разным разрешением. При отрисовке пиктограммы QIcon автоматически выбирает наиболее подходящее изображение и, если необходимо, уменьшает его масштаб. (QIcon никогда не увеличивает масштаб изображений, поскольку результат редко хорошо выглядит.)
На снимках экрана ниже показано, что произойдёте когда мы предоставим QIcon три изображения (qt_extended_16x16.png, qt_extended_32x32.png, qt_extended_48x48.png) и попытаемся отрисовать QIcon при разных разрешениях:
![]() | ![]() | ![]() | |
8 x 8 | 16 x 16 | 17 x 17 | |
![]() | ![]() | ![]() | ![]() |
32 x 32 | 33 x 33 | 48 x 48 | 64 x 64 |
Для размеров менее 16 x 16 QIcon использует qt_extended_16x16.png и, если необходимо, уменьшает масштаб. Для размеров от 17 x 17 до 32 x 32, используется qt_extended_32x32.png. Для размеров свыше 32 x 32, используется qt_extended_48x48.png.
Пример "Icons" состоит из четырёх классов:
Начнём с обзора класса IconPreviewArea перед изучением класса MainWindow. В заключение, рассмотрим классы IconSizeSpinBox и ImageDelegate.
Виджет IconPreviewArea состоит из групповой рамки, содержащей сетку виджетов QLabel, выводящих заголовки и растровые изображения.
class IconPreviewArea : public QWidget { Q_OBJECT public: IconPreviewArea(QWidget *parent = 0); void setIcon(const QIcon &icon); void setSize(const QSize &size); private: QLabel *createHeaderLabel(const QString &text); QLabel *createPixmapLabel(); void updatePixmapLabels(); enum { NumModes = 4, NumStates = 2 }; QIcon icon; QSize size; QLabel *stateLabels[NumStates]; QLabel *modeLabels[NumModes]; QLabel *pixmapLabels[NumModes][NumStates]; };
Класс IconPreviewArea унаследован от QWidget. Он выводит на экран сгенерированные растровые изображения соответствующие возможным для данного размера состояниям и режимам пиктограммы.
Нам нужны две открытых функции для установки текущей пиктограммы и размера пиктограммы. Кроме того класс имеет три закрытых функции: Мы используем функции createHeaderLabel() и createPixmapLabel() при создании области просмотра, а также нам нужна функция updatePixmapLabels() для обновления области просмотра когда изменяется пиктограмма или размер пиктограммы.
Константы NumModes и NumStates отражают количество определённых в настоящее время режимов и состояний QIcon'а.
IconPreviewArea::IconPreviewArea(QWidget *parent) : QWidget(parent) { QGridLayout *mainLayout = new QGridLayout; setLayout(mainLayout); stateLabels[0] = createHeaderLabel(tr("Off")); stateLabels[1] = createHeaderLabel(tr("On")); Q_ASSERT(NumStates == 2); modeLabels[0] = createHeaderLabel(tr("Normal")); modeLabels[1] = createHeaderLabel(tr("Active")); modeLabels[2] = createHeaderLabel(tr("Disabled")); modeLabels[3] = createHeaderLabel(tr("Selected")); Q_ASSERT(NumModes == 4); for (int j = 0; j < NumStates; ++j) mainLayout->addWidget(stateLabels[j], j + 1, 0); for (int i = 0; i < NumModes; ++i) { mainLayout->addWidget(modeLabels[i], 0, i + 1); for (int j = 0; j < NumStates; ++j) { pixmapLabels[i][j] = createPixmapLabel(); mainLayout->addWidget(pixmapLabels[i][j], j + 1, i + 1); } } }
В конструкторе мы создаём метки, отображающие заголовки и сгенерированные растровые изображения пиктограммы, и добавляем их в компоновку-сетку.
При создании меток заговков, мы убеждаемся, что перечисления NumModes и NumStates определённые в файле .h, соответствующие количеству создаваемых меток. Затем, если перечисления в некоторых местах изменились, макрос Q_ASSERT() предупредит о том, что эту часть файла .cpp нужно обновить.
Если приложение собирается в режиме отладки, макрос Q_ASSERT() будет расширен до
if (!condition) qFatal("ASSERT: "condition" in file ...");
В режиме релиза, макрос просто перестает существовать. Режим можно устнановить в файле .pro приложения. Одним из путей как это сделать - добавить опцию в qmake при сборке приложения:
qmake "CONFIG += debug" icons.pro
или
qmake "CONFIG += release" icons.pro
Другой подход заключается в добавлении этой строки непосредственно в файл .pro.
void IconPreviewArea::setIcon(const QIcon &icon) { this->icon = icon; updatePixmapLabels(); } void IconPreviewArea::setSize(const QSize &size) { if (size != this->size) { this->size = size; updatePixmapLabels(); } }
Открытые функции setIcon() и setSize() изменяют пиктограмму или её размер, а также гарантируют, что сгенерированные растровые изображения обновлены.
QLabel *IconPreviewArea::createHeaderLabel(const QString &text) { QLabel *label = new QLabel(tr("<b>%1</b>").arg(text)); label->setAlignment(Qt::AlignCenter); return label; } QLabel *IconPreviewArea::createPixmapLabel() { QLabel *label = new QLabel; label->setEnabled(false); label->setAlignment(Qt::AlignCenter); label->setFrameShape(QFrame::Box); label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); label->setBackgroundRole(QPalette::Base); label->setAutoFillBackground(true); label->setMinimumSize(132, 132); return label; }
Мы используем функции createHeaderLabel() и createPixmapLabel() для создания меток области предварительного просмотра, отображающих заголовки и сгенерированные растровые изображения пиктограмм. Обе функции возвращают созданную метку QLabel.
void IconPreviewArea::updatePixmapLabels() { for (int i = 0; i < NumModes; ++i) { QIcon::Mode mode; if (i == 0) { mode = QIcon::Normal; } else if (i == 1) { mode = QIcon::Active; } else if (i == 2) { mode = QIcon::Disabled; } else { mode = QIcon::Selected; } for (int j = 0; j < NumStates; ++j) { QIcon::State state = (j == 0) ? QIcon::Off : QIcon::On; QPixmap pixmap = icon.pixmap(size, mode, state); pixmapLabels[i][j]->setPixmap(pixmap); pixmapLabels[i][j]->setEnabled(!pixmap.isNull()); } } }
Мы использовали закрытую функцию updatePixmapLabel() для обновления сгенерированных растровых изображений, выводимых на экран в области предварительного просмотра.
Для каждого режима и для каждого состояния мы находим растровое изображение используя функцию QIcon::pixmap(), которая генерирует растровое изображение, соответствующее заданным состоянию, режиму и размеру.
Виджет MainWindow состоит из трёх главных элементов: групповой рамки вокруг изображений, групповой рамки размера пиктограммы и области предварительного просмотра.
class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(); private slots: void about(); void changeStyle(bool checked); void changeSize(bool checked = true); void changeIcon(); void addImages(); void removeAllImages(); private: void createPreviewGroupBox(); void createImagesGroupBox(); void createIconSizeGroupBox(); void createActions(); void createMenus(); void createContextMenu(); void checkCurrentStyle(); QWidget *centralWidget; QGroupBox *previewGroupBox; IconPreviewArea *previewArea; QGroupBox *imagesGroupBox; QTableWidget *imagesTable; QGroupBox *iconSizeGroupBox; QRadioButton *smallRadioButton; QRadioButton *largeRadioButton; QRadioButton *toolBarRadioButton; QRadioButton *listViewRadioButton; QRadioButton *iconViewRadioButton; QRadioButton *tabBarRadioButton; QRadioButton *otherRadioButton; IconSizeSpinBox *otherSpinBox; QMenu *fileMenu; QMenu *viewMenu; QMenu *helpMenu; QAction *addImagesAct; QAction *removeAllImagesAct; QAction *exitAct; QAction *guessModeStateAct; QActionGroup *styleActionGroup; QAction *aboutAct; QAction *aboutQtAct; };
Класс MainWindow унаследован от QMainWindow. Мы переопределили конструктор и объявили несколько закрытых слотов:
Кроме того мы объявляем несколько закрытых функций для упрощения конструктора.
MainWindow::MainWindow() { centralWidget = new QWidget; setCentralWidget(centralWidget); createPreviewGroupBox(); createImagesGroupBox(); createIconSizeGroupBox(); createActions(); createMenus(); createContextMenu(); QGridLayout *mainLayout = new QGridLayout; mainLayout->addWidget(previewGroupBox, 0, 0, 1, 2); mainLayout->addWidget(imagesGroupBox, 1, 0); mainLayout->addWidget(iconSizeGroupBox, 1, 1); centralWidget->setLayout(mainLayout); setWindowTitle(tr("Icons")); checkCurrentStyle(); otherRadioButton->click(); resize(minimumSizeHint()); }
В конструкторе сначала создаём центральный виджет главного окна и его дочерние виджеты, и помещаем их в компоновку-сетку. Затем создаём меню с ассоциированными с ними пунктами и действиями.
Перед изменением размера окна приложения до подходящего размера, мы устанавливаем заголовок окна и текущий стиль приложения. Также включаем счётчик размера пиктограммы, щёлкнув по связанной с ним радиокнопке, сделав текущее значение счётчика исходным размером пиктограммы.
void MainWindow::about() { QMessageBox::about(this, tr("About Icons"), tr("The <b>Icons</b> example illustrates how Qt renders an icon in " "different modes (active, normal, disabled, and selected) and " "states (on and off) based on a set of images.")); }
Слот about() выводит на экран окно с сообщением используя статическую функцию QMessageBox::about(). В этом примере она выводит на экран простое окно с информацией о примере.
Функция about() пытается найти подходящую пиктограмму в четырёх местах: Предпочтительнее родительская пиктограмма, если она имеется. Если нет, то функция пытается найти виджет верхнего уровня, содержащий родителя, и если неудачно, она пытается найти активное окно. В крайнем случае она использует пиктограмму информации из QMessageBox'а.
void MainWindow::changeStyle(bool checked) { if (!checked) return; QAction *action = qobject_cast<QAction *>(sender());
В слоте changeStyle() сначала мы проверяем параметр слота. Если он равен false, то мы немедленно возвращаем управление, в противном случае узнаём, на какой стиль нужно изменять, т.е. какое действие переключает слот, используя функцию QObject::sender().
Эта функция возвращает отправителя в виде указателя на QObject. Поскольку на известно, что отправителем является объект QAction, то мы можем безопасно привести тип к QObject. Можно использовать C-стиль приведения типа или же функцию static_cast() C++, но в качестве защитного программного приёма используем qobject_cast(). Преимуществом является то, что если объект имеет неверный тип, возвращается нулевой указатель. Крах из-за нулевых указателей значительно легче выявлять, чем падения от небезопасного приведения типов.
QStyle *style = QStyleFactory::create(action->data().toString()); Q_ASSERT(style); QApplication::setStyle(style); smallRadioButton->setText(tr("Small (%1 x %1)") .arg(style->pixelMetric(QStyle::PM_SmallIconSize))); largeRadioButton->setText(tr("Large (%1 x %1)") .arg(style->pixelMetric(QStyle::PM_LargeIconSize))); toolBarRadioButton->setText(tr("Toolbars (%1 x %1)") .arg(style->pixelMetric(QStyle::PM_ToolBarIconSize))); listViewRadioButton->setText(tr("List views (%1 x %1)") .arg(style->pixelMetric(QStyle::PM_ListViewIconSize))); iconViewRadioButton->setText(tr("Icon views (%1 x %1)") .arg(style->pixelMetric(QStyle::PM_IconViewIconSize))); tabBarRadioButton->setText(tr("Tab bars (%1 x %1)") .arg(style->pixelMetric(QStyle::PM_TabBarIconSize))); changeSize(); }
Поскольку у нас имеется действие, то извлечём имя стиля используя QAction::data(). Затем создадим объект QStyle используя статическую функцию QStyleFactory::create().
Кроме того, мы можем предположить, что стиль supported by the QStyleFactory: На всякий случай мы используем макрос Q_ASSERT() для проверки, чтобы созданный стиль являлся корректным до использования функции QApplication::setStyle() для установки нового стиля ГПИ приложения. QApplication автоматически удалит объект стиля когда установлен новый стиль или когда приложение завершает свою работу.
Предустановленные опции размера пиктограммы, предусмотренные в приложении, зависят от стиля, поэтому необходимо обновить метки в групповой рамке размера пиктограммы и в завершение вызывать слот changeSize() для обновления размера пиктограммы.
void MainWindow::changeSize(bool checked) { if (!checked) return; int extent; if (otherRadioButton->isChecked()) { extent = otherSpinBox->value(); } else { QStyle::PixelMetric metric; if (smallRadioButton->isChecked()) { metric = QStyle::PM_SmallIconSize; } else if (largeRadioButton->isChecked()) { metric = QStyle::PM_LargeIconSize; } else if (toolBarRadioButton->isChecked()) { metric = QStyle::PM_ToolBarIconSize; } else if (listViewRadioButton->isChecked()) { metric = QStyle::PM_ListViewIconSize; } else if (iconViewRadioButton->isChecked()) { metric = QStyle::PM_IconViewIconSize; } else { metric = QStyle::PM_TabBarIconSize; } extent = QApplication::style()->pixelMetric(metric); } previewArea->setSize(QSize(extent, extent)); otherSpinBox->setEnabled(otherRadioButton->isChecked()); }
Слот changeSize() устанавливает размер области предварительного просмотра пиктограммы.
Чтобы определить новый размер мы сначала проверяем включён ли счётчик. Если включён, то извлекаем величину нового размера из рамки. Если нет, то ищем среди предустановленных опций размера, извлекаем QStyle::PixelMetric и используем функцию QStyle::pixelMetric() для определения величины. Затем создаём объект QSize на основе размера, и используем этот объект для установки размера области предварительного просмотра пиктограммы.
void MainWindow::addImages() { QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open Images"), "", tr("Images (*.png *.xpm *.jpg);;" "All Files (*)")); if (!fileNames.isEmpty()) { foreach (QString fileName, fileNames) { int row = imagesTable->rowCount(); imagesTable->setRowCount(row + 1);
При вызове слота addImage() первым делом мы показываем пользователю диалог выбора файла. Самый лёгкий способ создать диалог выбора файла - использовать статическую фукнцию QFileDialog'а. Здесь мы используем функцию getOpenFileNames(), которая возвращает один или более существующих файлов, выбранных пользователем.
Для каждого файла, возвращаемого диалогом выбора файла, мы добавляем строку в виджет таблицы. Виджет таблицы выводит список изображений, которые загрузил в приложение пользователь.
QString imageName = QFileInfo(fileName).baseName(); QTableWidgetItem *item0 = new QTableWidgetItem(imageName); item0->setData(Qt::UserRole, fileName); item0->setFlags(item0->flags() & ~Qt::ItemIsEditable);
Находим имя изображения используя функцию QFileInfo::baseName(), которая возвращает базовое имя файла без пути, и создаём в строке первый элемент виджета таблицы. Затем добавляем полное имя файла в данные элемента. Поскольку элемент можно сохранить несколькими информационными фрагментами, нам нужно назначить имени файла роль, которая будет отличать его от других данных. Эта роль может быть Qt::UserRole или любым значением.
Также мы убеждаемся, что элемент не будет редактироваться, убрав флаг Qt::ItemIsEditable. По умолчанию элементы таблицы редактируются.
QTableWidgetItem *item1 = new QTableWidgetItem(tr("Normal")); QTableWidgetItem *item2 = new QTableWidgetItem(tr("Off")); if (guessModeStateAct->isChecked()) { if (fileName.contains("_act")) { item1->setText(tr("Active")); } else if (fileName.contains("_dis")) { item1->setText(tr("Disabled")); } else if (fileName.contains("_sel")) { item1->setText(tr("Selected")); } if (fileName.contains("_on")) item2->setText(tr("On")); }
Затем создаём второй и третий элементы в строке, сделав режимом по умолчанию Normal и состоянием по умолчанию Off. Но если выбрана опция Guess Image Mode/State, а имя файла содержит "_act", "_dis" или "_sel", то режимы изменятся на Active, Disabled или Selected. А если имя файла содержит "_on", то состояние изменится на On. Файлы примера в подкаталоге images соблюдают это соглашение об именовании.
imagesTable->setItem(row, 0, item0); imagesTable->setItem(row, 1, item1); imagesTable->setItem(row, 2, item2); imagesTable->openPersistentEditor(item1); imagesTable->openPersistentEditor(item2); item0->setCheckState(Qt::Checked); } } }
В заключение добавляем элементы в соответствующую строку, и используем функцию QTableWidget::openPersistentEditor() для создания выпадающих списком для столбцов режима и состояния элементов.
Due to the the connection between the table widget's itemChanged() signal and the changeIcon() slot, the new image is automatically converted into a pixmap and made part of the set of pixmaps available to the icon in the preview area. Таким образом, соответствуя этому факту, нам нужно убедиться, что флажок нового изображения отмечен.
void MainWindow::changeIcon() { QIcon icon; for (int row = 0; row < imagesTable->rowCount(); ++row) { QTableWidgetItem *item0 = imagesTable->item(row, 0); QTableWidgetItem *item1 = imagesTable->item(row, 1); QTableWidgetItem *item2 = imagesTable->item(row, 2); if (item0->checkState() == Qt::Checked) { QIcon::Mode mode; if (item1->text() == tr("Normal")) { mode = QIcon::Normal; } else if (item1->text() == tr("Active")) { mode = QIcon::Active; } else if (item1->text() == tr("Disabled")) { mode = QIcon::Disabled; } else { mode = QIcon::Selected; } QIcon::State state; if (item2->text() == tr("On")) { state = QIcon::On; } else { state = QIcon::Off; }
Слот changeIcon() вызывается, когда пользователь изменяет набор изображений, перечисленных в QTableWidget, для обновления объекта QIcon, отрисовываемого IconPreviewArea.
Сначала создаём объект QIcon object, а затем проходим по QTableWidget, который выводит в виде списка изображения, загруженные в приложение пользователем.
QString fileName = item0->data(Qt::UserRole).toString(); QImage image(fileName); if (!image.isNull()) icon.addPixmap(QPixmap::fromImage(image), mode, state); } }
Также извлекаем имя файла изображения, используя функцию QTableWidgetItem::data(). Эта функция принимает в качестве аргумента Qt::DataItemRole, чтобы найти верные данные (припомним, что элемент может хранить несколько фрагментов информации) и возвращает их в виде QVariant. Затем используем функцию QVariant::toString() чтобы получить имя файла в виде QString.
Для создания растрового изображения из файла нам нужно сначала создать изображение, а затем преобразовать это изображение в растровое используя QPixmap::fromImage(). Поскольку у нас имеется итоговое растровое изображение, добавим его, со связанными с ним режимом и состоянием, к набору доступных растровых изображений QIcon'а.
previewArea->setIcon(icon); }
После прохода по всему списку изображений изменяем пиктограмму области предварительного просмотра на ту, что только что создали.
void MainWindow::removeAllImages() { imagesTable->setRowCount(0); changeIcon(); }
В слоте removeAllImages() мы просто устанавливаем счётчик строк виджета таблицы равным нулю, автоматически удаляя все изображения, загруженные пользователем в приложение. Затем обновляем набор растровых изображений, доступных для пиктограммы области предварительного просмотра используя слот changeIcon().
Функция createImagesGroupBox() реализована для упрощения конструктора. Главная цель этой функции - создать QTableWidget, который будет отслеживать изображения, загруженные пользователем в приложение.
void MainWindow::createImagesGroupBox() { imagesGroupBox = new QGroupBox(tr("Images")); imagesTable = new QTableWidget; imagesTable->setSelectionMode(QAbstractItemView::NoSelection); imagesTable->setItemDelegate(new ImageDelegate(this));
Сначала создаём групповую рамку, которая будет содержать виджет таблицы. Затем создаём QTableWidget и настраиваем его для удовлетворения наших целей.
Вызываем QAbstractItemView::setSelectionMode() чтобы предотвращения выбора пользователем элементов.
Вызов QAbstractItemView::setItemDelegate() устанавливает делегат элемента для виджета таблицы. Создаём ImageDelegate, который сделаем делегатом элемента для нашего представления.
Класс QItemDelegate может быть использован для предоставления редактора для класса представления элементов, который является подклассом QAbstractItemView. Использование делегата для этой цели позволяет настраивать и разрабатывать механизм редактирования независимо от модели и представления.
В данном примере мы унаследовали ImageDelegate от QItemDelegate. QItemDelegate обычно предоставляет однострочные редакторы, в то время, как наш подкласс ImageDelegate предоставляет выпадающие списки для полей режима и состояния.
QStringList labels; labels << tr("Image") << tr("Mode") << tr("State"); imagesTable->horizontalHeader()->setDefaultSectionSize(90); imagesTable->setColumnCount(3); imagesTable->setHorizontalHeaderLabels(labels); imagesTable->horizontalHeader()->setResizeMode(0, QHeaderView::Stretch); imagesTable->horizontalHeader()->setResizeMode(1, QHeaderView::Fixed); imagesTable->horizontalHeader()->setResizeMode(2, QHeaderView::Fixed); imagesTable->verticalHeader()->hide();
Затем настраиваем горизонтальный заголовок QTableWidget'а и скрываем вертикальный заголовок.
connect(imagesTable, SIGNAL(itemChanged(QTableWidgetItem *)), this, SLOT(changeIcon())); QVBoxLayout *layout = new QVBoxLayout; layout->addWidget(imagesTable); imagesGroupBox->setLayout(layout); }
В заключении, мы соединяем сигнал QTableWidget::itemChanged() со слотом changeIcon(), чтобы гарантировать, что область предварительного просмотра синхронизирована с таблицей изображений.
Функция createIconSizeGroupBox() вызывается из конструктора. Она создаёт виджеты, управляющие размерами пиктограммы области предварительного просмотра.
void MainWindow::createIconSizeGroupBox() { iconSizeGroupBox = new QGroupBox(tr("Icon Size")); smallRadioButton = new QRadioButton; largeRadioButton = new QRadioButton; toolBarRadioButton = new QRadioButton; listViewRadioButton = new QRadioButton; iconViewRadioButton = new QRadioButton; tabBarRadioButton = new QRadioButton; otherRadioButton = new QRadioButton(tr("Other:")); otherSpinBox = new IconSizeSpinBox; otherSpinBox->setRange(8, 128); otherSpinBox->setValue(64);
Сначала мы создаём групповую рамку, которая будет содержать все виджеты; затем создаём радиокнопки и счётчик.
Счётчик - не стандартный QSpinBox, а IconSizeSpinBox. Класс IconSizeSpinBox унаследован от QSpinBox и переопределяет две функции: QSpinBox::textFromValue() и QSpinBox::valueFromText(). IconSizeSpinBox спроектирован для обработки размеров пиктограммы, например, "32 x 32", вместо простых целочисленных значений.
connect(smallRadioButton, SIGNAL(toggled(bool)), this, SLOT(changeSize(bool))); connect(largeRadioButton, SIGNAL(toggled(bool)), this, SLOT(changeSize(bool))); connect(toolBarRadioButton, SIGNAL(toggled(bool)), this, SLOT(changeSize(bool))); connect(listViewRadioButton, SIGNAL(toggled(bool)), this, SLOT(changeSize(bool))); connect(iconViewRadioButton, SIGNAL(toggled(bool)), this, SLOT(changeSize(bool))); connect(tabBarRadioButton, SIGNAL(toggled(bool)), this, SLOT(changeSize(bool))); connect(otherRadioButton, SIGNAL(toggled(bool)), this, SLOT(changeSize(bool))); connect(otherSpinBox, SIGNAL(valueChanged(int)), this, SLOT(changeSize())); QHBoxLayout *otherSizeLayout = new QHBoxLayout; otherSizeLayout->addWidget(otherRadioButton); otherSizeLayout->addWidget(otherSpinBox); otherSizeLayout->addStretch(); QGridLayout *layout = new QGridLayout; layout->addWidget(smallRadioButton, 0, 0); layout->addWidget(largeRadioButton, 1, 0); layout->addWidget(toolBarRadioButton, 2, 0); layout->addWidget(listViewRadioButton, 0, 1); layout->addWidget(iconViewRadioButton, 1, 1); layout->addWidget(tabBarRadioButton, 2, 1); layout->addLayout(otherSizeLayout, 3, 0, 1, 2); layout->setRowStretch(4, 1); iconSizeGroupBox->setLayout(layout); }
Затем соединяем все сигналы радиокнопок toggled() и сигнал счётчика valueChanged() со слотом changeSize(), чтобы гарантировать, что размер пиктограммы области предварительного просмотра будет обновляться всякий раз, когда пользователь меняет размер пиктограммы. В конце поместим виджеты в компоновку, которую мы установили в групповой рамке.
void MainWindow::createActions() { addImagesAct = new QAction(tr("&Add Images..."), this); addImagesAct->setShortcut(tr("Ctrl+A")); connect(addImagesAct, SIGNAL(triggered()), this, SLOT(addImages())); removeAllImagesAct = new QAction(tr("&Remove All Images"), this); removeAllImagesAct->setShortcut(tr("Ctrl+R")); connect(removeAllImagesAct, SIGNAL(triggered()), this, SLOT(removeAllImages())); exitAct = new QAction(tr("&Quit"), this); exitAct->setShortcut(tr("Ctrl+Q")); connect(exitAct, SIGNAL(triggered()), this, SLOT(close())); styleActionGroup = new QActionGroup(this); foreach (QString styleName, QStyleFactory::keys()) { QAction *action = new QAction(styleActionGroup); action->setText(tr("%1 Style").arg(styleName)); action->setData(styleName); action->setCheckable(true); connect(action, SIGNAL(triggered(bool)), this, SLOT(changeStyle(bool))); } guessModeStateAct = new QAction(tr("&Guess Image Mode/State"), this); guessModeStateAct->setCheckable(true); guessModeStateAct->setChecked(true); aboutAct = new QAction(tr("&About"), this); connect(aboutAct, SIGNAL(triggered()), this, SLOT(about())); aboutQtAct = new QAction(tr("About &Qt"), this); connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt())); }
В функции createActions() создаём и настраиваем все действия, необходимые для реализации функциональности, связанной с пунктами меню в приложении.
В частности, мы создаём styleActionGroup на основе текущих доступных стилей ГПИ используя QStyleFactory. QStyleFactory::keys() возвращает список допустимых ключей, обычно включающих "windows", "motif", "cde" и "plastique". В зависимости от платформы, могут быть доступны "windowsxp" и "macintosh".
Создаём по одному действию для каждого ключа и добавляем действие в группу действий. Также, для всех действий вызываем QAction::setData() с именем стиля. Позднее мы извлечём его используя QAction::data().
void MainWindow::createMenus() { fileMenu = menuBar()->addMenu(tr("&File")); fileMenu->addAction(addImagesAct); fileMenu->addAction(removeAllImagesAct); fileMenu->addSeparator(); fileMenu->addAction(exitAct); viewMenu = menuBar()->addMenu(tr("&View")); foreach (QAction *action, styleActionGroup->actions()) viewMenu->addAction(action); viewMenu->addSeparator(); viewMenu->addAction(guessModeStateAct); menuBar()->addSeparator(); helpMenu = menuBar()->addMenu(tr("&Help")); helpMenu->addAction(aboutAct); helpMenu->addAction(aboutQtAct); }
В функции createMenu(), мы добавляем созданные перед этим действия в меню File, View и Help.
Класс QMenu предоставляет виджет меню для использования в панелях меню, контекстных меню и других всплывающих меню. Мы поместили все меню приложения на панель меню, которую находим используя QMainWindow::menuBar().
void MainWindow::createContextMenu() { imagesTable->setContextMenuPolicy(Qt::ActionsContextMenu); imagesTable->addAction(addImagesAct); imagesTable->addAction(removeAllImagesAct); }
Объекты QWidget содержат свойство contextMenuPolicy, которое управляет тем, как виджет будет вести себя когда пользователь запрашивает контекстное меню (например, правым щелчком мыши). Мы установили политику контекстного меню QTableWidget'а в значение Qt::ActionsContextMenu, означающее, что объекты QAction, связанные с виджетом, появятся в своем контекстном меню.
Затем добавляем действия Add Image и Remove All Images к виджету таблицы. После этого они появятся в контекстном меню виджета таблицы.
void MainWindow::checkCurrentStyle() { foreach (QAction *action, styleActionGroup->actions()) { QString styleName = action->data().toString(); QStyle *candidate = QStyleFactory::create(styleName); Q_ASSERT(candidate); if (candidate->metaObject()->className() == QApplication::style()->metaObject()->className()) { action->trigger(); return; } delete candidate; } }
В функции checkCurrentStyle() проходим по группе действий стиля, разыскивая текущий стиль ГПИ.
Для каждого действия мы сначала извлекаем имя стиля используя QAction::data(). Поскольку это только ключ QStyleFactory (например, "macintosh"), мы не можем сравнивать их непосредственно с текущим именем класса стиля. Нам нужно создать объект QStyle, используя статическую функцию QStyleFactory::create(), и сравнить имя класса созданного объекта QStyle с текущим стилем. Как только мы произвели действия с кандидатом QStyle, удаляем его.
Для всех подклассов QObject, которые используют макрос Q_OBJECT, имя класса объекта доступно через его мета-объект.
Мы можем подразумевать, что стиль поддерживается QStyleFactory, но на всякий случай используем макрос Q_ASSERT(), чтобы убедиться, что QStyleFactory::create() возвратила корректный указатель.
class IconSizeSpinBox : public QSpinBox { Q_OBJECT public: IconSizeSpinBox(QWidget *parent = 0); int valueFromText(const QString &text) const; QString textFromValue(int value) const; };
Класс IconSizeSpinBox является подклассом QSpinBox. Обычный QSpinBox может обрабатывать только целые числа. Но поскольку нам нужно выводить значения счётчика на экран в более сложной виде, нам нужно создать подкласс QSpinBox и переопределить функции QSpinBox::textFromValue() и QSpinBox::valueFromText().
IconSizeSpinBox::IconSizeSpinBox(QWidget *parent) : QSpinBox(parent) { }
Конструктор тривиален.
QString IconSizeSpinBox::textFromValue(int value) const { return tr("%1 x %1").arg(value); }
QSpinBox::textFromValue() используется счётчиком всякий раз, когда ему необходимо вывести значение на экран. Стандартная реализация возвращается представление value в десятичной системе счисления.
Переопределённая нами возвращает QString вида "32 x 32".
int IconSizeSpinBox::valueFromText(const QString &text) const { QRegExp regExp(tr("(\\d+)(\\s*[xx]\\s*\\d+)?")); if (regExp.exactMatch(text)) { return regExp.cap(1).toInt(); } else { return 0; } }
Функция QSpinBox::valueFromText() используется счётчиком всякий раз, когда ему необходимо интерпретировать текст, введённый пользователем. Поскольку мы переопределили функцию textFromValue(), также необходимо переопределить функцию valueFromText() для интерпретации текстового параметра и возврата связанного с ним целочисленного значения.
Разбор текста производим с помощью регулярного выражения (QRegExp). Определим выражение, которое находит одну или несколько цифр, следующего после них необязательного пробела, символа "x" или символ умножения, пробел и ещё одна или несколько цифр.
Первые цифры регулярного выражения собираются используя круглые скобки. Это даёт возможность использовать функции QRegExp::cap() или QRegExp::capturedTexts() для извлечения подходящих символов. Если первое и второе числа значений счётчика отличаются (например, "16 x 24"), используем первое число.
Когда пользователь нажимает Enter, QSpinBox сначала вызывает QSpinBox::valueFromText() чтобы интерпретировать текст, набранный пользователем, затем - QSpinBox::textFromValue(), чтобы представить его в каноническом формате (например, "16 x 16").
class ImageDelegate : public QItemDelegate { Q_OBJECT public: ImageDelegate(QObject *parent = 0);
Класс ImageDelegate является подклассом QItemDelegate. Класс QItemDelegate предоставляет возможности вывода на экран и редактирования элементов данных модели. Единственный объект QItemDelegate отвечает за все элементы, выводимые на экран в представлении элементов (в нашем случае, QTableWidget).
QItemDelegate можно использовать для предоставления редактора для класса представления элементов, унаследованного от QAbstractItemView. Использование делегата для этой цели позволяет настраивать и разрабатывать механизм редактирования независимо от модели и представления.
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; void setEditorData(QWidget *editor, const QModelIndex &index) const; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;
Стандартная реализация QItemDelegate создаёт QLineEdit. Поскольку нам нужен редактора для QComboBox, нам нужно создать подкласс QItemDelegate и переопределить функции QItemDelegate::createEditor(), QItemDelegate::setEditorData() и QItemDelegate::setModelData().
private slots: void emitCommitData(); };
Слот emitCommitData() используется для отправки сигнала QImageDelegate::commitData() с соответствующим аргументом.
ImageDelegate::ImageDelegate(QObject *parent) : QItemDelegate(parent) { }
Конструктор тривиален.
QWidget *ImageDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem & /* option */,
const QModelIndex &index) const
{
QComboBox *comboBox = new QComboBox(parent);
if (index.column() == 1) {
comboBox->addItem(tr("Normal"));
comboBox->addItem(tr("Active"));
comboBox->addItem(tr("Disabled"));
comboBox->addItem(tr("Selected"));
} else if (index.column() == 2) {
comboBox->addItem(tr("Off"));
comboBox->addItem(tr("On"));
}
connect(comboBox, SIGNAL(activated(int)), this, SLOT(emitCommitData()));
return comboBox;
}
Стандартная реализация QItemDelegate::createEditor() возвращает виджет, используемый для редактирования элемента, задаваемого моделью и индексом элемента. Родительский виджет и опции стиля используются для управления внешним видом виджета редактора.
Наша переопределённая функция создаёт и заполняет выпадающий список вместо однострочного редактора по умолчанию. Содержимое выпадающего списка зависит от столбца в таблице, для которого запрашивается редактор. Столбец 1 содержит режимы QIcon, тогда как столбец 2 содержит состояния QIcon.
Кроме того, мы соединили сигнал activated() выпадающего списка со слотом emitCommitData() для отправки сигнала QAbstractItemDelegate::commitData() всякий раз, когда пользователь выберет элемент используя выпадающий список. Этим гарантируется, что остальная часть приложения уведомлена об изменении и обновит себя самостоятельно.
void ImageDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { QComboBox *comboBox = qobject_cast<QComboBox *>(editor); if (!comboBox) return; int pos = comboBox->findText(index.model()->data(index).toString(), Qt::MatchExactly); comboBox->setCurrentIndex(pos); }
Функция QItemDelegate::setEditorData() используется QTableWidget для передачи данных из QTableWidgetItem в редактор. Данные сохраняются в виде строки; мы используем QComboBox::findText() для отыскания их в выпадающем списке.
Делегаты работают в терминах моделей, а не элементов. Это делает возможным его использование с любым классом представления элементов (например, QListView, QListWidget, QTreeView и т.д.). Переход между моделью и элементами выполняется QTableWidget неявно; нам не нужно беспокоиться об этом.
void ImageDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QComboBox *comboBox = qobject_cast<QComboBox *>(editor); if (!comboBox) return; model->setData(index, comboBox->currentText()); }
Функция QItemDelegate::setEditorData() используется QTableWidget для передачи данных обратно из редактора в QTableWidgetItem.
void ImageDelegate::emitCommitData() { emit commitData(qobject_cast<QWidget *>(sender())); }
Слот emitCommitData() просто отправляет сигнал QAbstractItemDelegate::commitData() редактору, который переключает слот. Этот сигнал должен быть отправлен когда виджет редактора завершил редактирование данных и хочет записать их обратно в модель.
Copyright © 2008 Nokia | Торговые марки | Qt 4.4.3 |
Попытка перевода Qt документации. Если есть желание присоединиться, или если есть замечания или пожелания, то заходите на форум: Перевод Qt документации на русский язык... Люди внесшие вклад в перевод: Команда переводчиков |