Qt Custom widget with overlapping subwidgets - c++

I'm trying to make a custom widget in Qt5 which is sort of like a QProgressBar but with 3 sliders and user movable: (slightly broken JS implementation: codepen, gist)
I'm having issues working out quite how to do this. My attempts have failed to either render all the parts of the widget correctly (making it very difficult to select and move different parts of it) or not working correctly with a VBoxLayout (doesn't expand to fit the horizontal space)
My latest attempt (you should be able to get the general idea from the constructor, nothing else was implemented)
UTrackSlider::UTrackSlider(QWidget *parent) : QWidget(parent)
{
this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
// CIRCLE_HEIGHT = 10
// BACKGROUND_HEIGHT = 30
/// #todo css should be in global stylesheet
background = new QLabel(this);
background->setMinimumHeight(this->BACKGROUND_HEIGHT);
background->setStyleSheet("QLabel{ border: 1px solid gray; background-color:white; border-radius:2px}");
background->setAttribute(Qt::WA_DeleteOnClose);
cue = new QLabel(this);
cue->setFixedSize(QSize(this->CIRCLE_HEIGHT, this->CIRCLE_HEIGHT));
cue->setStyleSheet("QLabel:hover{ border: 2px solid #d9534f; border-radius:5px}");
cue->setAttribute(Qt::WA_DeleteOnClose);
duration = new QLabel(this);
duration->setFixedSize(3, this->BACKGROUND_HEIGHT);
duration->setStyleSheet("QLabel{border: 3px solid #2376bb} QLabel:hover{border: 5px solid #2376bb}");
duration->setAttribute(Qt::WA_DeleteOnClose);
intro = new QLabel(this);
intro->setFixedSize(QSize(this->CIRCLE_HEIGHT, this->CIRCLE_HEIGHT));
intro->setStyleSheet("QLabel:hover{ border: 2px solid #5bc85c; border-radius:5px}");
intro->setAttribute(Qt::WA_DeleteOnClose);
QGridLayout *mainLayout = new QGridLayout(this);
mainLayout->addWidget(cue, 5, 0);
mainLayout->addWidget(background, 0, this->CIRCLE_HEIGHT);
mainLayout->addWidget(duration, 2, this->CIRCLE_HEIGHT);
mainLayout->addWidget(intro, 5, this->CIRCLE_HEIGHT + this->BACKGROUND_HEIGHT);
this->setLayout(mainLayout);
}
Basically, any pointers about how I should structure this composite widget such that it works in all these conditions?
EDIT: After discussing the issue with some people on #qt , i've come to the conclusion that I must override paintEvent. But this also means overriding some other functions to do the onClick and dragging effects, and I've got no idea where to start

You're right, you have to re-implement
1.void paintEvent(QPaintEvent *)
2.void mouseMoveEvent(QMouseEvent *)
3.void mousePressEvent(QMouseEvent *)
4.void mouseReleaseEvent(QMouseEvent *)
of QWidget.
In order to handle the mouse event correctly, you may use QCoreApplication::sendEvent(QObject *, QEvent *) to pass the event to the target widget.
Here's an example:
https://github.com/Serge45/MultiSlider
In this example, I create three widgets(ColorSlider) in a container widget, and then use a linked list to propagate the mouse event correctly.

Related

Set color of tabified QDockWidget tabs

I am creating a QDockWidget, and I want it's tabs(when tabified) or title bar(when not tabified) to flash between two colors.
I am currently doing the following, but only the body flashes between two colors:
//Setup the array of styles for the flash:
std::array<const char*, 2> flashStyles = {
" background-color: lightblue; color: black;",
" background-color: orange; color: black; "
};
//Setup the timer and kick it off:
connect(m_flashTimer, &QTimer::timeout, [=]()
{
//Perform the flash by alternating the background:
setStyleSheet(flashStyles[
m_pass == 0 ? m_pass++ : m_pass--
]);
update();
});
I can't seem to find any properties that would allow me to change the tab color / title bar color. Do I need to set the tab color properties somewhere else?
You need to find out which Widget is used internally by QDockWidget to display tabs. Then use the appropriate styles with the correct selector. Probably you need to select a QTabBar which is a descendant of QDockWidget. This could look like:
QDockWidget QTabBar {}
Maybe these links can also help you:
http://doc.qt.io/archives/qt-4.8/stylesheet-syntax.html
http://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qdockwidget
http://doc.qt.io/archives/qt-4.8/stylesheet-reference.html

How do I display a border around a QWebEngineView?

I have a QGraphicsWidget which I am using to paint and display a number of items including a QWebEngineView using the QGraphicsProxyWidget. I am able to load web content into QWebEngineView, but I would like to make the view contain a border. I have used "setStyleSheet" to try to add a border, but this does not appear to work. The following code is in the constructor of my QGraphicsWidget class to add the QWebEngineView:
QWebEngineView * view = new QWebEngineView();
view->setFixedWidth(700);
view->setFixedHeight(200);
view->setStyleSheet("border: 10px border-color: black");
view->load(QUrl("qrc:/myresources/guidetext.html"));
QGraphicsProxyWidget * proxyView = new QGraphicsProxyWidget(this);
proxyView->setWidget(view);
This is how it currently looks:
How I would like it to look like:
Problem
Normally, setting the Qt::WA_StyledBackground attribute, then the correct stylesheet and the content margins like that:
view->setAttribute(Qt::WA_StyledBackground);
view->setStyleSheet("border: 1px solid black;");
view->setContentsMargins(1, 1, 1, 1);
should do the trick.
However, it seems that QWebEngineView does not respect the content margins:
Workaround
I would suggest you to make QWebEngineView child of another QWidget and style the parent widget instead.
Example
Here is an example I have prepared for you of how to change your code in order to implement the proposed solution:
auto *proxyView = new QGraphicsProxyWidget();
auto *widget = new QWidget();
auto *view = new QWebEngineView(widget);
auto *l = new QVBoxLayout(widget);
l->addWidget(view);
l->setContentsMargins(1, 1, 1, 1);
widget->setAttribute(Qt::WA_StyledBackground);
widget->setStyleSheet("border: 1px solid black;");
widget->setFixedWidth(700);
widget->setFixedHeight(200);
view->load(QUrl("qrc:/myresources/guidetext.html"));
proxyView->setWidget(widget);
Result
Here is the result when loading Google:

How can I resize QMessageBox?

