How do I synchronize model views that have been hidden? - qt

I have three views of the same model: two QListViews and one QTableView. Generally only one is visible but the user can switch views, hiding the current view and making one of the others visible. Qt does not update hidden views so I need to scroll a view that has been set visible to match a previous view. The issue is that when a QListView is set visible it takes time to paint and setup the scrollbars etc. How can I reliably determine when the view is ready to accept a scrollTo request?
I have tried calculating the maximum value of the scrollbars and then checking the horizontalScrollBar()->maximum(), but there is still a delay after the widget scrollbars have reached maximum size. I have also monitored all the view widget events, which include multiple paint events until the widget is finished. I would prefer to poll the QListView on a timer to determine when it is ready, but I do not know what to call.
void MW::gridDisplay()
{
// hide thumbView (QListView)
thumbView->setVisible(false);
// show gridView (QListView)
gridView->setVisible(true);
gridView->setFocus();
// scrollTo not working if gridView was hidden unless delay by about 300ms
// until gridView is ready
gridView->scrollTo(gridView->currentIndex(), gridView->ScrollHint::PositionAtCenter);
}

I figured out what was causing the delay in rendering the QListView, requiring a delay before calling QListView::scrollTo(). I had included a QListView::setLayoutMode(QListView::Batched) which really slows things down. Here is some code that illustrates the problem:
#include <QtWidgets>
#include <QDebug>
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
auto f = new QFrame;
auto blank = new QListView;
auto view = new QListView;
view->setViewMode(QListView::IconMode);
view->setResizeMode(QListView::Adjust);
// this prevents the scrollTo working in time
view->setLayoutMode(QListView::Batched);
auto stack = new QStackedLayout;
f->setLayout(stack);
f->layout()->addWidget(blank);
f->layout()->addWidget(view);
QFile file("D:/Pictures/Avatars/frog.jpg"); // substitute your own image
QImage image;
QImageReader thumbReader;
thumbReader.setFileName("D:/Pictures/Avatars/frog.jpg");
thumbReader.setScaledSize(QSize(40,40));
image = thumbReader.read();
auto model = new QStandardItemModel;
view->setModel(model);
for (int row = 0; row < 50000; ++row) {
auto item = new QStandardItem();
item->setData(QString::number(row), Qt::DisplayRole);
item->setIcon(QPixmap::fromImage(image));
model->appendRow(item);
}
stack->setCurrentIndex(1);
// scrollTo does not work if view->setLayoutMode(QListView::Batched) without a delay
view->scrollTo(model->index(40000,0), QAbstractItemView::PositionAtCenter);
f->show();
return a.exec();
}
Many thanks for the help and advice to create a minimal reproducible example. Lesson learned.

I really tried to reproduce your error, but I just cannot see the same behavior as you did. The app has two views, which can be hidden and modified by either drag and drop or by adding items at the end.
Still, the update works as expected. Maybe there is still something different with your app. (I'm using Qt 5.13.0)
#include <QApplication>
#include <QDebug>
#include <QPushButton>
#include <QCheckBox>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QFrame>
#include <QTreeView>
#include <QStandardItemModel>
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
auto f = new QFrame;
auto viewLeft = new QTreeView;
auto viewRight = new QTreeView;
auto model = new QStandardItemModel;
viewLeft->setDragDropMode(QAbstractItemView::DragDropMode::InternalMove);
viewRight->setDragDropMode(QAbstractItemView::DragDropMode::InternalMove);
viewLeft->setModel(model);
viewRight->setModel(model);
auto item1 = new QStandardItem("Item1");
model->appendRow(item1);
auto item2 = new QStandardItem("Item2");
model->appendRow(item2);
auto item3 = new QStandardItem("Item3");
item2->appendRow(item3);
f->setLayout(new QHBoxLayout);
f->layout()->addWidget(viewLeft);
f->layout()->addWidget(viewRight);
auto grpBox = new QGroupBox;
grpBox->setLayout(new QHBoxLayout);
auto chkLeft = new QCheckBox("Left");
auto chkRight = new QCheckBox("Right");
grpBox->layout()->addWidget(chkLeft);
grpBox->layout()->addWidget(chkRight);
f->layout()->addWidget(grpBox);
auto btnAdd = new QPushButton("Add");
QObject::connect(btnAdd, &QPushButton::clicked, [&]() {
model->appendRow(new QStandardItem("New Item"));
});
f->layout()->addWidget(btnAdd);
QObject::connect(chkLeft, &QCheckBox::toggled, [&](auto checked) { viewLeft->setVisible(checked); });
QObject::connect(chkRight, &QCheckBox::toggled, [&](auto checked) { viewRight->setVisible(checked); });
f->show();
return a.exec();
}

