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