Реализация двойной панели инструментов в QT

Ссылка на исходный код

Задача и требования

  • Создание двойного «тулбара» (панели инструментов), положение разделителя которого меняло бы соотношение полей сплиттераа.
    Иными словами разделитель должен перетягиваться мышкой.

  • Положение разделителя тулбаров так же должно зависеть от положения сплиттера.

  • При наведении курсора на разделитель тулбаров должен меняться тип курсора на горизонтальный QT::SizeHorCursor

  • При перетягивании разделителя курсор так же должен менять свой тип на горизонтальный

  • В целом данный тулбар должен соответствовать типичному представлению пользователя о данном типе тулбаров, это сделает интерфейс простым и понятным

  • Приложение должно давать возможность пользователю менять размер окна

Реализация

Первый шаг

Создаем Qt Widgets application. Основы cpp и h файлов, с которыми мы работаем, сгенерируются без нашего участия.

Object Inspector

Выполнение задачи необходимо начать с построения структуры интерфейса.

Для корректного отображения виджета toolbar необходима хотя бы одна иконка, расположенная на нем. Иконку можете выбрать на свой вкус.

Параметры виджетов

У виджетов MainWindow, centralwidget и у обоих тулбаров задаем параметр Mouse tracking, как true. Это позволит перехватывать события нажатия мыши, отпускания мыши и передвижения мыши над тулбарами.

У обоих списков, что лежат в сплиттере, задаем параметр минимальной ширины. В моем случае этот параметр равен 200 пикселям. У сплиттера задаем параметр childrenCollapsible, как false. Эти действия необходимы для того, чтобы не было возможности «схлопнуть» одну из секций сплиттера или увести тулбар за пределы экрана. Ниже покажу: как это связано с реализацией cpp файла.

У тулбаров нужно установить параметр movable, как false, чтобы убрать дефолтное задание размеров тулбаров пользователем.

MainWindow.h

private:     Ui::MainWindow *ui;     int timerId = 0;     bool toolbar_dragged = false;

Под Ui::MainWindow *ui создаем две переменные: timerId и toolbar_dragged.
timerId необходима для хранения времени таймера, которое мы будем использовать.
toolbar_dragged определяет: тянет ли пользователь за разделитель

public:     MainWindow(QWidget *parent = nullptr);     ~MainWindow();  private slots:     void on_splitter_splitterMoved(int pos, int index, bool windowResized  = true);     void mouseMoveEvent(QMouseEvent *event);     void mouseReleaseEvent(QMouseEvent *e);     void mousePressEvent(QMouseEvent *event);      void timerEvent(QTimerEvent *event);     void resizeEvent(QResizeEvent* event);

Задаем слоты и указываем входной параметр слота on_splitter_splitterMoved windowResized, как параметр, по умолчанию заданный, как true. В resizeEvent, событии изменения размера окна, будет вызываться on_splitter_splitterMoved при помощи emit. Позиция сплиттера при изменении окна меняется, потому и должна меняться позиция разделителя тулбаров. Но в случае изменения размера окна перетягивания разделителя не происходит, toolbar_dragged == false. Потому для случая, когда окно меняет размер, необходимо данное входное условие. Подробнее об этом в cpp файле.

MainWindow.cpp

В файл необходимо включить две библиотеки:

QMouseEvent нужна для обработки сообщений мыши
QWidget необходима для работы с виджетами приложения

#include <QMouseEvent> #include <QWidget>

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

centralWidget()->layout()->setContentsMargins убирает отступы от границ окна, созданные добавлением layout-а, как центрального виджета.
setSpacing(2) нужен потому, что иконки на этом тулбаре не передают событие передвижения мыши в MainWindow, Отступ в 2 пикселя вполне достаточен, чтобы избавиться от этой проблемы.