I have a QMessageBox which I'd like it to be bigger. It's a simple QMessageBox with two standard buttons, Ok and Cancel. The problem is that it is very small for my application's purposes. Code shows like this:
QMessageBox msg;
msg.setText("Whatever");
msg.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
msg.setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
int ret = msg.exec();
switch (ret) {
case QMessageBox::Ok:
ui->textEdit->clear();
break;
case QMessageBox::Cancel:
break;}
I tried several ways to increase the size:
msg.setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
msg.setSizePolicy(QSizePolicy::Maximum,QSizePolicy::Maximum);
msg.setFixedHeight(600);
msg.setFixedWidth(600);
I even cleared and rebuilt, and it compiles everything but nothing take effect...
Do you have any idea on how to set QMessageBox size "by hand"? Thanks.
You can edit the css of the label:
msg.setStyleSheet("QLabel{min-width: 700px;}");
You can similarly edit the css of the buttons to add a margin or make them bigger.
For example:
msg.setStyleSheet("QLabel{min-width:500 px; font-size: 24px;} QPushButton{ width:250px; font-size: 18px; }");
There is also a trick mentioned:
QSpacerItem* horizontalSpacer = new QSpacerItem(800, 0, QSizePolicy::Minimum, QSizePolicy::Expanding);
QGridLayout* layout = (QGridLayout*)msg.layout();
layout->addItem(horizontalSpacer, layout->rowCount(), 0, 1, layout->columnCount());
But this doesn't seem to work for everyone.
coyotte508's answer caused my layout to be horribly off center and at different widths it was cut off. In searching around further I found this thread which explains a better solution.
In essence the layout of a messagebox is a grid, so you can add a SpacerItem to it to control the width. Here's the c++ code sample from that link:
QMessageBox msgBox;
QSpacerItem* horizontalSpacer = new QSpacerItem(500, 0, QSizePolicy::Minimum, QSizePolicy::Expanding);
msgBox.setText( "SomText" );
QGridLayout* layout = (QGridLayout*)msgBox.layout();
layout->addItem(horizontalSpacer, layout->rowCount(), 0, 1, layout->columnCount());
msgBox.exec();
You can subclass QMessageBox and reimplement resize event handler as following:
void MyMessageBox::resizeEvent(QResizeEvent *Event)
{
QMessageBox::resizeEvent(Event);
this->setFixedWidth(myFixedWidth);
this->setFixedHeight(myFixedHeight);
}
I wanted my QMessageBox width to adapt in proportion to the length of the text content with a certain amount of buffer to avoid line wrap. After surveying numerous forums and threads including this one, I came up with:
int x_offset = (2.0 * MainWindow::geometry().x());
int y_offset = (0.5 * MainWindow::geometry().y());
msgBox.setText(vers_msg.data());
QSpacerItem* horizontalSpacer = new QSpacerItem
(8 * vers_msg.size(), 0,
QSizePolicy::Minimum, QSizePolicy::Expanding);
QGridLayout* layout = (QGridLayout*)msgBox.layout();
layout->addItem(horizontalSpacer, layout->rowCount(),
0, 1, layout->columnCount());
msgBox.setGeometry(
MainWindow::geometry().x() + x_offset,
MainWindow::geometry().y() + y_offset,
msgBox.geometry().width(),
msgBox.geometry().height());
Adjust the hard numbers in x_offset, y_offset and horizontalSpacer to suit your situation. I was hoping it would be easier than this but at least this works.
Inspired by the "inspect the Qt source code and adapt" approach, this worked for me with Qt 5.12:
if (auto grid = dynamic_cast<QGridLayout*>(msgBox.layout())) {
if (auto text = grid->itemAtPosition(0, grid->columnCount() - 1); text && text->widget()) {
text->widget()->setFixedWidth(500);
}
}
Keep in mind, of course, that this will break if ever they change the way they do layouts of QMessageBox.

Align to the right text from QLabel and QLineEdit