Related

Disable specific items in QComboBox

In my application, I want to disable some items (i.e. not selectable, no highlights when mouse hovering above, and the texts are greyed out) in the QComboBox when certain conditions are met.
I indeed found someone did ask the same question here: Disable Item in Qt Combobox
But none of these solutions in the answers seem to actually work (including the trick).
Is there a decent and 'correct' way to implement this?
EDIT:
I found out why setting the flags wouldn't disable the items in my application: for some reasons, I had to set the style QStyle::SH_ComboBox_UseNativePopup(see https://codereview.qt-project.org/#/c/82718/). And this setting for some reasons blocked the flag setting. Does anyone have an idea why, and how to work around? A minimum test example is included (modified from the answer of #Mike):
#include <QApplication>
#include <QComboBox>
#include <QStandardItemModel>
#include <QProxyStyle>
class ComboBoxStyle : public QProxyStyle
{
public:
int styleHint ( StyleHint hint, const QStyleOption * option = 0, const QWidget * widget = 0, QStyleHintReturn * returnData = 0 ) const override
{
if ( hint == QStyle::SH_ComboBox_UseNativePopup )
{
return 1;
}
return QProxyStyle::styleHint( hint, option, widget, returnData );
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QComboBox comboBox;
// Setting this style would block the flag settings later on.
comboBox.setStyle( new ComboBoxStyle() );
comboBox.insertItem(0, QObject::tr("item1"));
comboBox.insertItem(1, QObject::tr("item2"));
QStandardItemModel* model = qobject_cast<QStandardItemModel*>(comboBox.model());
QStandardItem* item= model->item(1);
item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
comboBox.show();
return a.exec();
}
The answer linked in my comment above seems to be talking about an old version of Qt. I have tested on Qt5.4 and Qt5.6 and there is no need set the color yourself here, you just need to set and/or clear the Qt::ItemIsEnabled flag, here is an example:
#include <QtWidgets>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
QComboBox comboBox;
comboBox.addItem(QObject::tr("item1"));
comboBox.addItem(QObject::tr("item2"));
comboBox.addItem(QObject::tr("item3"));
QStandardItemModel *model =
qobject_cast<QStandardItemModel *>(comboBox.model());
Q_ASSERT(model != nullptr);
bool disabled = true;
QStandardItem *item = model->item(2);
item->setFlags(disabled ? item->flags() & ~Qt::ItemIsEnabled
: item->flags() | Qt::ItemIsEnabled);
comboBox.show();
return a.exec();
}
Here is the technique #Mike describes, wrapped up in a convenient utility function:
void SetComboBoxItemEnabled(QComboBox * comboBox, int index, bool enabled)
{
auto * model = qobject_cast<QStandardItemModel*>(comboBox->model());
assert(model);
if(!model) return;
auto * item = model->item(index);
assert(item);
if(!item) return;
item->setEnabled(enabled);
}

Understanding QGraphicsScene in Qt

