Qt5 QDockWidget with Fixed Height CentralWidget - c++

I'm trying to use dock widgets on the main window class in Qt5. However, when I set the central widget to have a fixed height Qt has trouble docking the windows to the top or bottom. Basically, it looks like there is some "padding" or "margins" above and below the central widget. If I set an auto height on the widget, the docking works fine all they way edge-to-edge (top/bottom). How can I either remove the margins or enable the docking function while using a fixed height central widget?
See screenshots for example.
Dock Right w/ Auto Height (No Margins on Central Widget)
Dock Bottom w/ Auto Height (No Margins on Central Widget)
Dock Bottom w/ Fixed Height (Margins/Padding--Grey areas, won't dock)
Here is the code if that helps.
Header:
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QString);
~MainWindow();
private:
void createDockWindows();
QListWidget *m_dock_list;
QString m_directory;
QWidget *m_mainWidget;
};
Class definition:
MainWindow::MainWindow(QString program)
: m_directory(".")
{
m_mainWidget = new QWidget;
m_mainWidget->setFixedHeight(156);
m_mainWidget->setStyleSheet("background-color: blue;");
createDockWindows();
// set central widget and default size
setCentralWidget(m_mainWidget);
}
// dock functions
void MainWindow::createDockWindows()
{
QDockWidget *dock = new QDockWidget(tr("Dock List"), this);
dock->setAllowedAreas(Qt::LeftDockWidgetArea |
Qt::RightDockWidgetArea |
Qt::BottomDockWidgetArea);
m_dock_list = new QListWidget(dock);
m_dock_list->addItems(QStringList()
<< "item 1"
<< "item 2"
<< "item 3"
<< "item 4");
dock->setWidget(m_dock_list);
addDockWidget(Qt::RightDockWidgetArea, dock);
}

In case anyone else tries to figure out this behavior, here is what I've found. The top and bottom margins are just Qt trying to place the fixed size widget into a space larger than the widget (obvious). However, the restricted docking ability results from the docking widget having a minimum vertical size. Thus, when window height < central widget height + min(docking widget height) Qt does not allow the dock function. Once the threshold window height is reached (by user resize) then Qt allows the dock function.
I would prefer if the docking feature occurred regardless of window height with an automatic window resize to accommodate the docking window.
Solution using signals
Arguably a bit hacky, and may be hard to maintain if you have a complex layout. But for my use case, this is workable.
Don't enforce the size on the center widget, let it auto-fill the main window.
When the dock changes location it emits the dockLocationChanged. We can auto-size the main window if the dockable window is moved into target location. For my case this is the bottom.
Code:
void MainWindow::createDockWindows()
{
...
QObject::connect(dock, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)),
this, SLOT(s_dock_window_resize(Qt::DockWidgetArea)));
}
// Autosize window slot on dock location change.
void MainWindow::s_dock_window_resize(Qt::DockWidgetArea area)
{
if (area == Qt::DockWidgetArea::BottomDockWidgetArea) {
// m_mainWidget_min_height is the height we would have enforced
// on the widget. Now we set it somewhere else and check it here.
int min_height = m_dock_list->height() + m_mainWidget_min_height;
if (min_height > MainWindow::height()) {
MainWindow::resize(MainWindow::width(), min_height);
}
}
}

Related

qt remove layouts code

