Пример "Image Viewer"Файлы:
Пример "Image Viewer" показывает, как сочетать метку QLabel и прокручиваемую область QScrollArea для вывода изображения на экран. QLabel обычно используется для вывода текст на экран, но также она может выводить на экран изображение. QScrollArea предоставляет прокручиваемую область вокруг другого виджета. Если дочерний виджет превышает размеры рамки, QScrollArea автоматически предоставит полосы прокрутки. Пример демонстрирует как возможность QLabel'а по масштабированию своего содержимого (QLabel::scaledContents), и возможности QScrollArea'а по автоматическому изменению размера своего содержимого (QScrollArea::widgetResizable), могут быть использованы для реализации возможностей масштабирования. Кроме того, пример показывает, как использовать QPainter для печати изображения. С приложением "Image Viewer" пользователи могут просматривать выбранное ими изображение. Меню File даёт пользователю возможности для:
После загрузки изображения, меню View позволяет пользователю:
Кроме того, меню Help предоставляет пользователям информацию о примере "Image Viewer" в частности, и о Qt вообще. Определение класса ImageViewerclass ImageViewer : public QMainWindow { Q_OBJECT public: ImageViewer(); private slots: void open(); void print(); void zoomIn(); void zoomOut(); void normalSize(); void fitToWindow(); void about(); private: void createActions(); void createMenus(); void updateActions(); void scaleImage(double factor); void adjustScrollBar(QScrollBar *scrollBar, double factor); QLabel *imageLabel; QScrollArea *scrollArea; double scaleFactor; #ifndef QT_NO_PRINTER QPrinter printer; #endif QAction *openAct; QAction *printAct; QAction *exitAct; QAction *zoomInAct; QAction *zoomOutAct; QAction *normalSizeAct; QAction *fitToWindowAct; QAction *aboutAct; QAction *aboutQtAct; QMenu *fileMenu; QMenu *viewMenu; QMenu *helpMenu; }; Класс ImageViewer унаследован от QMainWindow. Переопределим конструктор и создадим несколько закрытых слотов, облегчающих работу пунктам меню. Дополнительно создадим пять закрытых функций. Используем createActions() и createMenus() при создании виджета ImageViewer. Используем функцию updateActions() для обновления пунктов меню когда загружено новое изображение, или когда включена опция Fit to Window. Слоты масштаба используют scaleImage() для выполнения масштабирования. В свою очередь, scaleImage() использует adjustScrollBar() для сохранения точки фокуса после масштабирования изображения. Реализация класса ImageViewerImageViewer::ImageViewer() { imageLabel = new QLabel; imageLabel->setBackgroundRole(QPalette::Base); imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); imageLabel->setScaledContents(true); scrollArea = new QScrollArea; scrollArea->setBackgroundRole(QPalette::Dark); scrollArea->setWidget(imageLabel); setCentralWidget(scrollArea); createActions(); createMenus(); setWindowTitle(tr("Image Viewer")); resize(500, 400); } В конструкторе мы сначала создаём метку и прокручиваемую область. Мы установили политику размера imageLabel'а в значение ignored, сделав возможным для пользователей масштабирование изображения до необходимого из размера когда включена опция Fit to Window. В противном случае, политика размера по умолчанию (preferred) приведёт к появлению полос прокрутки когда область прокрутки станет меньше, чем минимальная подсказка размера метки. Мы гарантируем, что метка будет масштабировать своё содержимое с тем, чтобы заполнить всё доступное пространство, чтобы разрешить масштабирование изображения правильно во время изменения масштаба. Если мы не установим свойство imageLabel'а, scaledContents, увеличение масштаба будет увеличивать QLabel, но сохранит исходные размеры растрового изображения, сделав видимым фон QLabel'а. Сделаем imageLabel дочерним виджетом области прокрутки, а также сделаем scrollArea главным виджетом QMainWindow. В завершение создадим ассоциированные действия и меню, а также настроим внешний вид ImageViewer'а. void ImageViewer::open() { QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), QDir::currentPath()); if (!fileName.isEmpty()) { QImage image(fileName); if (image.isNull()) { QMessageBox::information(this, tr("Image Viewer"), tr("Cannot load %1.").arg(fileName)); return; } В слоте open() показываем пользователю диалог выбора файла. Самый лёгкий способ создания QFileDialog - использовать статические вспомогательные функции. QFileDialog::getOpenFileName() возвращает существующий файл, выбранный пользователем. Если пользователь нажал Cancel, QFileDialog вернёт пустую строку. Если имя файла не пустая строка, то мы проверяем не совпадает ли формат файла с форматом изображения создав QImage, который пытается загрузить изображение из файла. Если конструктор вернул пустое изображение, используем QMessageBox чтобы предупредить пользователя. Класс QMessageBox предоставляет модальный диалог с коротким сообщением, пиктограммой и несколькими кнопками. С QFileDialog самым лёгким способом создания QMessageBox является использование его вспомогательных статических функций. QMessageBox предоставляет ряд различных сообщений упорядоченных по двум осям: серьёзности (вопрос, информация, предупреждение и критическое сообщение) и сложности (количеству необходимых реагирующих кнопок). В данном конкретном примере информационного сообщения с кнопкой OK (по умолчанию) достаточно, поскольку сообщение является частью обычной операции. imageLabel->setPixmap(QPixmap::fromImage(image)); scaleFactor = 1.0; printAct->setEnabled(true); fitToWindowAct->setEnabled(true); updateActions(); if (!fitToWindowAct->isChecked()) imageLabel->adjustSize(); } } Если формат поддерживается, выводим изображение в imageLabel установив растровое изображение растрового изображения. Затем включаем пункты меню Print и Fit to Window и обновляем остальные пункты меню вид. Пункты Open и Exit включены по умолчанию. Если опция Fit to Window выключена, то свойство QScrollArea::widgetResizable равно false и мы (а не QScrollArea'а) отвечаем за предоставление QLabel рациональных размеров основываясь на его содержимом. Чтобы этого добиться вызываем {QWidget::adjustSize()}{adjustSize()}, который, по существу, является тем же самым что и imageLabel->resize(imageLabel->pixmap()->size()); В слоте print() мы сначала убеждаемся, что изображение загружено в приложение: void ImageViewer::print() { Q_ASSERT(imageLabel->pixmap()); #ifndef QT_NO_PRINTER Если приложение собирается в режиме отладки, макрос Q_ASSERT() будет расширен до if (!imageLabel->pixmap()) qFatal("ASSERT: " imageLabel->pixmap() " in file ..."); В режиме релиза, макрос просто перестает существовать. Режим можно устнановить в файле .pro приложения. Одним из способов сделать это - добавить при сборке приложения опцию к qmake: qmake "CONFIG += debug" foo.pro или qmake "CONFIG += release" foo.pro Другой подход заключается в добавлении этой строки непосредственно в файл .pro. QPrintDialog dialog(&printer, this); if (dialog.exec()) { QPainter painter(&printer); QRect rect = painter.viewport(); QSize size = imageLabel->pixmap()->size(); size.scale(rect.size(), Qt::KeepAspectRatio); painter.setViewport(rect.x(), rect.y(), size.width(), size.height()); painter.setWindow(imageLabel->pixmap()->rect()); painter.drawPixmap(0, 0, *imageLabel->pixmap()); } #endif } Затем покажем диалог печати, позволяющий пользователю выбрать принтер и установить некоторые опции. В качестве устройства рисования создаём рисоваольщика с помощью QPrinter. Устанавливаем окно рисовальщика и область просмотра таким образом, чтобы изображение было на бумаге максимально большим, но без изменения его коэффициента пропорциональности. В заключение рисуем растровое изображение в позиции (0, 0). void ImageViewer::zoomIn() { scaleImage(1.25); } void ImageViewer::zoomOut() { scaleImage(0.8); } Мы реализовали слоты изменения масштаба используя закрытую функцию scaleImage(). Установили коэффициенты масштабирования от 1.25 до 0.8, соответственно. Эти значения коэффициента гарантируют, что действие Zoom In и действие Zoom Out взаимно погасят друг друга (поскольку 1.25 * 0.8 == 1), и таким образом будет восстановлено обычный размер изображения используя возможности изменения масштаба. Снимки экрана ниже показывают изображение в его обычном размере, а также некоторое изображение после увеличения масштаба: void ImageViewer::normalSize() { imageLabel->adjustSize(); scaleFactor = 1.0; } При изменении масштаба мы используем возможность QLabel'а по масштабированию своего содержимого. Такое масштабирование не изменяет действующую подсказку размера содержимого. А поскольку функция adjustSize() использует эту подсказку размер, для восстановления обычного размера отображаемого в настоящий момент времени изображения на нужно только вызвать adjustSize() и сбросить коэффициент масштабирования в значение 1.0. void ImageViewer::fitToWindow() { bool fitToWindow = fitToWindowAct->isChecked(); scrollArea->setWidgetResizable(fitToWindow); if (!fitToWindow) { normalSize(); } updateActions(); } Слот fitToWindow() вызывается каждый раз, когда пользователь переключает опцию Fit to Window. Если слоты вызывается для включения опции, то мы сообщаем области прокрутки о необходимости изменить размер его дочернего виджета с помощью функции QScrollArea::setWidgetResizable(). Затем мы отключаем пункты меню Zoom In, Zoom Out и Normal Size, используя закрытую функцию updateActions(). Если свойство QScrollArea::widgetResizable имеет значение false (по умолчанию), область прокрутки соблюдает размер дочернего виджета. Если это свойство имеет значение true, область прокрутки будет автоматически изменять размер виджета с тем, чтобы избежать появления полос прокрутки там, где они не нужны, или для получения выгоды от дополнительного пространства. Но область прокрутки будет соблюдать минимальную подсказку размера дочернего виджета независимо от свойства изменения размера виджета. В этом примере мы установили политику размера imageLabel'а в значение ignored в конструкторе, чтобы избежать появления полос прокрутки когда область прокручивания становится меньше, чем минимальная подсказка размера метки. Снимки экрана ниже показывают изображение обычного размера, а также несколько изображений с включённой опцией Fit to window. Увеличение размера окна растянет изображение, как показано на третьем снимке экрана. Если слот вызывается для отключения опции, свойство {QScrollArea::setWidgetResizable} примет значение false. Также мы восстановили исходный размер растрового изображения, подогнав размеры метки под её содержимое. И в заключение обновили пункты меню вид. void ImageViewer::about() { QMessageBox::about(this, tr("About Image Viewer"), tr("<p>The <b>Image Viewer</b> example shows how to combine QLabel " "and QScrollArea to display an image. QLabel is typically used " "for displaying a text, but it can also display an image. " "QScrollArea provides a scrolling view around another widget. " "If the child widget exceeds the size of the frame, QScrollArea " "automatically provides scroll bars. </p><p>The example " "demonstrates how QLabel's ability to scale its contents " "(QLabel::scaledContents), and QScrollArea's ability to " "automatically resize its contents " "(QScrollArea::widgetResizable), can be used to implement " "zooming and scaling features. </p><p>In addition the example " "shows how to use QPainter to print an image.</p>")); } Мы реализовали слот about() для создания окна сообщения, описывающего для чего спроектирован пример. void ImageViewer::createActions() { openAct = new QAction(tr("&Open..."), this); openAct->setShortcut(tr("Ctrl+O")); connect(openAct, SIGNAL(triggered()), this, SLOT(open())); printAct = new QAction(tr("&Print..."), this); printAct->setShortcut(tr("Ctrl+P")); printAct->setEnabled(false); connect(printAct, SIGNAL(triggered()), this, SLOT(print())); exitAct = new QAction(tr("E&xit"), this); exitAct->setShortcut(tr("Ctrl+Q")); connect(exitAct, SIGNAL(triggered()), this, SLOT(close())); zoomInAct = new QAction(tr("Zoom &In (25%)"), this); zoomInAct->setShortcut(tr("Ctrl++")); zoomInAct->setEnabled(false); connect(zoomInAct, SIGNAL(triggered()), this, SLOT(zoomIn())); zoomOutAct = new QAction(tr("Zoom &Out (25%)"), this); zoomOutAct->setShortcut(tr("Ctrl+-")); zoomOutAct->setEnabled(false); connect(zoomOutAct, SIGNAL(triggered()), this, SLOT(zoomOut())); normalSizeAct = new QAction(tr("&Normal Size"), this); normalSizeAct->setShortcut(tr("Ctrl+S")); normalSizeAct->setEnabled(false); connect(normalSizeAct, SIGNAL(triggered()), this, SLOT(normalSize())); fitToWindowAct = new QAction(tr("&Fit to Window"), this); fitToWindowAct->setEnabled(false); fitToWindowAct->setCheckable(true); fitToWindowAct->setShortcut(tr("Ctrl+F")); connect(fitToWindowAct, SIGNAL(triggered()), this, SLOT(fitToWindow())); 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())); } В закрытой функции createAction() мы создали действия, предоставляющие возможности приложения. Мы назначили "быстрые" клавиши для всех действий и соединили их с соответствующими слотами. Включили только openAct и exitAxt во время создания, остальные обновили один раз после загрузки изображения в приложение. В дополнение мы сделали fitToWindowAct отмечаемым. void ImageViewer::createMenus() { fileMenu = new QMenu(tr("&File"), this); fileMenu->addAction(openAct); fileMenu->addAction(printAct); fileMenu->addSeparator(); fileMenu->addAction(exitAct); viewMenu = new QMenu(tr("&View"), this); viewMenu->addAction(zoomInAct); viewMenu->addAction(zoomOutAct); viewMenu->addAction(normalSizeAct); viewMenu->addSeparator(); viewMenu->addAction(fitToWindowAct); helpMenu = new QMenu(tr("&Help"), this); helpMenu->addAction(aboutAct); helpMenu->addAction(aboutQtAct); menuBar()->addMenu(fileMenu); menuBar()->addMenu(viewMenu); menuBar()->addMenu(helpMenu); } В закрытой функции createMenu(), добавили предварительно созданные действия для меню File, View и Help. Класс QMenu предоставляет виджет меню для использования в панелях меню, контекстных меню и других всплывающих меню. Класс QMenuBar предоставляет панель горизонтального меню, содержащее список пунктов выпадающих меню. Поэтому в конце мы поместим меню в панель меню ImageViewer'а, который находим с помощью функции QMainWindow::menuBar(). void ImageViewer::updateActions() { zoomInAct->setEnabled(!fitToWindowAct->isChecked()); zoomOutAct->setEnabled(!fitToWindowAct->isChecked()); normalSizeAct->setEnabled(!fitToWindowAct->isChecked()); } Закрытая функция updateActions() включает или выключает пункты меню Zoom In, Zoom Out и Normal Size, в зависимости от того, включена или отключена опция Fit to Window. void ImageViewer::scaleImage(double factor) { Q_ASSERT(imageLabel->pixmap()); scaleFactor *= factor; imageLabel->resize(scaleFactor * imageLabel->pixmap()->size()); adjustScrollBar(scrollArea->horizontalScrollBar(), factor); adjustScrollBar(scrollArea->verticalScrollBar(), factor); zoomInAct->setEnabled(scaleFactor < 3.0); zoomOutAct->setEnabled(scaleFactor > 0.333); } В scaleImage() используем параметр factor для вычисления нового коэффициента масштабирования для выводимого на экран изображения, и изменяем размер imageLabel. Так как в конструкторе установили свойство scaledContents в значение true, то вызов QWidget::resize() изменит масштаб изображения отображаемого в метке. Также мы подгоняем полосы прокрутки так, чтобы сохранить фокус на изображении. В заключение, если коэффициент масштабирования меньше чем 33.3% или больше чем 300%, отключаем соответствующий пункт меню чтобы предотвратить излишнее увеличение растрового изображения, захватывающего слишком много ресурсов в оконной системе. void ImageViewer::adjustScrollBar(QScrollBar *scrollBar, double factor) { scrollBar->setValue(int(factor * scrollBar->value() + ((factor - 1) * scrollBar->pageStep()/2))); } Всякий раз когда увеличивается или уменьшается масштаб нам в результате нужно подгонять полосы прокрутки. Заманчиво делать это простым вызовом scrollBar->setValue(int(factor * scrollBar->value())); но это сделает точкой фокуса верхний левый угол, а не центр. По этой причине нам нужно принимать во внимание размер метки-манипулятор полосы прокрутки (прокрутка на страницу). |
Попытка перевода Qt документации. Если есть желание присоединиться, или если есть замечания или пожелания, то заходите на форум: Перевод Qt документации на русский язык... Люди внесшие вклад в перевод: Команда переводчиков |