How to customize Qt Items' paint event? - qt

I'd like to write a music player, and I want to implement a music info list like this:
ID
liked
download
name
artists
album
duration
01
a button here
a download button here
music name
...
...
02:30
it can be implemented with QTableWidget or QTreeWidget, but when I want to re-rendering the whole item background as a progress bar when downloading music, I couldn't find a function like this:
void QMusicListWidgetItem::paintEvent(QPaintEvent* event) {
QPainter painter(this);
int pos = this->downloadProgress / 1.0 * width();
painter.setBrush(QColor(0, 120, 248));
painter.drawRect(0, 0, pos, height());
painter.setBrush(QColor(0, 0, 0));
painter.drawRect(pos, 0, width() - pos, height());
QWidget::paintEvent(event);
}
void QMusicListWidgetItem::updateProgressSlot(float progress) {
this->downloadProgress = progress;
update();
}
I tried to use delegate, but delegate's paint function can not re-render the whole item background. it can only customize the single cell's display effect.
What's the correct solution of this demind?

Related

Qt connect QScrollBar with QLineEdit

I would like to "activate" the scrollbar when the lineedit text is too long to display on the window. I have already done it.
I want to move the cursor with the scrollbar. I also want to modify the scroll bar slider length with the increment/decrement of the text length.
.h
private:
Ui::MainWindow *ui;
QLineEdit* LineEdit;
QScrollBar* hScrollBar;
void HDScrollBar();
constructor:
resize(400,100);
LineEdit = new QLineEdit(this);
LineEdit->resize(400,100);
LineEdit->setFont(QFont("Times",20));
hScrollBar = new QScrollBar(Qt::Horizontal, LineEdit);
hScrollBar->resize(400,20);
hScrollBar->move(0,80);
hScrollBar->hide();
connect(LineEdit, &QLineEdit::textChanged, this, &MainWindow::HDScrollBar);
hide/display scrollbar
void MainWindow::HDScrollBar() {
QFont myFont(QFont("Times",20));;
QString str = LineEdit->text();
QFontMetrics fm(myFont);
int width = fm.horizontalAdvance(str);
(width >= 400) ? hScrollBar->show() : hScrollBar->hide();
}
As for the first part, you use can scrollbar's valueChanged signal, e.g. this way:
connect(ui->horizontalScrollBar, &QScrollBar::valueChanged, ui->lineEdit, [&](int v) {
auto scrollbarAt = static_cast<double>(v) / ui->horizontalScrollBar->maximum();
auto cursorTo = std::round(ui->lineEdit->text().length() * scrollbarAt);
ui->lineEdit->setCursorPosition(cursorTo);
});
EDIT:
as for the latter part:
you can either alter the PageStep of the scrollbar, or you can set it to 1 and alter its maximum value, then also the first part should simplify:
ui->horizontalScrollBar->setPageStep(1);
ui->horizontalScrollBar->setMaximum(ui->lineEdit.text().length());
connect(ui->horizontalScrollBar, &QScrollBar::valueChanged,
ui->lineEdit, &QLineEdit::setCursorPosition);
//this can also ben done on textChanged, however for the price
//of more frequent execution...
connect(ui->lineEdit, &QLineEdit::cursorPositionChanged,
ui->horizontalScrollBar, [&](int, int n) {
ui->horizontalScrollBar->setMaximum(ui->lineEdit->text().length());
//...one gets the easy way to track the cursor
//with the slider
ui->horizontalScrollBar->setValue(n);
});

Space Buttons equally in Layout, but with an empty slot