I have less than 1 day of experience in QT (that's why I do not know much of it) I have a window full of information (labels, text, buttons, etc) organized by layouts.
I need that after I press one button, all of the components in a window be hidden (which I already did) except for one label which should increase to barely the size of the whole window
Despite I tried modifying the "geometry" attribute (with code) the hidden layouts do not let the label to be increased. I thought also of using the option of layout breaking, but the label losses its dynamism. Could anyone please recommend me anything to do? Thanks.
Has anyone done something like this before. Thanks.
I once provided an answer to SO: Qt - How to create Image that scale with window, and keeps aspect ratio?. The actual intention was to scale an image in a QLabel with original aspect ratio to consume maximum available size.
However, I got the feedback that the suggested solution would not work properly when my Label would be used in a QGridLayout. (This sounds very similar to the issue of the OP.) Hence, I modified the sample to reproduce the issue and fiddled a little bit around with. For me, it seems that resize events of the main window are processed in the QGridLayout but affect layouted image label only partially. (Shrinking is applied but growing not.) Fortunately, I found a very simple work-around: Setting a non-empty frame to the QLabel solved the problem. I had a look into the source code on woboq.org. I hoped to get a hint what the changed frame style would activate (to apply this as fix for my resize issue). Finally, I was not patient enough and put it aside.
Beside of this QLabel in a QGridLayout resize issue, changing the visibility of widgets should cause a proper re-layout. I would prefer show/hide (instead of delete and re-new) as this is surely easier to implement, more efficient, and less error-prone.
I took the old sample code and added a tool button which can be used to toggle the visibilty of some of the layouted widgets:
// Qt header:
#include <QtWidgets>
class LabelImage: public QLabel {
private:
QPixmap _qPixmap, _qPixmapScaled;
public:
void setPixmap(const QPixmap &qPixmap) { setPixmap(qPixmap, size()); }
protected:
virtual void resizeEvent(QResizeEvent *pQEvent);
private:
void setPixmap(const QPixmap &qPixmap, const QSize &size);
};
void LabelImage::resizeEvent(QResizeEvent *pQEvent)
{
QLabel::resizeEvent(pQEvent);
setPixmap(_qPixmap, pQEvent->size());
}
void LabelImage::setPixmap(const QPixmap &qPixmap, const QSize &size)
{
_qPixmap = qPixmap;
_qPixmapScaled = _qPixmap.scaled(size, Qt::KeepAspectRatio);
QLabel::setPixmap(_qPixmapScaled);
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
// main application
QApplication app(argc, argv);
// setup GUI
QMainWindow qWin;
QToolBar qToolbar;
QAction qCmdTgl(QString::fromUtf8("Decoration"));
qCmdTgl.setCheckable(true);
qCmdTgl.setChecked(true);
qToolbar.addAction(&qCmdTgl);
qWin.addToolBar(&qToolbar);
QGroupBox qBox;
QGridLayout qGrid;
// a macro for the keyboard lazy:
#define Q_LBL_WITH_POS(ROW, COL) \
QLabel qLbl##ROW##COL(QString::fromLatin1(#ROW", "#COL)); \
/*qLbl##ROW##COL.setFrameStyle(QLabel::Raised | QLabel::Box);*/ \
qGrid.addWidget(&qLbl##ROW##COL, ROW, COL, Qt::AlignCenter)
Q_LBL_WITH_POS(0, 0);
Q_LBL_WITH_POS(0, 1);
Q_LBL_WITH_POS(0, 2);
Q_LBL_WITH_POS(1, 0);
LabelImage qLblImg;
qLblImg.setFrameStyle(QLabel::Raised | QLabel::Box);
qLblImg.setAlignment(Qt::AlignCenter);
//qLblImg.setMinimumSize(QSize(1, 1)); // seems to be not necessary
qLblImg.setSizePolicy(
QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored));
QPixmap qPM;
if (qPM.load("cats.jpg")) qLblImg.setPixmap(qPM);
else {
qLblImg.setText(
QString::fromLatin1("Sorry. Cannot find file 'cats.jpg'."));
}
qGrid.addWidget(&qLblImg, 1, 1, Qt::AlignCenter);
qGrid.setRowStretch(1, 1); // tell QGridLayout to stretch this cell...
qGrid.setColumnStretch(1, 1); // ...prior to other cells (w/ stretch 0)
Q_LBL_WITH_POS(1, 2);
Q_LBL_WITH_POS(2, 0);
Q_LBL_WITH_POS(2, 1);
Q_LBL_WITH_POS(2, 2);
qBox.setLayout(&qGrid);
qWin.setCentralWidget(&qBox);
qWin.show();
// install signal handlers
QObject::connect(&qCmdTgl, &QAction::triggered,
[&](bool on) {
qLbl00.setVisible(on); qLbl01.setVisible(on); qLbl02.setVisible(on);
qLbl10.setVisible(on); qLbl12.setVisible(on);
qLbl20.setVisible(on); qLbl21.setVisible(on); qLbl22.setVisible(on);
});
// run application
return app.exec();
}
I compiled and tested in VS2013 on Windows 10:
After toggling the Decoration tool button:
Note:
Out of curiosity, I commented the line which changes the frame style
qLblImg.setFrameStyle(QLabel::Raised | QLabel::Box);
and again, resizing of image didn't work properly anymore.
You can remove and hide widgets inside a layout using QLayout::removeWidget(*widget); but you do not need to actually remove it. You should use QWidget::hide() for the content to disappear and for the video label's cell to be able to take that space. I think you need to pay attention to the video label's size policy if it does not increase in size. Assuming you have a QGridLayout like so:
label1 label2 label3
label4 videoLabel label5
button1 button2 button3
And let's say, when you click button3, label1, label2 and label4 should all disappear and videoLabel takes the newly created space. I would group the widgets label1, label2, label4 and videoLabel into a single widget having its own sub-layout. I use QSizePolicy::Expaning to make sure my videoLabel takes the maximum space possible. Here is the implementation:
Widget::Widget(QWidget *parent) :
QWidget(parent)
{
setStyleSheet("QLabel{font-size:20px;}");
fullScreen = false; //current fullscreen state
//main grid layout
baseLayout = new QGridLayout(this);
baseLayout->setMargin(0);
baseLayout->setAlignment(Qt::AlignCenter);
setLayout(baseLayout);
//widget container for label1, label2, label4, videolabel
groupWidget = new QWidget();
//sub-layout inside the group layout
subLayout = new QGridLayout();
subLayout->setAlignment(Qt::AlignCenter);
subLayout->setMargin(0);
groupWidget->setLayout(subLayout);
//label and button instantializing. I set background colors to show their sizes
label1 = new QLabel("Label1");
label1->setStyleSheet("background-color:white;");
label2 = new QLabel("Label2");
label2->setStyleSheet("background-color:orange;");
label3 = new QLabel("Label3");
label4 = new QLabel("Label4");
label4->setStyleSheet("background-color:blue;color:white;");
label5 = new QLabel("Label5");
videoLabel = new QLabel("videoLabel");
videoLabel->setStyleSheet("background-color:red;");
videoLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
button1 = new QPushButton("button1");
button2 = new QPushButton("button2");
button3 = new QPushButton("button3");
//the grouped widget spans for 2 rows and columns, hence "2,2"
baseLayout->addWidget(groupWidget, 0,0,2,2);
subLayout->addWidget(label1, 0, 0);
subLayout->addWidget(label2, 0, 1);
subLayout->addWidget(label4, 1, 0);
subLayout->addWidget(videoLabel, 1, 1);
//adding rest of the labels and buttons to the base grid
baseLayout->addWidget(label3, 0, 2);
baseLayout->addWidget(label5, 1, 2);
baseLayout->addWidget(button1, 2, 0);
baseLayout->addWidget(button2, 2, 1);
baseLayout->addWidget(button3, 2, 2);
//button3 toggles fullscreen
connect(button3, SIGNAL(clicked(bool)), this, SLOT(onButton3Clicked(bool)));
}
//slot for button3 click
void Widget::onButton3Clicked(bool)
{
if (!fullScreen){
//removing widget from layouts is not really necessary. Make sure to hide
/*subLayout->removeWidget(label1);
subLayout->removeWidget(label2);
subLayout->removeWidget(label4);*/
label1->hide();
label2->hide();
label4->hide();
fullScreen = true;
}
else{
label1->show();
label2->show();
label4->show();
/*subLayout->addWidget(label1, 0, 0);
subLayout->addWidget(label2, 0, 1);
subLayout->addWidget(label4, 1, 0);*/
fullScreen = false;
}
}
I got the following results for this:
Keep in mind there are other approaches to this question. This one need not be necessarily the best when it comes to memory, but it is quite easy to follow.

Hide Icon from the label of QComboBox

I am trying to implement a QComboBox, which holds QIcon and QString, like this:
QComboBox.addItem(icon, label);
I want the icons to be visible in the drop-down list, but not in the toolbar. Only the string should be visible after item selection.
Is there an easy way to do this?
To solve this problem, it is enough to override the paintEvent method, taking the default implementation from the sources. Before drawing control QStyle::CE_ComboBoxLabel, it is necessary to set an invalid value of the QStyleOptionComboBox.currentIcon
This works well if the combobox is not editable, otherwise there is an empty space on the left side intended for drawing the icon. Looking at the source, I found out that the combobox changes the geometry of the QLineEdit. If the current element has an icon then geometrically QLineEdit will shift to the right. In order to prevent this in the same paintEvent, it is necessary to force the geometry of QLineEdit without taking into account the icon.
The following code takes this into account and works well in both modes:
class ComboBox : public QComboBox {
public:
ComboBox(QWidget *parent = nullptr)
: QComboBox(parent) { }
protected:
void paintEvent(QPaintEvent *) override {
QStylePainter painter(this);
painter.setPen(palette().color(QPalette::Text));
// draw the combobox frame, focusrect and selected etc.
QStyleOptionComboBox opt;
initStyleOption(&opt);
painter.drawComplexControl(QStyle::CC_ComboBox, opt);
// draw the icon and text
opt.currentIcon = QIcon();
if (auto le = lineEdit()) {
le->setGeometry(style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField, this));
} else {
painter.drawControl(QStyle::CE_ComboBoxLabel, opt);
}
}
};
The best way is to set a delegate and to draw the items yourself.
Then you can choose when or not to draw the icon ( decorationRole ), you can choose not to draw the icon for the index which is the current index.
I could find a quick example on how to use a delegate on combobox:
http://programmingexamples.net/wiki/Qt/Delegates/ComboBoxDelegate
But I am afraid it is not the most easist of the ways.
Good luck!