MainWindow::MainWindow(QWidget *parent)     : QMainWindow(parent)     , ui(new Ui::MainWindow) {     ui->setupUi(this);      centralWidget()->layout()->setContentsMargins(0, 0, 0, 0);      //this is nessesary     this->ui->toolBar_2->layout()->setSpacing(2); }

Функция on_splitter_splitterMoved задает фиксированный размер левого тулбара. Второй тулбар автоматически подстраивается под размер первого. Данный размер можно задавать многократно, что мы и делаем. Обычными resize и move методами это сделать нельзя.

В случае, если размер тулбара становится меньше, чем минимальный размер первого списка, или начинает прижимать второй список к левому краю окна так, что размер второго списка меньше минимального, изменение размера первого тулбара, а следовательно и положения разделителя между тулбарами — не происходит. Это необходимо, не смотря на то, что в QT дизайнере уже заданы минимальные параметры ширины для списков.

void MainWindow::on_splitter_splitterMoved(int pos, int /*index*/, bool windowResized) {     if (((pos>this->ui->listWidget->minimumSize().width()) &&         (pos<(this->width() - this->ui->listWidget_2->minimumSize().width()))) || windowResized)     {         this->ui->toolBar->setMaximumSize(pos, ui->toolBar->rect().height());         this->ui->toolBar->setMinimumSize(pos, ui->toolBar->rect().height());     } }

Функция mouseReleaseEvent вызывается, когда пользователь отпускает мышь. После этого нужно приветси курсор к типу Qt::ArrowCursor и задать соответствующую переменную toolbar_dragged, как false.

void MainWindow::mouseReleaseEvent(QMouseEvent* /*e*/) {     if (toolbar_dragged)     {         toolbar_dragged = false;         this->setCursor(Qt::ArrowCursor);     } }

mousePressEvent перехватывает событие нажатия мыши пользователем.

При срабатывании данной функции, если мышь находится на промежутке +-20 пикселей от разделителя сплиттера, на высоте тулбаров, toolbar_dragged становится true, а тип курсора меняется на соответствующий перетягиванию.

void MainWindow::mousePressEvent(QMouseEvent *event) {     QList<int> currentSizes = this->ui->splitter->sizes();     if ((this->ui->toolBar_2->underMouse()) &&         (event->buttons() == Qt::LeftButton) &&         (event->pos().x() < (currentSizes[0]+20)))     {         this->ui->toolBar_2->movableChanged(true);         toolbar_dragged = true;         this->setCursor(Qt::SizeHorCursor);     }     else if ((this->ui->toolBar->underMouse()) &&              (event->buttons() == Qt::LeftButton) &&              (event->pos().x() > (currentSizes[0]-20)))     {         this->ui->toolBar->movableChanged(true);         toolbar_dragged = true;         this->setCursor(Qt::SizeHorCursor);     } }

mouseMoveEvent вызывается при перемещении мыши. Если происходит перетягивание разделителя (toolbar_dragged), то вызывает функцию on_splitter_splitterMoved для изменения размера тулбара, предварительно поменяв размеры сплиттера.

В противном случае, если перетягивания не происходит, но мышь находится на промежутке -2 +5 пикселей от разделителя сплиттера, на высоте тулбаров, с отступами от горизонтальных границ в два пикселя, то тип курсора меняется на SizeHorCursor. Если эти отступы не сделать, то мышь не будет менять тип с SizeHorCursor на ArrowCursor, даже если задать Mouse tracking параметр, как true, всем другим виджетам.

void MainWindow::mouseMoveEvent(QMouseEvent* event) {     QList<int> currentSizes = this->ui->splitter->sizes();     if (toolbar_dragged)     {         QList<int> currentSizes = this->ui->splitter->sizes();         currentSizes[0] = event->pos().x();         currentSizes[1] = this->width() - event->pos().x();         this->ui->splitter->setSizes(currentSizes);         emit on_splitter_splitterMoved(event->pos().x(), 1, false);      }     else if ((event->pos().y() > (2+this->ui->toolBar->y())) &&              (event->pos().y() < (this->ui->toolBar->height()-2+this->ui->toolBar->y())) &&              (event->pos().x() < (currentSizes[0]+5)) &&              (event->pos().x() > (currentSizes[0]-10)))     {         this->setCursor(Qt::SizeHorCursor);     }     else     {         this->setCursor(Qt::ArrowCursor);     } }

resizeEvent — событие изменения размера окна. В этом событии нельзя вызвать on_splitter_splitterMoved, потому необходимо сделать таймер, который будет «выходить за пределы» resizeEvent, работать вне ее.

void MainWindow::resizeEvent(QResizeEvent* event) {          if (timerId)         {             killTimer(timerId);             timerId = 0;         }         // delay beetween ends of resize and your action         timerId = startTimer(1);      QMainWindow::resizeEvent(event); }

timerEvent меняет размеры тулбара через on_splitter_splitterMoved. При изменении размера окна, положение разделителя сплиттера определяется автоматически

void MainWindow::timerEvent(QTimerEvent *event) {     QList<int> currentSizes = this->ui->splitter->sizes();     this->ui->toolBar_2->adjustSize();     emit on_splitter_splitterMoved(currentSizes[0], 1,true);     killTimer(event->timerId());     timerId = 0; }

ссылка на оригинал статьи https://habr.com/ru/post/545824/

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *