QPaintEvent painting region in Qt? - c++

This is a basic doubt regarding the Painting done in Qt.
I have a QScrollArea as my centralWidget in Main Window of my Application. I have added a QFrame frame to the scrollarea. The Layout of the QFrame is QGridLayout.
When I add widgets to the layout like this:
MainWindow::AddLabel()
{
setUpdatesEnabled(false);
QGridLayout *myGrid = (QGridLayout *)ui->frame->layout();
for(int i = 0; i < 1000; i++)
{
QLabel *label = new QLabel();
QString str;
str.SetNum(i);
label->SetText(str);
myGrid->AddWidget(label, 0, i, 0);//add label to i'th column of row 0
}
setUpdatesEnabled(true);
repaint();
}
Please dont worry about the memory leak as it is not the focus of the question.
So my doubt's are:
Is setting the updates disabled during adding widgets to layout any helpful?
Even if I maximise the window not all the QLabel's will be visible to me. So when the code flow leaves the above function & goes to the event loop then are all the QLabel's & the enormous area of QFrame painted? Or only those QLabel's which are visible & only that much area of QFrame which is visible painted?

If you are using a form (.ui) then the widgets inside the ui are not children of your widget MainWindow. Well , setUpdatesEnabled() only affect the current widget as well as its children, so the object ui->frame will still receive updates after myGrid->AddWidget. Change to
ui->frame->setUpdatesEnabled(false);
...
ui->frame->setUpdatesEnabled(true);
Btw, when you enable updates, then screen will be updated. So you dont need to call repaint(); on any widget.

Related

Qt window resize event after resize is done

I have a QtChart in a QDialog and I use a simple QWidget to show it on the screen. I need to resize this hart whenever the dialog window is resized by user.
This is how I add the chart to the dialog (in constructor):
// Setup chart view to show the chart
mChartView = new QChartView(mChart, ui->widget);
mChartView->setParent(this);
mChartView->resize(ui->widget->size());
mChartView->setRenderHint(QPainter::Antialiasing);
I have overrided the resizeEvent of the QDialog in my own dialog:
void CurveDialog::resizeEvent(QResizeEvent *event)
{
mChartView->resize(ui->widget->size());
}
This works, and the chart gets resized...but the problem is it is terribly slow! because it will resize for all the steps that user drags the window corner to resize it!
How can I do the resize only when there resize is done? I wanted to use a timer but this looks like a dirty hack! any better ideas?
Qt provides a layout system to manage the geometries of child widgets within a widget. A layout will arrange the size and the position of each child to ensure that it will take all the available space.
The layout will automatically resize the child widgets when the parent is resized:
QChartView *chartView = new QChartView(chart);
chartView->setRenderHint(QPainter::Antialiasing);
QDialog* dialog = new QDialog();
QVBoxLayout* layoutDialog = new QVBoxLayout(dialog);
QWidget* widget = new QWidget();
QVBoxLayout* layoutWidget = new QVBoxLayout(widget);
layoutDialog->addWidget(widget);
layoutWidget->addWidget(chartView);
dialog->exec();

QWidget not displaying the QLabel

