Artikel dieses Zyklus:
Im Artikel über die Arbeit mit dem Audioplayer in Qt haben wir uns mit dem Abspielen von Audiospuren und dem Wechseln zwischen ihnen in einer Wiedergabeliste vertraut gemacht.
Wie wäre es, das Erscheinungsbild des Players so anzupassen, dass es beispielsweise wie AIMP aussieht? Schauen wir uns nur zum Vergleich das ursprüngliche AIMP und das Erscheinungsbild des Players nach der Anpassung an.
Das Hauptproblem bei dieser Anpassung von Qt-Anwendungen besteht darin, dass Qt nicht über die Möglichkeit verfügt, den Rahmen des Anwendungsfensters anzupassen. Das heißt, im Fall von Windows ist es notwendig, WinAPI zu verwenden, um das Fenster zu dekorieren, und im Fall von Unix- und Linux-Systemen ist es notwendig, die entsprechenden Window-Decorator-APIs zu verwenden.
Das einzige, was Qt tun kann, ist, die Fensterränder vollständig zu deaktivieren. Dann müssen Sie die Fensterdekoration vollständig mit Qt schreiben, ohne die Fähigkeiten des Betriebssystems zu nutzen, die Logik für die Schaltflächen schreiben, um das Fenster zu minimieren und zu maximieren, sowie die Logik zum Verschieben und Ändern der Größe des Anwendungsfensters. Zeichnen Sie dementsprechend alle Stile von Schnittstellenelementen mit dem QSS StyleSheet-Mechanismus. Eine alternative Designoption ist QPalette, aber Stile werden im Gegensatz zur Verwendung von QSS Stylesheet nicht immer korrekt verarbeitet.
Anwendungsfensterränder deaktivieren
Damit die Anwendungsschnittstelle korrekt verarbeitet werden kann, benötigen wir:
- Fensterrahmen deaktivieren;
- Machen Sie das Haupt-Widget des Fensters transparent, in dem sich die gesamte Oberfläche befindet;
- und platzieren Sie die Elemente der Benutzeroberfläche selbst in einem anderen Widget, das sich innerhalb des Haupt-Widgets befindet und gleichzeitig nicht transparent ist;
- Sie müssen auch einen Schatten um das Widget mit einer Schnittstelle erstellen, um den Schatten aus dem Anwendungsfenster zu emulieren, wie es für gewöhnliche Fenster, einschließlich des AIMP-Fensters, gemacht wird.
Bevor die Fensterränder deaktiviert und das Haupt-Widget in den transparenten Modus versetzt wurden, wurden einige Änderungen an der Benutzeroberfläche selbst im Grafikdesigner vorgenommen.
Wie Sie dem Elementbaum entnehmen können, gibt es ein Haupt-Widget, das transparent sein sollte, sowie ein Widget mit einer grafischen Oberfläche. Der Bereich, der zwischen den Rändern zweier rechteckiger roter Auswahlen eingeschlossen ist, ist der sichtbare Teil des Haupt-Widgets. Es enthält den Schatten des Anwendungsfensters sowie den Bereich, der zum Ändern der Größe des Anwendungsfensters verwendet wird.
Das Qt::FramelessWindowHint -Flag muss gesetzt werden, um Fensterrahmen zu deaktivieren. Um den Hintergrund des Widgets in den vollständig transparenten Modus zu versetzen, legen Sie das Attribut Qt::WA_TranslucentBackground fest.
Die Installation des Schattens muss nicht mehr auf dem Haupt-Widget selbst erfolgen, sondern auf dem Widget, das die Anwendungsoberfläche enthält. Um einen Schatteneffekt zu erzeugen, wird ein Objekt der Klasse QGraphicsFropShadowEffect. verwendet.
All dies erledigen wir im Klassenkonstruktor Widget , der für das Anwendungsfenster verantwortlich ist.
/// Настройка UI this->setWindowFlags(Qt::FramelessWindowHint); // Отключаем оформление окна this->setAttribute(Qt::WA_TranslucentBackground); // Делаем фон главного виджета прозрачным this->setStyleSheet(StyleHelper::getWindowStyleSheet()); // Устанавливаем стиль виджета // Создаём эффект тени QGraphicsDropShadowEffect *shadowEffect = new QGraphicsDropShadowEffect(this); shadowEffect->setBlurRadius(9); // Устанавливаем радиус размытия shadowEffect->setOffset(0); // Устанавливаем смещение тени ui->widgetInterface->setGraphicsEffect(shadowEffect); // Устанавливаем эффект тени на окно ui->widgetInterface->layout()->setMargin(0); // Устанавливаем размер полей ui->widgetInterface->layout()->setSpacing(0); // Устанавливаем размер пространства между элементами в размещении виджета
QSS-Stylesheet
Wie Sie bereits bemerkt haben, wird im obigen Codeabschnitt die Klasse StyleHelper verwendet, die die statische Methode getWindowStyleSheet() aufruft. Diese Klasse ist eine Hilfsklasse und gibt einen QString zurück, der den Stil für die Gestaltung des Schnittstellen-Widgets beschreibt. Diese Methoden setzen auch Schaltflächensymbole aus Ressourcendateien, einige dieser Symbole werden unten gezeigt.
QSS-Stile werden nach dem Prinzip von CSS-Stilen im Weblayout implementiert. Sie sind für genau die gleichen Parameter verantwortlich. Zum Beispiel Padding, Margin etc.
Die Klasse StyleHelper mit folgendem Inhalt wird verwendet, um die Fensterdekoration für diese Anwendung zu implementieren.
stylehelper.h
Ich mache Sie darauf aufmerksam, dass die Klasse zwei Methoden verwendet, mit Stilen, die unter bestimmten Bedingungen auf dasselbe Element (die Schaltfläche zum Maximieren / Normalisieren der Größe des Anwendungsfensters) angewendet werden. Dies sind die Methoden getMaximizeStyleSheet() und getRestoreStyleSheet().
#ifndef STYLEHELPER_H #define STYLEHELPER_H #include <QString> class StyleHelper { public: static QString getWindowStyleSheet(); static QString getLabelStyleSheet(); static QString getCloseStyleSheet(); static QString getMaximizeStyleSheet(); static QString getRestoreStyleSheet(); static QString getMinimizeStyleSheet(); static QString getNextStyleSheet(); static QString getPreviousStyleSheet(); static QString getStopStyleSheet(); static QString getPlayStyleSheet(); static QString getPauseStyleSheet(); static QString getMenuStyleSheet(); static QString getTableViewStyleSheet(); }; #endif // STYLEHELPER_H
stylehelper.cpp
Wie Sie sehen, beschreiben die Klassenmethoden den Stil der gesamten Anwendungsoberfläche sowie Stile für die sogenannten Pseudoklassen, die für den Zustand des Objekts verantwortlich sind, wenn darauf geklickt wird oder wenn der Mauszeiger darüber liegt dieses Objekt. Für diese Zustände erfolgt auch die Verbindung verschiedener Icons, wie für die Schaltflächen in der Anwendung.
#include "stylehelper.h" QString StyleHelper::getWindowStyleSheet() { return "QWidget { " "background-color: #454545; " "border: 1px solid black; " "}"; } QString StyleHelper::getLabelStyleSheet() { return "QLabel { " "color: #8f8f8f; " "border: none; " "margin: 6px; " "}"; } QString StyleHelper::getCloseStyleSheet() { return "QToolButton { " "image: url(:/buttons/close-orange.png);" "background-color: #292929; " "icon-size: 12px;" "padding-left: 10px;" "padding-right: 10px;" "padding-top: 5px;" "padding-bottom: 5px;" "border: 1px solid #292929; " "}" "QToolButton:hover {" "image: url(:/buttons/close.png); " "}" "QToolButton:pressed { " "image: url(:/buttons/close.png);" "background-color: #de8e37; " "}"; } QString StyleHelper::getMaximizeStyleSheet() { return "QToolButton { " "image: url(:/buttons/window-maximize-gray.png);" "background-color: #292929;" "icon-size: 12px;" "padding-left: 10px;" "padding-right: 10px;" "padding-top: 5px;" "padding-bottom: 5px;" "border: 1px solid #292929; " "}" "QToolButton:hover {" "image: url(:/buttons/window-maximize.png); " "}" "QToolButton:pressed { " "image: url(:/buttons/window-maximize.png);" "background-color: #de8e37; " "}"; } QString StyleHelper::getRestoreStyleSheet() { return "QToolButton { " "image: url(:/buttons/window-restore-gray.png);" "background-color: #292929;" "icon-size: 12px;" "padding-left: 10px;" "padding-right: 10px;" "padding-top: 5px;" "padding-bottom: 5px;" "border: 1px solid #292929; " "}" "QToolButton:hover {" "image: url(:/buttons/window-restore.png); " "}" "QToolButton:pressed { " "image: url(:/buttons/window-restore.png);" "background-color: #de8e37; " "}"; } QString StyleHelper::getMinimizeStyleSheet() { return "QToolButton { " "image: url(:/buttons/window-minimize-gray.png);" "background-color: #292929;" "icon-size: 12px;" "padding-left: 10px;" "padding-right: 10px;" "padding-top: 5px;" "padding-bottom: 5px;" "border: 1px solid #292929; " "}" "QToolButton:hover { " "image: url(:/buttons/window-minimize.png); " "}" "QToolButton:pressed { " "image: url(:/buttons/window-minimize.png);" "background-color: #de8e37; " "}"; } QString StyleHelper::getNextStyleSheet() { return "QToolButton { " "image: url(:/buttons/skip-next.png);" "icon-size: 24px;" "padding: 6px;" "margin: 6px;" "border: none;" "}" "QToolButton:pressed { " "image: url(:/buttons/skip-next-orange.png)" "}"; } QString StyleHelper::getPreviousStyleSheet() { return "QToolButton { " "image: url(:/buttons/skip-previous.png);" "icon-size: 24px;" "padding: 6px;" "margin: 6px;" "border: none;" "}" "QToolButton:pressed { " "image: url(:/buttons/skip-previous-orange.png)" "}"; } QString StyleHelper::getStopStyleSheet() { return "QToolButton { " "image: url(:/buttons/stop.png);" "icon-size: 24px;" "padding: 6px;" "margin: 6px;" "border: none;" "}" "QToolButton:pressed { " "image: url(:/buttons/stop-orange.png)" "}"; } QString StyleHelper::getPlayStyleSheet() { return "QToolButton { " "image: url(:/buttons/play.png);" "icon-size: 48px;" "padding: 6px;" "margin: 6px;" "border: none;" "}" "QToolButton:pressed { " "image: url(:/buttons/play-orange.png)" "}"; } QString StyleHelper::getPauseStyleSheet() { return "QToolButton { " "image: url(:/buttons/pause.png);" "icon-size: 24px;" "padding: 6px;" "margin: 6px;" "border: none;" "}" "QToolButton:pressed { " "image: url(:/buttons/pause-orange.png)" "}"; } QString StyleHelper::getMenuStyleSheet() { return "QToolButton { " "color: #8f8f8f;" "background-color: #292929;" "icon-size: 12px;" "padding-left: 10px;" "padding-right: 10px;" "padding-top: 5px;" "padding-bottom: 5px;" "border: 1px solid #292929; " "}" "QToolButton:hover {" "color: white;" "}" "QToolButton:pressed { " "color: white; " "background-color: #de8e37; " "}"; } QString StyleHelper::getTableViewStyleSheet() { return "QTableView { " "background-color: white; " "color: black; " "border: 1px solid #e2e2de;" "}" "QTableView::item:selected {" "background-color: #de8e37;" "}" "QHeaderView::section:horizintal {" "background-color: white;" "border-style: none;" "color: black; " "border: 1px solid #e2e2de; " "padding: 6px; " "}"; }
Größe und Position des Anwendungsfensters ändern
Da wir das Framing des Anwendungsfensters deaktiviert haben, fällt nun die Umsetzung dieser Aufgaben auf unsere Schultern.
Widget.h
Werfen wir einen Blick auf die Header-Datei des Widgets, um zu verstehen, was wir zur Implementierung dieser Funktionalität benötigen.
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QStandardItemModel> #include <QMediaPlayer> #include <QMediaPlaylist> #include <QMouseEvent> namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT // Свойство с точкой предыдущей позиции мыши // Относительно данной точки идёт пересчёт позиции окна // Или размеров окна. При этом свойство устанавливается при нажатии мыши // по окну и в ряде иных случаев Q_PROPERTY(QPoint previousPosition READ previousPosition WRITE setPreviousPosition NOTIFY previousPositionChanged) // тип клика мыши, при перемещении курсора по этому типу будем определять // что именно нужно сделать, перенести окно, или изменить его размер с одной из сторон enum MouseType { None = 0, Top, Bottom, Left, Right, Move }; public: explicit Widget(QWidget *parent = 0); ~Widget(); QPoint previousPosition() const; public slots: void setPreviousPosition(QPoint previousPosition); signals: void previousPositionChanged(QPoint previousPosition); private slots: void on_btn_add_clicked(); protected: void mousePressEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); private: Ui::Widget *ui; QStandardItemModel *m_playListModel; QMediaPlayer *m_player; QMediaPlaylist *m_playlist; // Переменная, от которой будем отталкиваться при работе с перемещением и изменением размера окна MouseType m_leftMouseButtonPressed; QPoint m_previousPosition; MouseType checkResizableField(QMouseEvent *event); }; #endif // WIDGET_H
Widget.cpp
Um die Bewegung und Größenänderung des Anwendungsfensters zu implementieren, müssen Sie genau nachverfolgen, wo der Klick erfolgt ist. Wenn wir in ein Widget mit einer Benutzeroberfläche geklickt haben (wo es jedoch keine Steuerelemente gibt, die Klickereignisse abfangen, wie z. B. Schaltflächen), haben wir die Möglichkeit, das Anwendungsfenster zu verschieben. Wenn Sie zum Ändern der Größe auf einen der vier Bereiche geklickt haben, ändern wir die Größe entsprechend.
In der folgenden Abbildung sind die Bereiche zum Ändern der Größe rot dargestellt, und der Bereich zum Verschieben des Anwendungsfensters ist blau hervorgehoben.
Und jetzt beschäftigen wir uns mit dem Code, alle Momente darin sind kommentiert.
#include "widget.h" #include "ui_widget.h" #include <QFileDialog> #include <QDir> #include <QGraphicsDropShadowEffect> #include "stylehelper.h" Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget), m_leftMouseButtonPressed(None) { ui->setupUi(this); /// Настройка UI this->setWindowFlags(Qt::FramelessWindowHint); // Отключаем оформление окна this->setAttribute(Qt::WA_TranslucentBackground); // Делаем фон главного виджета прозрачным this->setStyleSheet(StyleHelper::getWindowStyleSheet()); // Устанавливаем стиль виджета this->setMouseTracking(true); // Включаем отслеживание курсора без нажатых кнопокы // Создаём эффект тени QGraphicsDropShadowEffect *shadowEffect = new QGraphicsDropShadowEffect(this); shadowEffect->setBlurRadius(9); // Устанавливаем радиус размытия shadowEffect->setOffset(0); // Устанавливаем смещение тени ui->widgetInterface->setGraphicsEffect(shadowEffect); // Устанавливаем эффект тени на окно ui->widgetInterface->layout()->setMargin(0); // Устанавливаем размер полей ui->widgetInterface->layout()->setSpacing(0); ui->label->setText("AIMP Fake Player"); ui->label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); // Установка стилей для всех элементов ui->currentTrack->setStyleSheet(StyleHelper::getLabelStyleSheet()); ui->label->setStyleSheet(StyleHelper::getLabelStyleSheet()); ui->btn_close->setStyleSheet(StyleHelper::getCloseStyleSheet()); ui->btn_maximize->setStyleSheet(StyleHelper::getMaximizeStyleSheet()); ui->btn_minimize->setStyleSheet(StyleHelper::getMinimizeStyleSheet()); ui->btn_next->setStyleSheet(StyleHelper::getNextStyleSheet()); ui->btn_previous->setStyleSheet(StyleHelper::getPreviousStyleSheet()); ui->btn_stop->setStyleSheet(StyleHelper::getStopStyleSheet()); ui->btn_play->setStyleSheet(StyleHelper::getPlayStyleSheet()); ui->btn_pause->setStyleSheet(StyleHelper::getPauseStyleSheet()); ui->btn_add->setStyleSheet(StyleHelper::getMenuStyleSheet()); ui->playlistView->setStyleSheet(StyleHelper::getTableViewStyleSheet()); ui->btn_add->setText(tr("Добавить")); ui->btn_next->setCursor(Qt::PointingHandCursor); ui->btn_previous->setCursor(Qt::PointingHandCursor); ui->btn_stop->setCursor(Qt::PointingHandCursor); ui->btn_play->setCursor(Qt::PointingHandCursor); ui->btn_pause->setCursor(Qt::PointingHandCursor); ui->horizontalLayout->setSpacing(6); /// /* Код из предыдущей статьи */ /// коннекты для кнопок сворачивания/максимизации/минимизации/закрытия // Сворачивание окна приложения в панель задач connect(ui->btn_minimize, &QToolButton::clicked, this, &QWidget::showMinimized); connect(ui->btn_maximize, &QToolButton::clicked, [this](){ // При нажатии на кнопку максимизации/нормализации окна // Делаем проверку на то, в каком состоянии находится окно и переключаем его режим if (this->isMaximized()) { // Заметьте, каждый раз устанавливаем новый стиль в эту кнопку ui->btn_maximize->setStyleSheet(StyleHelper::getMaximizeStyleSheet()); this->layout()->setMargin(9); this->showNormal(); } else { ui->btn_maximize->setStyleSheet(StyleHelper::getRestoreStyleSheet()); this->layout()->setMargin(0); this->showMaximized(); } }); // Закрытие окна приложения connect(ui->btn_close, &QToolButton::clicked, this, &QWidget::close); /* Код из предыдущей статьи */ } Widget::~Widget() { delete ui; delete m_playListModel; delete m_playlist; delete m_player; } QPoint Widget::previousPosition() const { return m_previousPosition; } void Widget::setPreviousPosition(QPoint previousPosition) { if (m_previousPosition == previousPosition) return; m_previousPosition = previousPosition; emit previousPositionChanged(previousPosition); } void Widget::on_btn_add_clicked() { /* Код из предыдущего урока */ } void Widget::mousePressEvent(QMouseEvent *event) { // При клике левой кнопкой мыши if (event->button() == Qt::LeftButton ) { // Определяем, в какой области произошёл клик m_leftMouseButtonPressed = checkResizableField(event); setPreviousPosition(event->pos()); // и устанавливаем позицию клика } return QWidget::mousePressEvent(event); } void Widget::mouseReleaseEvent(QMouseEvent *event) { // При отпускании левой кнопки мыши сбрасываем состояние клика if (event->button() == Qt::LeftButton) { m_leftMouseButtonPressed = None; } return QWidget::mouseReleaseEvent(event); } void Widget::mouseMoveEvent(QMouseEvent *event) { // При перемещении мыши, проверяем статус нажатия левой кнопки мыши switch (m_leftMouseButtonPressed) { case Move: { // При этом проверяем, не максимизировано ли окно if (isMaximized()) { // При перемещении из максимизированного состояния // Необходимо вернуть окно в нормальное состояние и установить стили кнопки // А также путём нехитрых вычислений пересчитать позицию окна, // чтобы оно оказалось под курсором ui->btn_maximize->setStyleSheet(StyleHelper::getMaximizeStyleSheet()); this->layout()->setMargin(9); auto part = event->screenPos().x() / width(); this->showNormal(); auto offsetX = width() * part; setGeometry(event->screenPos().x() - offsetX, 0, width(), height()); setPreviousPosition(QPoint(offsetX, event->y())); } else { // Если окно не максимизировано, то просто перемещаем его относительно // последней запомненной позиции, пока не отпустим кнопку мыши auto dx = event->x() - m_previousPosition.x(); auto dy = event->y() - m_previousPosition.y(); setGeometry(x() + dx, y() + dy, width(), height()); } break; } case Top: { // Для изменения размеров также проверяем на максимизацию // поскольку мы же не можем изменить размеры у максимизированного окна if (!isMaximized()) { auto dy = event->y() - m_previousPosition.y(); setGeometry(x(), y() + dy, width(), height() - dy); } break; } case Bottom: { if (!isMaximized()) { auto dy = event->y() - m_previousPosition.y(); setGeometry(x(), y(), width(), height() + dy); setPreviousPosition(event->pos()); } break; } case Left: { if (!isMaximized()) { auto dx = event->x() - m_previousPosition.x(); setGeometry(x() + dx, y(), width() - dx, height()); } break; } case Right: { if (!isMaximized()) { auto dx = event->x() - m_previousPosition.x(); setGeometry(x(), y(), width() + dx, height()); setPreviousPosition(event->pos()); } break; } default: // Если курсор перемещается по окну без зажатой кнопки, // то просто отслеживаем в какой области он находится // и изменяем его курсор checkResizableField(event); break; } return QWidget::mouseMoveEvent(event); } Widget::MouseType Widget::checkResizableField(QMouseEvent *event) { QPointF position = event->screenPos(); // Определяем позицию курсора на экране qreal x = this->x(); // координаты окна приложения, ... qreal y = this->y(); // ... то есть координату левого верхнего угла окна qreal width = this->width(); // А также ширину ... qreal height = this->height(); // ... и высоту окна // Определяем области, в которых может находиться курсор мыши // По ним будет определён статус клика QRectF rectTop(x + 9, y, width - 18, 7); QRectF rectBottom(x + 9, y + height - 7, width - 18, 7); QRectF rectLeft(x, y + 9, 7, height - 18); QRectF rectRight(x + width - 7, y + 9, 7, height - 18); QRectF rectInterface(x + 9, y + 9, width - 18, height - 18); // И в зависимости от области, в которой находится курсор // устанавливаем внешний вид курсора и возвращаем его статус if (rectTop.contains(position)) { setCursor(Qt::SizeVerCursor); return Top; } else if (rectBottom.contains(position)) { setCursor(Qt::SizeVerCursor); return Bottom; } else if (rectLeft.contains(position)) { setCursor(Qt::SizeHorCursor); return Left; } else if (rectRight.contains(position)) { setCursor(Qt::SizeHorCursor); return Right; } else if (rectInterface.contains(position)){ setCursor(QCursor()); return Move; } else { setCursor(QCursor()); return None; } }
Insgesamt
Somit ist es möglich, eine vollständig angepasste Anwendungsoberfläche in Qt zu erstellen. Es bleibt nur zu entscheiden, ob Sie es brauchen. Wenn Sie das System-Framing des Anwendungsfensters nicht besonders stört, sollten Sie sich wahrscheinlich nicht allzu sehr darum kümmern, aber wenn Sie trotzdem wirklich sogar die Schaltfläche zum Schließen des Anwendungsfensters mit Ihrem eigenen Erscheinungsbild gestalten möchten, dann beachten Sie bitte, dass die Der in diesem Artikel angegebene Code reicht eindeutig nicht aus, um alle Nuancen der Funktionsweise des Anwendungsfensters abzudecken. Beispielsweise gibt es keine gleichzeitige Größenänderung in Breite und Höhe. Und auch periodisch ändert sich das Aussehen des Cursors beim Bewegen nicht richtig. Obwohl ich denke, dass die Grundidee der Anpassung jetzt klar ist.
Benutzerdefinierten Qt AIMP Style Audio Player herunterladen
Как сделать так, что бы только когда верхнюю полосу зажимаешь, то перетаскивалось окно и что бы оно оставалось на месте?
В методах mousePressEvent, mouseMoveEvent и т.д. в этом же самом уроке показано, как определять области, в которых находится курсор мыши. Это реализовано для изменения размеров, в методе checkResizableField определяется область нахождения курсора. Аналогично можно реализовать область нахождения курсора в верхней полосе. И сделать перетаскивание только в этой области. Так что изучите внимательно урок и сделайте по аналогии.