I'm trying to create a panel of buttons that will have 4 buttons, a space, and another button, all of equal space, like this:
I have tried to use Spacers, but it seems like those require a specific height and weight, and I would like this layout to be dynamic enough to appear correctly on any resolution, so a fixed size Spacer would not work.
I have tried to following code, but this just squishes the first 4 buttons to the top and the last one to the bottom, and doesn't space them out evenly.
QVBoxLayout *layout = new QVBoxLayout;
layout->setMargin(15);
layout->setSpacing(15);
layout->addWidget(button1, 1);
layout->addWidget(button2, 1);
layout->addWidget(button3, 1);
layout->addWidget(button4, 1);
layout->addWidget(button5, 2, Qt::AlignBottom);
layout->addStretch();
buttonPnl->setLayout(layout);
I also tried using a QGridLayout and specifying the height of each row, but this looks the same as the previous example.
QGridLayout *gridLayout = new QGridLayout;
gridLayout->setMargin(15);
gridLayout->setSpacing(15);
gridLayout->addWidget(button1, 0, 0);
gridLayout->addWidget(button2, 1, 0);
gridLayout->addWidget(button3, 2, 0);
gridLayout->addWidget(button4, 3, 0);
gridLayout->addWidget(button5, 5, 0);
gridLayout->setRowStretch(0, 1);
gridLayout->setRowStretch(1, 1);
gridLayout->setRowStretch(2, 1);
gridLayout->setRowStretch(3, 1);
gridLayout->setRowStretch(4, 1);
gridLayout->setRowStretch(5, 1);
How can I create a dynamic layout that will display my buttons correctly at any reasonable resolution?
It's a little bit ``hacky-slash'' but... the easiest way to get the desired behaviour is probably to define a spacer class that inherits from QPushButton but has an empty paintEvent definition...
class spacer: public QPushButton {
using super = QPushButton;
public:
using super::super;
protected:
virtual void paintEvent (QPaintEvent *event) override
{
}
};
Then just make sure you instantiate it with a text string that's in keeping with the other buttons so that it has a suitable return value from sizeHint(). So (based on your own example)...
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(button1);
layout->addWidget(button2);
layout->addWidget(button3);
layout->addWidget(button4);
/*
* Add a spacer using the text from button4 as a reference.
*/
layout->addWidget(new spacer(button4->text()));
layout->addWidget(button5);
layout->addStretch();
buttonPnl->setLayout(layout);
This gives me something like...

paintEvent in QTableView derived class: Paint device returned engine == 0, type: 1

As a follow up of Qt load indicator by animated image (aka preloader) or alternative? I try to paint inside a QTableView. But when I initialize the QPainter I get the following warnings.
QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
Here is the code (SO answer, with a button it seems to work):
void CDerivedFromQTableView::paintEvent(QPaintEvent *event)
{
QTableView::paintEvent(event); // draw original content
QPainter p(this); // Problem: QPainter::begin: Paint device returned engine == 0, type: 1
const QPixmap pm(QPixmap::grabWidget(this->m_loadIndicator));
QPoint middle = this->geometry().center();
int x = middle.x() - pm.width() / 2;
int y = middle.y() - pm.height() / 2;
p.drawPixmap(QPoint(x, y), pm); // draw load indicator inside QTableView
}
I am surprised creating the QPainterfails, so why is that. What am I doing wrong?
The simplified version still gives the warning
QPainter p(this);
QTableView::paintEvent(event);
return;
Warning (of course) gone when I comment out QPainter, so it really seems to be the root cause, but why?
As QTableView is a subclass of QAbstractScrollArea you should open QPainter on its viewport:
void CDerivedFromQTableView::paintEvent(QPaintEvent *event)
{
QTableView::paintEvent(event); // draw original content
QPainter p(this->viewport());
p.drawRect(0, 0, 20, 20);
}
The docs say it:
This event handler can be reimplemented in a subclass to receive paint
events (passed in event), for the viewport() widget.
Note: If you open a painter, make sure to open it on the viewport().

How can I show/hide background drawing on QGraphicsScene or QGraphicsView?