I have a QWidget which I want to use like a Dialog on top of another QWidget.
What I'm trying to do is a simple "Please wait while yadda yadda..." dialog with no buttons.
The code section is as follows:
void NewWindow::on_combobox_New_currentIndexChanged(int index) //slot function
{
QWidget* box = new QWidget();
box->setWindowModality(Qt::ApplicationModal);
box->setWindowTitle("Wait...");
QHBoxLayout* layout = new QHBoxLayout();
box->setLayout(layout);
QLabel* lbl = new QLabel();
lbl->setText("Loading...");
layout->addWidget(lbl);
box->show();
for (int var = 0; var < st.size(); ++var)
{
//Some heavy lifting here
}
box->close();
}
Normally I would expect this dialogue box to appear with the proper text and disappear after the loop ends. In fact it does that too but with one difference: The label does not display. The widget looks empty. Then disappears.
If I copy the code into a different area (for example to the MainWindow constructor) it displays properly with the message in it.
I sense that the loop blocks the draw operation but then the widget itself should be missing too. Why it is only the label eludes me.
Thanks for any insight.
Since you are creating and displaying this widget in a QObject slot, and then before returning from the slot, closing the widget, by the time Qt goes through the process of executing all your instructions, the last one is close, and so the widget disappears from view.
Underneath your slot, Qt is running in an event loop. Since control is never returned to the event loop, Qt never has an opportunity to render the graphics you've requested of it.
When you create widgets, add labels, etc, you are actually registering a bunch of commands with the event loop, which will only later be processed.
If you want Qt to render any changes you have made whilst in a slot, before returning to the event loop, you have to call processEvents.
Without doing so, you won't see those changes until control passes back to the Qt event loop.
So what is happening here, is that since you're also closing the widget at the end of your slot, Qt will create the widget, render its contents, and then immediately close it, and you won't see anything.
The reason for this is so that Qt can do calculations on what is visible, what isn't, be smart about what it renders etc, and only decide to draw what is necessary.
If it just rendered everything immediately, without waiting for control to return to it so it can process the "next batch of updates", it would likely be horribly inefficient.
So you need to put processEvents inside your callback slot.
void NewWindow::on_combobox_New_currentIndexChanged(int index) //slot function
{
QWidget* box = new QWidget();
box->setWindowModality(Qt::ApplicationModal);
box->setWindowTitle("Wait...");
QHBoxLayout* layout = new QHBoxLayout();
box->setLayout(layout);
QLabel* lbl = new QLabel();
lbl->setText("Loading...");
layout->addWidget(lbl);
box->show();
QCoreApplication::processEvents(); // cause the box to be displayed
for (int var = 0; var < st.size(); ++var)
{
//Some heavy lifting here
// if you do anything here to change the widget, such as
// updating a progress bar, you need to `processEvents` again
QCoreApplication::processEvents();
}
box->close();
}
As for why the widget window appears, but not the contents, this is likely because when creating the widget Qt sends a message to the Window Manager (in this case MS Windows), which will create the window on which Qt can render its contents.
So you see the result of Windows creating a new window for Qt, but nothing from Qt painting on that window itself.

Qt 'glue' two widgets together

I have two widgets (both QFrames), none of them have any title bar associated with them (which I achieve through setWindowFlags(Qt::FramelessWindowHint)). One of them is a main widget, and the other a sidebar sort of widget, which is supposed to stick to it at its right boundary (its height being approximately 1/4th of the main widget).
I cannot keep them both in a transparent QFrame with static positioning, since the main widget is draggable through its top (since title bar is missing on it, I do it manually by intercepting mousepress/mousemove events and moving it accordingly). The custom drag on the main widget works fine, but when I try to move the sidebar along with, a very obvious visual delay shows up between the two, there are momentary gaps visible between the two while dragging the main widget to the left, or momentary overlapping between the two when dragging the main widget to the right (the sidebar is not draggable, no drag logic is implemented for it).
How do I 'glue' these two widgets together, such that they move together all the time without any delay? I browsed the Qt docs, it may be possible that QDockWidget can help here, but I could not understand how. The main widget here is not a QMainWindow.
Platform - OS X Yosemite, Qt 5.3.1, 32 bit.
You should definitely use QDockWidget here.
Make your "main widget" derive from QMainWindow rather than QFrame (it may not be "obvious" as QMainWindow does not derive from QFrame, but it should not be such a big deal).
Then, encapsulate your second widget within a QDockWidget and dock it in the main widget like that:
// secondWidget being your QFrame based widget
// mainWidget being your "main widget"
QDockWidget* dockingBar = new QDockWidget("My bar", mainWidget );
dockingBar->setWidget( secondWidget );
// dock on left side, change first parameter to dock somewhere else:
mainWidget->addDockWidget( Qt::LeftDockWidgetArea, dockingBar );
An alternative is to create a third widget that would become your top-level widget and use a QLayout to insert your two QFrames in this new one:
QWidget* newTopLevelWidget = new QWidget();
// QHBoxLayout to have mainWidget on the left hand side of secondWidget
// Replace by QVBoxLayout to have mainWidget on top of secondWidget
QLayout* layout = new QHBoxLayout( newTopLevelWidget );
layout->addWidget( mainWidget );
layout->addWidget( secondWidget );

QWidget within scrollarea