I am trying to understand the basics of Qt. After going through some posts, I came to know that ui_mainwindow.h gets created by UIC tool and that ui_mainwindow.h contains the information about my form/ui that I created.
In my GUI, I have taken a pushbutton and a graphics view. I want a simple image (which I am creating inside the program itself) gets displayed in the graphicsView. I am trying to do it with two ways (for learning purpose):
I can write the code inside on_pushButton_clicked()(i.e. the slot of my push_button).
I am trying to put the image from the main()
Problem: I am done with the first method. I used the following lines of code inside on_pushButton_clicked() and it worked.
void MainWindow::on_pushButton_clicked()
{
//Display image in the graphics viewer
Mat img(200,200, CV_8UC3, Scalar(255,0,0));
QImage image( img.data, img.cols, img.rows, img.step, QImage::Format_RGB888 );
QGraphicsScene* scene = new QGraphicsScene();
QGraphicsPixmapItem* item = new QGraphicsPixmapItem(QPixmap::fromImage(image));
scene->addItem(item);
ui->graphicsView->setScene(scene);
}
Now, I want to do the similar thing from the main(). To do that, now my main() looks like following:
#include "mainwindow.h"
#include <QApplication>
//For image
#include <QImage>
#include <QPixmap>
#include <QGraphicsPixmapItem>
//#include "ui_mainwindow.h"
//OPENCV Headers
#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/imgcodecs/imgcodecs.hpp>
using namespace cv;
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
//Display image in the graphics viewer
Mat img(200,200, CV_8UC3, Scalar(255,0,0));
QImage image( img.data, img.cols, img.rows, img.step, QImage::Format_RGB888 );
QGraphicsScene* scene = new QGraphicsScene();
QGraphicsPixmapItem* item = new QGraphicsPixmapItem(QPixmap::fromImage(image));
scene->addItem(item);
w.ui->graphicsView->setScene(scene);
w.show();
return a.exec();
}
The above code written inside the main() works if I put #include "ui_mainwindow.h" in main.cpp. But if I comment #include "ui_mainwindow.h" and w.ui->graphicsView->setScene(scene); then, it throws error for the QGraphicsScene* scene = new QGraphicsScene();.
Error is main.cpp:32: error: allocation of incomplete type 'QGraphicsScene' QGraphicsScene* scene = new QGraphicsScene();
QUESTIONS: Why is the connection between QGraphicsScene and "ui_mainwindow.h". I understand that I need "ui_mainwindow.h" for the line w.ui->graphicsView->setScene(scene); becasue there I am using my ui but I don't understand the need for QGraphicsScene.
If You want to draw only static image better use just QLabel and set in some image
QImage *image = new QImage (":/prefix/image/vl.jpg" );
QLabel *magelab = new QLabel();
magelab->setPixmap(QPixmap::fromImage(*image));
Generaly QGraphicsScene better using with connection QGraphicsView. That is mean QGraphicsView set some object (scene) from QGraphicsScene just like:
class SomeObject : public QGraphicsView
{
Q_OBJECT
public:
explicit Schemat(QWidget *parent = 0);
private:
QGraphicsScene *scene;
};
and source
Schemat::Schemat( QWidget *parent) : QGraphicsView(parent)
{
scene = new QGraphicsScene(this);
this->setScene(scene);
// to Your scene You can add some image for example
scene->addPixmap(SOME_PIXMAP)
}
Then You create main window and add with Your QGraphicsView, for example as some part QGroupBox
void MainWindow::SetupSchemat()
{
schema = new Schemat();
QGroupBox *schbox;
QHBoxLayout *hschbox;
schbox = new QGroupBox(this);
hschbox = new QHBoxLayout(this);
schbox->setTitle("SomeScene");
hschbox->addWidget(schema); //add scene to layout
schbox->setLayout(hschbox);
}
QGraphicsScene is like some part of Your MainWindow on which you can make some animation, You can something draw. QGraphicsScene is more better to using if You want use animation not only static image it supplies more option to manipulate image (ex scaling, catch mouse click, manage via cordinates others object), and other object each should be animate or just display. To QGraphicsScene You can add some QGraphicsItem in turn each QGraphicsItem can moving onto QGraphicsScene with particular conditions defined erly or in flow. Together QGraphicsView, QGraphicsScene and QGraphicsItem can created animation or just image in some part Your main window.
Also nice explained You will find here
https://www.youtube.com/watch?v=fmSs2mNGh9I

How to use TAB Key to focus one of two qpushbutton

In a widget I put two QPushButton (let's say "OK" at left and "EXIT" at right).
They regularly work when I press them using the mouse.
Suppose I want to switch from one to the other using TAB key: is it possible?
And how can do this?
On some platforms, keyboard focus navigation among buttons is a default behavior, but on some it isn't.
If you wish keyboard navigation on all platforms, the buttons should have a Qt::StrongFocus policy set on them. Note that the shortcut used to trigger the buttons is also platform-specific. E.g. on OS X you'd use Space.
#include <QtWidgets>
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QWidget w;
QVBoxLayout layout{&w};
// Individual Buttons
QPushButton p1{"button1"}, p2{"button2"};
for (auto p : {&p1, &p2}) {
layout.addWidget(p);
p->setFocusPolicy(Qt::StrongFocus);
}
// A button box
QDialogButtonBox box;
for (auto text : {"button3", "button4"})
box.addButton(text, QDialogButtonBox::NoRole)->setFocusPolicy(Qt::StrongFocus);
layout.addWidget(&box);
w.show();
return app.exec();
}
I tried it out on KDE/Ubuntu. It works automatically.
main.cpp
#include <QApplication>
#include "mainwindow.hpp"
int main(int argc, char** args) {
QApplication app(argc, args);
MainWindow m;
m.show();
return app.exec();
}
mainwindow.hpp
#ifndef MAINWINDOW_HPP
#define MAINWINDOW_HPP
#include <QMainWindow>
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow();
};
#endif // MAINWINDOW_HPP
mainwindow.cpp
#include "mainwindow.hpp"
#include <QPushButton>
#include <QVBoxLayout>
MainWindow::MainWindow() : QMainWindow() {
auto* w = new QWidget;
auto* l = new QVBoxLayout;
auto* p1 = new QPushButton("ok");
auto* p2 = new QPushButton("exit");
l->addWidget(p1);
l->addWidget(p2);
w->setLayout(l);
setCentralWidget(w);
}
a.pro
TEMPLATE = app
TARGET = a
INCLUDEPATH += .
QT += widgets
HEADERS += mainwindow.hpp
SOURCES += main.cpp mainwindow.cpp
QMAKE_CXXFLAGS += -std=c++14
Edit: Apparently the buttons switch focus, but pressing enter does nothing. I guess you have to use focus-related mechanics (search for "focus" in the QWidget documentation) and implement it yourself. Or have a look at QDialog (as a replacement for QMainWindow in my example). It should have some meaningful default behavior for the enter and escape buttons.
Side note: Maybe you rather want to use the QDialogButtonBox for ok- and exit-buttons in your project. It's the cross-platform way of displaying OK/Cancel/Accept/Reject/... buttons because their arrangement differs between platforms. And this class can help you with that.
It is easier than all that code. Just use setFocusPolicy with Tabfocus on both buttons like this:
yourButtonOk->setFocusPolicy(Qt::TabFocus);
yourButtonExit->setFocusPolicy(Qt::TabFocus);

