The recommendation in my comment of yesterday:
The last resort might be to make a custom widget for the tab bar, and to use a QStackedWidget as container for the tab pages.
There are probably samples around. Instead of googling I didn't hesitate to add one on my own:
qIconBar.h:
#ifndef Q_ICON_BAR
#define Q_ICON_BAR
#include <vector>
#include <QIcon>
#include <QWidget>
class QIconBar: public QWidget {
Q_OBJECT
private:
Qt::Orientation _ori;
QSize _qSizeIcon = QSize(64, 64); // icon size
int _spc = 4; // size between
double _scl = 0.6; // scale factor for icons not under mouse
struct Tab {
QIcon qIcon;
QString qLabel;
};
std::vector<Tab> _tabs;
int _iTab = 0; // current tab
signals:
void currentChanged(int i);
void tabClicked(int i);
public:
/// constructor.
explicit QIconBar(
Qt::Orientation ori = Qt::Horizontal, QWidget* pQParent = nullptr);
/// destructor.
virtual ~QIconBar() = default;
// disabled:
QIconBar(const QIconBar&) = delete;
QIconBar& operator=(const QIconBar&) = delete;
public:
virtual QSize sizeHint() const;
QSize iconSize() const { return _qSizeIcon; }
void setIconSize(const QSize& qSizeIcon);
int addTab(const QIcon& qIcon, const QString& qText)
{
return insertTab(-1, qIcon, qText);
}
int insertTab(int i, const QIcon& qIcon, const QString& qText);
void removeTab(int i);
int tabAt(const QPoint& qPos) const;
QIcon tabIcon(int i) const
{
return (size_t)i < _tabs.size() ? _tabs[i].qIcon : QIcon();
}
void setTabIcon(int i, const QIcon& qIcon);
QString tabText(int i) const
{
return (size_t)i < _tabs.size() ? _tabs[i].qLabel : QString();
}
void setTabText(int i, const QString& qLabel);
int count() const { return (int)_tabs.size(); }
int currentIndex() const { return _iTab; }
void setCurrentIndex(int i);
protected:
virtual void paintEvent(QPaintEvent* pQEvent) override;
virtual void mouseMoveEvent(QMouseEvent* pQEvent) override;
virtual void mousePressEvent(QMouseEvent* pQEvent) override;
virtual void leaveEvent(QEvent* pQEvent) override;
};
#endif // Q_ICON_BAR
Concerning the API, I (shamelessly) borrowed from QTabBar.
qIconBar.cc:
#include <QMouseEvent>
#include <QPainter>
#include "qIconBar.h"
QIconBar::QIconBar(Qt::Orientation ori, QWidget* pQParent):
QWidget(pQParent),
_ori(ori)
{
setMouseTracking(true);
}
QSize QIconBar::sizeHint() const
{
const int n = (int)_tabs.size();
const int spc = std::max(n - 1, 0) * _spc;
return _ori == Qt::Vertical
? QSize(_qSizeIcon.width(), _qSizeIcon.height() * n + spc)
: QSize(_qSizeIcon.width() * n + spc, _qSizeIcon.height());
}
int QIconBar::insertTab(int i, const QIcon& qIcon, const QString& qText)
{
if ((size_t)i > _tabs.size()) i = (int)_tabs.size();
_tabs.insert(_tabs.begin() + i, { qIcon, qText });
update();
if (i <= _iTab && _iTab + 1 < (int)_tabs.size()) {
setCurrentIndex(_iTab + 1);
}
return i;
}
void QIconBar::removeTab(int i)
{
if ((size_t)i >= _tabs.size()) return;
_tabs.erase(_tabs.begin() + i);
update();
if ((size_t)_iTab >= _tabs.size()) {
setCurrentIndex(0);
}
}
int QIconBar::tabAt(const QPoint& qPos) const
{
if (!QRect(QPoint(), size()).contains(qPos)) return -1;
return _ori == Qt::Vertical
? qPos.y() % (_qSizeIcon.height() + _spc) < _qSizeIcon.height()
? qPos.y() / (_qSizeIcon.height() + _spc)
: -1
: qPos.x() % (_qSizeIcon.width() + _spc) < _qSizeIcon.width()
? qPos.x() / (_qSizeIcon.width() + _spc)
: -1;
}
void QIconBar::setCurrentIndex(int i)
{
_iTab = i;
emit currentChanged(_iTab);
update();
}
void QIconBar::paintEvent(QPaintEvent* pQEvent)
{
const QPoint qPosMouse = mapFromGlobal(QCursor::pos());
const int iTabMouse = tabAt(qPosMouse);
QPainter qPainter(this);
for (int i = 0, n = (int)_tabs.size(); i < n; ++i) {
const Tab& tab = _tabs[i];
const QPoint qPos(
(_ori == Qt::Horizontal) * i * (_qSizeIcon.width() + _spc)
+ _qSizeIcon.width() / 2,
(_ori == Qt::Vertical) * i *(_qSizeIcon.height() + _spc)
+ _qSizeIcon.height() / 2);
if (i == _iTab) {
QRect qRect(QPoint(), _qSizeIcon);
qRect.moveCenter(qPos);
qPainter.fillRect(qRect, palette().color(QPalette::Base));
}
if (!tab.qIcon.isNull()) {
const double scale = iTabMouse == i ? 1.0 : _scl;
const QSize qSizeIcon = scale * _qSizeIcon;
const QPixmap qPixmap = tab.qIcon.pixmap(qSizeIcon);
QRect qRect(QPoint(), qSizeIcon);
qRect.moveCenter(qPos);
qPainter.drawPixmap(qRect, qPixmap);
}
if (i == iTabMouse) {
QFont qFont = font();
qFont.setBold(true);
qPainter.setFont(qFont);
QRect qRect(QPoint(), _qSizeIcon);
qRect.moveCenter(qPos);
qPainter.drawText(qRect, Qt::AlignHCenter | Qt::AlignBottom,
tab.qLabel);
}
}
}
void QIconBar::mouseMoveEvent(QMouseEvent* pQEvent)
{
update();
QWidget::mouseMoveEvent(pQEvent);
}
void QIconBar::mousePressEvent(QMouseEvent* pQEvent)
{
if (pQEvent->button() == Qt::LeftButton) {
const int iTab = tabAt(pQEvent->pos());
if ((size_t)iTab < _tabs.size()) {
setCurrentIndex(iTab);
emit tabClicked(iTab);
return;
}
}
QWidget::mousePressEvent(pQEvent);
}
void QIconBar::leaveEvent(QEvent* pQEvent)
{
update();
QWidget::leaveEvent(pQEvent);
}
A sample application testQIconBar.cc:
#include <QtWidgets>
#include "qIconBar.h"
const struct Item {
const char* filePathIcon;
const char* label;
} items[] = {
{ "icons/document-open.svg", "File" },
{ "icons/document-properties.svg", "Edit" },
{ "icons/edit-find.svg", "Find" },
{ "icons/help-help.svg", "Help" }
};
const size_t nItems = std::size(items);
int main(int argc, char** argv)
{
QApplication app(argc, argv);
// setup GUI
QWidget qWinMain;
qWinMain.setWindowTitle("QIconBar Test");
qWinMain.resize(640, 480);
QHBoxLayout qHBox;
QIconBar qIconBar(Qt::Vertical);
qHBox.addWidget(&qIconBar);
QStackedLayout qStack;
qHBox.addLayout(&qStack, 1);
qWinMain.setLayout(&qHBox);
qWinMain.show();
// populate icon bar and stack
QFont qFont = qWinMain.font();
qFont.setPixelSize(30);
for (const Item& item : items) {
qIconBar.addTab(
QIcon(item.filePathIcon), QString::fromUtf8(item.label));
const QString qText
= QString("This is the page for item %1.").arg(item.label);
QLabel* pQLbl = new QLabel(qText);
pQLbl->setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
pQLbl->setFont(qFont);
pQLbl->setAlignment(Qt::AlignCenter);
qStack.addWidget(pQLbl);
}
qStack.setCurrentIndex(qIconBar.currentIndex());
// install signal handlers
QObject::connect(&qIconBar, &QIconBar::currentChanged,
&qStack, &QStackedLayout::setCurrentIndex);
// runtime loop
return app.exec();
}
Additionally, I used the following CMakeLists.txt:
project(QIconBar)
cmake_minimum_required(VERSION 3.10.0)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(Qt5Widgets CONFIG REQUIRED)
include_directories("${CMAKE_SOURCE_DIR}")
qt5_wrap_cpp(moc_sources QIconBar.h
TARGET testQIconBar)
add_executable(testQIconBar
testQIconBar.cc qIconBar.cc qIconBar.h
${moc_sources})
target_link_libraries(testQIconBar Qt5::Widgets)
…and compiled this in Visual Studio 2019.
Output:

According to feedback, I tested my MCVE concerning transparency of the widgets — i.e. with a background image in the main window:

The QIconBar doesn't clear it's background — the contents is just drawn over the background of the parent window. (It's the same with the QLabels I used in the QStackedLayout.)