I have a QLabel just below a QLineEdit with the same size and alignment properties:
QLineEdit *lineEdit = new QLineEdit("999");
lineEdit->setFixedWidth(100);
lineEdit->setAlignment(Qt::AlignRight);
//
QLabel *label = new QLabel("999");
label->setFixedWidth(100);
label->setAlignment(Qt::AlignRight);
//
QLayout *layout = new QVBoxLayout;
layout->addWidget(lineEdit);
layout->addWidget(label);
Here is how this is rendered:
How can I have the text of the bottom label exactly right-aligned to the text of the lineEdit?
Full award if you find a solution that works on all platforms, and that also works when the font sizes are different in the lineEdit and label.
Unfortunately it may not be possible, at least not out of the box, the right margin will not work, as it is always 0 even when the text is obviously offset to the left. The reason for this is this offset is not determined by the margins, but depends on the combination of platform GUI style and particular font's metrics that's being used, and its value is "conveniently" not available in the class public interface, there is no way to get to it.
You can get the font metrics easily, but you can't get the QStyleOptionFrame as the method required is protected, accessing it will require to subclass QLineEdit. However, if you are lucky, that value is very likely to be zero, so you could go with something as simple as this:
QVBoxLayout *layout = new QVBoxLayout;
QLineEdit *lineEdit = new QLineEdit("999");
lineEdit->setAlignment(Qt::AlignRight);
QLabel *label = new QLabel("999");
label->setAlignment(Qt::AlignRight);
int offsetValue = lineEdit->fontMetrics().averageCharWidth();
label->setIndent(offsetValue);
setLayout(layout);
layout->addWidget(lineEdit);
layout->addWidget(label);
If that doesn't work correctly for you, you will have no other choice but to subclass QLineEdit, carefully examine its paint event, determine where the offset is being calculated, and store that value in a public member so it can be accessed from the outside to be used to offset the label.
I personally got lucky with that code:
Would you be able to instead of using a QLineEdit and a QLabel to use two QLineEdits?
Consider the following:
QWidget* widget = new QWidget();
// Original line edit
QLineEdit *lineEdit1 = new QLineEdit("999");
lineEdit1->setFixedWidth(100);
lineEdit1->setAlignment(Qt::AlignRight);
lineEdit1->setStyleSheet("border-width: 2px;");
// A suggestion if you want a label
QLabel *label = new QLabel("999");
label->setFixedWidth(100);
label->setAlignment(Qt::AlignRight);
label->setStyleSheet("border: 2px solid rgba(255, 0, 0, 0%)");
// Alternatively if you can use another QLineEdit
QLineEdit *lineEdit2 = new QLineEdit("999");
lineEdit2->setFixedWidth(100);
lineEdit2->setAlignment(Qt::AlignRight);
lineEdit2->setReadOnly(true);
lineEdit2->setStyleSheet("background: rgba(0, 0, 0, 0%); "
"border-width: 2px; "
"border-style: solid; "
"border-color: rgba(0, 0, 0, 0%);");
// Bring it all together
QLayout *layout = new QVBoxLayout(widget);
layout->addWidget(lineEdit1);
layout->addWidget(label);
layout->addWidget(lineEdit2);
widget->show();
It forces all borders to be 2px, so on different platforms it should be the same. The second QLineEdit should not look different than the QLabel (The text color looks slightly darker than that of the label though, which might be a good thing since it matches the original edit)
The added benefit of using a QLineEdit instead of the QLabel is that the value is now selectable...
Disclaimer: I have only tested on Linux and I have not done a pixel level comparison.
Edit: I see the alignment fails with different font sizes.
simply, you can use the indent property of the QLabel.
https://doc.qt.io/qt-5/qlabel.html#indent-prop
The indent property can take +/- values. maybe the margin feature can do its job.

QT GridLayout add Stacked QLabel

I am creating an image gallery, i've implemented the reading in of Files and showing them in a resizable scroll-Area. We've decided to add meta-tags / Buttons and i am searching for a convenient way not to change too much but add this little features.
Any suggestion how i can achieve this? Can i add two Qlabels to each other? I tried to stuck two labels in a new layout and push this to the scrollWidgetLayout, but then i have only one Thumbnail.
//Create new ThumbNail-Object
thumbNail = new Thumbnail(ui->scrollArea);
scrollWidgetLayout->addWidget(thumbNail);
In the picture you can see what i have already and what i need (yellow).
You create a widget that acts like a container and put the labels inside it. Set a layout to this widget, I used QVBoxLayout. A better design would be to create a custom widget by subclassing QWidget, but I just used QFrame to make the example quick and simple.
centralWidget()->setLayout(new QVBoxLayout);
QScrollArea *area = new QScrollArea(this);
area->setWidgetResizable(true);
area->setWidget(new QWidget);
QGridLayout *grid = new QGridLayout;
area->widget()->setLayout(grid);
centralWidget()->layout()->addWidget(area);
for(int row = 0; row < 2; row++)
{
for(int column = 0; column < 5; column++)
{
QFrame *container = new QFrame; // this is your widget.. you can also subclass QWidget to make a custom widget.. might be better design
container->setStyleSheet("QFrame{border: 1px solid black;}"); // just to see the shapes better.. you don't need this
container->setLayout(new QVBoxLayout); // a layout for your widget.. again, if you subclass QWidget do this in its constructor
container->layout()->addWidget(new QLabel("TOP")); // the top label.. in your case where you show the icon
container->layout()->addWidget(new QLabel("BOTTOM")); // the bottom label.. in your case where you show the tag
grid->addWidget(container, row, column); // add the widget to the grid
}
}