Qt C++ window title bar is blocked

I'm very new to Qt and trying to create application, that includes main window, QDockWidget and a button.
Suppose my main window has 1280 x 720 resolution. Then I want to implement QDockWidget that pop up from the left side, width of dockWidth and height of 720 without windowTitleBar. The button has size of (buttonWidth, 720). At first its hidden, and only the button is present, when we click the button dock pops up, button changes position to the right edge of dock.
Here is my code:
window::window(unsigned int h, unsigned int v, QWidget *parent) {
this->setFixedSize(h, v);
ui.setupUi(this);
createDockWindow();
}
void window::createDockWindow() {
dock = new QDockWidget(this);
dock->setTitleBarWidget(new QMainWindow());
dock->setGeometry(QRect(this->rect().topLeft(),
QSize(dockWidth, this->height())));
dock->setFloating(true);
dock->hide();
path_button = new QPushButton(">", this);
path_button->setGeometry(QRect(this->rect().topLeft(),
QSize(buttonWidth, this->height())));
connect(path_button, SIGNAL (released()), this, SLOT (showDock()));
}
void rubrick::showDock() {
if(dock->isHidden()){
dock->show();
path_button->setGeometry(QRect(dock->rect().topRight(),
QSize(buttonWidth, this->height())));
} else {
dock->hide();
path_button->setGeometry(QRect(dock->rect().topLeft(),
QSize(buttonWidth, this->height())));
}
}
So button works perfectly, at first my app looks like that screenshot:
But when the dock shows, it blocks app window title bar, like that: screenshot
I figured, this->rect().topLeft() returns top left of screen, but doesn't take into consideration window Title Bar, I tried to get menuBar height, but it return 30, and I found out that if I move left top by (0, 45) with 0 being width and 45 being height, the dock would be perfectly in place.
What am I doing wrong and how to fix that problem?
The method you're probably looking for is QWidget::frameGeometry, which returns the geometry of the window with the frame included. The rect method returns only the internal area. If you look at QWidget::rect in Qt Assistant, you'll find a link to a "Window Geometry" description that explains all of these interactions reasonably well.

