Задача и требования
-
Создание двойного "тулбара" (панели инструментов), положение разделителя которого меняло бы соотношение полей сплиттераа.
Иными словами разделитель должен перетягиваться мышкой. -
Положение разделителя тулбаров так же должно зависеть от положения сплиттера.
-
При наведении курсора на разделитель тулбаров должен меняться тип курсора на горизонтальный 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;}