Displaying database values with radio button selection in Qt?

I want to show some database row values with radio button selection in Qt GUI. How this can be accomplished?. This could be done using foreach loop I guess. I have studied a bit about the following classes :
1) QMainWindow
2) QSqlTableModel
3) QTableWidget.
But which one satisfies my requirement? I am not able to implement it, please guide me. Thanks in advance.
I have implemented upto this in my source file-
main.cpp:
#include <QtGui/QApplication>
#include <QtSql>
#include <QTableWidget>
#include <QMessageBox>
#include "mainwindow.h"
#include <QRadioButton>
#include <QVBoxLayout>
#include <QGroupBox>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTableWidget* table = new QTableWidget();
table->setWindowTitle("Connect to Mysql Database Example");
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");
db.setHostName("localhost");
db.setDatabaseName("guests");
db.setUserName("sri");
db.setPassword("******");
if (!db.open())
{
QMessageBox::critical(0, QObject::tr("Database Error"),
db.lastError().text());
}
QSqlQuery query("SELECT * FROM new_members");
table->setColumnCount(query.record().count());
table->setRowCount(query.size());
int index=0;
while (query.next())
{
table->setItem(index,0,new QTableWidgetItem(query.value(0).toString()));
table->setItem(index,1,new QTableWidgetItem(query.value(1).toString()));
index++;
}
// This is sample radiobutton from QGroupBox class. Like this I need to implement the values from DB in with radio button selections for each value
QMainWindow *window = new QMainWindow();
window->setWindowTitle(QString::fromUtf8("QGroupBox"));
window->resize(400, 400);
QGroupBox *groupBox = new QGroupBox("Radio Buttons");
QRadioButton *radio1 = new QRadioButton("Radio button 1");
radio1->setChecked(true);
QVBoxLayout *vbox = new QVBoxLayout;
vbox->addWidget(radio1);
groupBox->setLayout(vbox);
window->setCentralWidget(groupBox);
window->show();
table->show();
//MainWindow w; w.show();
return a.exec();
}
Use a QSqlTableModel to drive a QTableView, you will need a custom QStyledItemDelegate to draw the QRadioButton (yes I said draw, and not create), and create an editor widget (of course that really will be a QRadioButton).
This is quite a big job, so you will need to read the above class' docs to reimplement the bits you need. Start with the MVC documents.

'Magical' QTextEdit size

Here is an equivalent extracted code:
#include <QApplication>
#include <QWidget>
#include <QVBoxLayout>
#include <QTextBrowser>
#include <QTextEdit>
class ChatMessageEdit : public QTextEdit {
public:
ChatMessageEdit(QWidget* parent) : QTextEdit(parent) { }
virtual QSize sizeHint() const { return QSize(0, 25); }
};
int main(int argc, char** argv) {
QApplication app(argc, argv);
QWidget* widget = new QWidget;
QVBoxLayout* layout = new QVBoxLayout;
QTextBrowser* log = new QTextBrowser(widget);
layout->addWidget(log, 1);
ChatMessageEdit* editor = new ChatMessageEdit(widget);
editor->setMinimumHeight(editor->sizeHint().height()); // empty
layout->addWidget(editor);
widget->setLayout(layout);
widget->show();
return app.exec();
}
The minimum size for editor is 25px, and so is it's minimal size. But by some strange reason it is created with a size about 100px that is always preferred to my size hint. Everything other is working as expected: expanding (size hint isn't really fixed in my application), shrinking etc. I tried changing size policy, but with abolutely no result.
This was the minumumSizeHint() method. I overloaded it to return sizeHint(), and everything is working as expected now.
You are also overlooking how layouts work. Please read up here on why your sizes are not being respected in a layout.

Resources