QScrollArea issue with vertical scroll

I have read few pages about QScrollArea, and I couldn't solve my issue. I have the next code:
QDialog *window = new QDialog;
window->resize(300, 300);
for(int i = 0; i < 50; ++i)
{
QLabel *label = new QLabel(window);
label->move(10, i * 15);
label->setText("Text");
}
QScrollArea *area = new QScrollArea;
area->setWidget(window);
area->show();
It seems that the vertical scroll from QScrollArea doesn't appear. I can't use QVBoxLayout because on my QDialog I don't have only QLabels aligned vertically ( this is just a simplified version of my QDialog ).
The QScrollArea won't get scrollbars unless the QWidget inside grows. Just moving some QLabels out of bounds doesn't make the parent QWidget grow, especially without a QLayout.
But if you manually resize them so that the QWidget is bigger than the QScrollAreay, you'll get scroll bars as expected :
QDialog *window = new QDialog;
window->resize(300, 600); //< 600px high widget
for(int i = 0; i < 50; ++i)
{
QLabel *label = new QLabel(window);
label->move(10, i * 15);
label->setText("Text");
}
QScrollArea *area = new QScrollArea;
area->setWidget(window);
area->resize(300,300); //< Inside a 300px high scrollarea, expect scrollbars!
area->show();
Note that now you will have both scroll bars, because the vertical scroll bar means there isn't enough room for our 300px width anymore. You can forcefully hide the horizontal scroll bar with area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
You could also always force a vertical scroll bar to appear with area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);, but this by itself wouldn't solve your problem. You'd still have a 300px widget inside a 300px area, and the scrollbar wouldn't have any space to move.
Making sure the QWidget is big enough for everything it contains is what you'll want to do, the QScrollArea will adapt. Usually we use layouts for that, but you can make it work by hand as well.