I would like to have certain things drawn on QGraphicsScene, but not be QGraphicsItem (it would interfere with the processing of the QGraphicsItem collection).
Example: a scene bounding rectangle, a grid
I am overriding the drawBackground(QPainter *painter, const QRectF &rect) for that purpose. (I should subclass the scene... )
void MyView::showHideBounds()
{
m_showBackgroundBounds = !m_showBackgroundBounds;
// can't triger an update ???
update(); // neither does anything
viewport()->update();
}
void MyView::drawBackground(QPainter *painter, const QRectF &rect)
{
QPen pen;
if(m_showBackgroundBounds)
pen = QPen(QColor(0, 0, 0), 10, Qt::PenStyle(Qt::SolidLine));
else
pen = QPen(QColor(255, 255, 255), 10, Qt::PenStyle(Qt::SolidLine));
painter->setPen(pen);
painter->drawRect(QRect(QPoint(-scene()->sceneRect().size().toSize().width()/2,
-scene()->sceneRect().size().toSize().height()/2),
scene()->sceneRect().size().toSize()));
}
I would like the option to show/hide either the bounding rectangle or the grid.
The only thing I can think of is paint over them with the color of the background brush ? Is there any other option ?
As I have written it above, it works - except I need user action on items (or a zoom or some other scene changing action) to trigger refresh, or call an update... (the function showHideBounds doesn't - not sure how to make it force a refresh)
I would call the drawBackground from the showHideBounds function - but I don't know how to get the painter
[Also, the drawBackground seems to be drawn automatically... how can I give it the rect argument it needs ? (it seems if I draw the rect it does draw the scene rectangle but I only see the right and bottom edges)]
In order to redraw a particular section of scene, you can call
QGraphicsScene->invalidate(rect_to_redraw, Backgroundlayer)
Note that if drawBackground(*painter, rect) paints over area outside rect, it will not update automatically. In that case invalidate has to be called with appropriate rect parameters.

Can QMessageBox::about adjust size to title length?

I wanted to create a simple About dialog, but noticed that the QMessageBox::about does not adjust its size to the length of the title (which is usually longer due to the larger font ...at least in my desktop environment), only to the content. Is there a way to make sure that the dialog is made big enough to show all of the title as well? I could of course add white space to the aboutText, but I am hoping for a less hackish solution.
Example:
QString titleText("Some title which is slightly longer");
QString aboutText("Short about text");
QMessageBox::about(this,titleText,aboutText);
Currently the above code only gives me "Some ..." as the title string. I have built the program in Eclipse on Ubuntu with Qt 4.7.
Use "setStyleSheet()" function of "QMessageBox". Here is an example.
background-color: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #787878, stop: 0.5 #404040, stop: 0.6 #303030, stop: 0.8 #252525, stop: 1 #151515);
border: 2px solid #05b8cc;
border-radius: 8px;
color: white;
min-width: 300px;
min-height: 80px;
It will also affect the children of the "QMessageBox" whose stylesheets can be reverted by iterating through them. To access the children use "findChildren(QWidget)".
I believe QMessageBox does adjust size to fit the window title, but for some reason it doesn't work right on my system also, not sure if it's a bug or a feature, this is done in the qmessagabox.cpp QMessageBoxPrivate::updateSize() method.
Another thing I've noticed is that you're using an instance of the QMessageBox class to call about() method, which is static and you can execute it by using just the class name: QMessageBox::about(..).
What you could do to adjust the window size is creating your own subclass of the QMessageBox and adjusting min width of the window in the showEvent method, see the example below for details:
class MyMessageBox : public QMessageBox
{
public:
explicit MyMessageBox(QWidget *parent = 0) : QMessageBox(parent) { }
MyMessageBox(const QString &title, const QString &text, Icon icon,
int button0, int button1, int button2,
QWidget *parent = 0,
Qt::WindowFlags f = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint) :
QMessageBox(title, text, icon, button0, button1, button2, parent, f) { }
static void about(QString title, QString text)
{
MyMessageBox aboutBox(title, text, QMessageBox::Information, 0, 0, 0, NULL);
aboutBox.setText(title);
aboutBox.setText(text);
QIcon icon = aboutBox.windowIcon();
QSize size = icon.actualSize(QSize(64, 64));
aboutBox.setIconPixmap(icon.pixmap(size));
aboutBox.exec();
}
void showEvent(QShowEvent *event)
{
QMessageBox::showEvent(event);
QWidget *textField = findChild<QWidget *>("qt_msgbox_label");
if (textField != NULL)
{
// getting what ever my system has set for the window title font
QFont font = QFont("Ubuntu Bold", 11);
// you might want to make it more generic by detecting the actuall font
// or using smth like this:
//QFont font = QApplication::font("QWorkspaceTitleBar");
QFontMetrics fm(font);
int width = qMax(fm.width(windowTitle()) + 50, textField->minimumWidth());
textField->setMinimumWidth(width);
}
}
};
here's how you can call it:
QString titleText("Some title which is slightly longer");
QString aboutText("Short about text");
MyMessageBox::about(titleText, aboutText);
hope this helps, regards

Resources