I have a QWidget that I want to include within a scroll-area so that when the designated QWidget size is exceeded vertically, the user can scroll up and down to see more.
QWidget renameWidget;
QScrollArea scrollarea.
How do I go about doing this? I set the widget inside the scroll-area on the UI editor but it didn't work.
Any ideas?
Thanks.
Think of QScrollArea as another layout. Add the scroll area to your main widget and put everything else inside it with setWidget().
QScrollArea is QWidget, so you can even use it as a top level widget:
QScrollArea *scrollArea = new QScrollArea();
scrollArea->resize(250, 250);
QWidget *widget = new QWidget(scrollArea);
widget->setBackgroundRole(QPalette::Dark);
widget->resize(200, 200);
scrollArea->setWidget(widget);
scrollArea->show();
QScrollArea provides a scrolling view onto another widget. It is used to display the contents of a child widget within a frame. If the widget exceeds the size of the frame, the view can provide scroll bars so that the entire area of the child widget can be viewed.
An example:
QScrollArea *scrollArea = new QScrollArea(this);
scrollArea->setBackgroundRole(QPalette::Dark);
scrollArea->setWidget(renameWidget);

How to control the Widgets in QGridLayout that should be painted in GUI?

In my application I have a QGridLayout which covers the majority of the Window. In it I have added a sequence of QLineEdit & QLabel objects. Currently when the no of QLineEdit objects > 500 && QLabel objects > 500 the GUI is significantly slow & for greater values does not operate properly. Also most of these widgets are not visible in the window, they need to be scrolled to be viewd.
Since I am adding so many widgets in the grid layout (by looping & calling repaint after the loop) the painting takes lots of time.
So I have an idea for the solution that even though my widgets are added in the Grid Layout not everybody are painted. I want to have a rectangle within which all widgets are painted & the co-ordinates of the rectangle will be updated whenever the the window is scrolled. But I dont know how to do this. So I wanted to know is it possible to do that?
And if possible please add a small sample code so that I can understand how to implement that.
Thank You.
UPDATE : Adding an image to depict the sitatuion.
Black Rectangle = QGridLayout say myGid.
Red Rectangle = Bounding Rectangle which is approximately same size as Main Window of my Application.
Green Rectangle = Widgets in myGrid.
Green Rectangle filled with yellow = Widgets shown in Main Window (only these widgets should be considered for call to repaint), rest of the unfilled rectangles are widgets present in myGrid but not be considered for call to repaint.
Thus when I scroll in my main application, the co-ordinates of red rectangle are updated & all the widgets bounded by it are considered for repaint.
I hope I made the problem simple to understand.
I understand you don't want to discard your code. I'd try one of these, starting from the easiest:
Are you using a QScrollArea or are you simulating it using scroll bars? The QScrollArea probably already discards paint events to children widgets that are off the viewport. Assemble the grid off-screen. Otherwise, Qt will recalculate and repaint the layout every time you add a new widget. (Here is a complete example.)
QWidget* widget = new QWidget(); // This is an invisible widget.
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 100; j++) {
QLineEdit* lineEdit = new QLineEdit();
lineEdit->setText(QString("I am the line edit at (%1, %2)").arg(i).arg(j));
layout->addWidget(lineEdit, i, j);
if (j % 10 == 0) {
// Do not block the UI thread while the UI is being assembled.
qApp->processEvents();
}
}
}
// The layout will be calculated only once, here:
scrollArea->setWidget(widget);
widget->show();
If that doesn't work, create an event filter that has a reference the visible rectangle. Event filtering is a useful technique in which you can intercept events targeted at one or more widgets and decide if they should be discarded before being handled.
In your case, when you intercept a QPaintEvent, check if the target widget intersects the visible rectangle. If it does, pass the event along to the target widget. If it doesn't, discard the event.
I don't know the specifics of how you scroll your UI, so I leave the calculating the visible rectangle up to you. The event filter code would be something like this.
bool MyClass::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::Paint) {
if (QWidget* widget = qobject_cast<QWidget*>(object)) {
QRect visibleRectangle = somehowGetVisibleRectangle();
if (visibleRectangle.intersects(widget->geometry())) {
return false;
} else {
// Returning true means "drop this event."
return true;
}
}
}
// Assuming MyClass extends QWidget. Adjust as necessary.
return QWidget::eventFilter(obj, event);
}
As a last resort, relayout your UI using QGraphicsScene, QGraphicsWidget, QGraphicsGridLayout, and QGraphicsView. The scene graph might be better at discarding unnecessary UI repaints.
First of all. Are you sure you are solving your problem in the right way? Perhaps you will be happier with QTableWidget? Its cells can be editable and then QTableWidget will take care of creating and maintaining QLineEdit for the cell that is being edited.