Resizing parent window after child sizes change

I have been messing with this problem for hours, and decided it's time to ask SO :)
I have a Qt program that rotates an image and then updates the size of the widget. Here is the code I'm using to do this currently.
void VideoSubWindow::showFrame(const QImage& frame)
{
QPixmap pixmap = QPixmap::fromImage(frame);
ui->videoFrameLabel->setPixmap(pixmap);
resizeWidgets(pixmap.size());
}
void VideoSubWindow::resizeWidgets(const QSize &size)
{
if(frameSize != size)
{
frameSize = size;
ui->videoFrameLabel->setFixedSize(size);
ui->scrollArea->setMinimumSize(size.width() + 2, size.height() + 2);
}
}
The widgets are structured as follows:
VideoSubWindow (QMainWindow)
-> centralWidget (QWidget) (Vertical layout is set on this)
-> scrollArea (QScrollArea)
-> videoFrameLabel (QLabel)
-> statusBar (QStatusBar)
-> menuBar (QMenuBar)
When the code above is executed, like rotating the image 90 degrees, the image will be rotated, but the window doesn't resize to fit the new pixmap size. I have tried to call adjustSize() and updateGeometry() on SubWindow and centralWidget, but those seem to have zero effect. But, if I manually resize the window with my mouse, the window snaps to the minimum size that was set for the scrollArea, so that seems to be taking effect.
Does anyone have experience with this? Thanks!
Try with the resize(...) function : Qt documentation
adjustSize() used sizeHint() function, so calling adjustSize() on SubWindow and centralWidget cannot have any effet