Issue with QTableView and QStandardItemModel - qt

I am facing strange thing with dynamically adding items to my QStandardItemModel and showing them with QTableView. If the time between events is long enough (like 50 ms) then the view is scrollable, but in case of faster event (10 ms) the view always scrolls down and doesn't allow me to do anything.
So here is the simple code snippet:
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.h
#include <QMainWindow>
#include <QTimer>
#include <QTableView>
#include <QStandardItemModel>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
QTimer dataTimer;
QStandardItemModel *model;
QTableView *view;
public slots:
void insertData();
};
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QTime>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
view = new QTableView(this);
this->setCentralWidget(view);
model = new QStandardItemModel(this);
model->setColumnCount(1);
view->setModel(model);
QObject::connect(&dataTimer, &QTimer::timeout, this, &MainWindow::insertData);
dataTimer.start(10); // 50 here gives correct behaviour while 10 does not
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::insertData()
{
QList<QStandardItem *> items;
items.append(new QStandardItem(QTime::currentTime().toString("hh:mm:ss.zzz")));
model->insertRow(0, items);
if (model->rowCount() > 20){
model->removeRow(model->rowCount()-1);
}
}
dataTimer.start(50) works fine, while dataTimer.start(10) brakes behavior. It is for my laptop and maybe for someone else the numbers will be different but I think logic is clear.
I was thinking that it is kind of too fast and some methods like beginInsertRows() doesn't work well. But that is for custom models usually and here it is just a standard one.
Can someone tell me why that happens?
Thank you

10 ms timer in a GUI thread to affect GUI elements is a bad idea itself. For example, in Windows 10 ms timeout is unattainable by standard means. Does your user really need to see updates of some table data more than 1 time per second? What does he really need? Based on this, you should determine an optimization method.
Then you could choose:
Decrease timeout of the timer and affect several rows on each event. Seems you have to decrease timeout of the GUI timer in anyway.
Reserve the required number of rows periodically in advance, for example by model->setRowCount(n);
Temporarily disable table refresh by setUpdatesEnabled() method:
setUpdatesEnabled(false);
bigVisualChanges();
setUpdatesEnabled(true);
Sublass one of the Qt model classes and reimplement insertRows() or something more.
Read
Fetch More Example.
Qt Model/View Programming.

HelloThere. Okay so when the timer is set to 10ms, it adds and removes a row with every screen refresh.
The Qtimer is linked to the screen refresh rate. Which means if the timer goes off in less than 16ms, it will go off on every screen refresh.
When you add an item to the list, Qt runs additional UI events on the list for layout and view bounds. Because you're adding and removing with every screen refresh your list stuck in a constant loop of running these events. Not to mention your list is essentially being replaced entirely every 20 frames.
I'm not sure why you need to add items to the list so often, but maybe you should batch the adds and removes into a list and then every 100ms you can do a bulk add and remove? That would solve the issue.

Related

How to close the qt application when setQuitOnLastWindowClosed(false)

I wish to create a project including 3 windows: mainWindow, childWindow1, childWindow2, and only one window should show at a time. And I can switch between these windows.
So I have three tasks:
I place two buttons in the mainWindow and want to use them to make one of the child windows showing and the main window hiding.
And when I close the child window, I wish to show the main window.
When I close the main window, terminate the whole application.
I first had a problem:
If I close the child window, the application exit.
So I use the qApp.setQuitOnLastWindowClosed(false), and I got task 2 done.
But another problem occured:
If I close the main window, the program is still running.
Last problem:
How to show the child window in the task bar? It looks wired to run a program which can't be found in the taskbar.
I search everywhere I could, any help would be really appreciated!
main.cpp:
int main()
{
QApplication a(argc, argv);
a.setQuitOnLastWindowClosed(false);
MainWindow mainWindow;
mainWindow.show();
return a.exec();
}
mainWindow.cpp:
void mainWindow::button1Clicked()
{
this->hide();
childWindow1 = new ChildWindow1(this);
connect(childWindow1, &QMainWindow::destroyed, this, &QMainWindow::show);
childWindow1->setWindowModality(Qt::WindowModal);
childWindow1->show();
}
childWindow1.cpp
ChildWindow1::ChildWindow1(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
}
To achieve this you need to do the following:
In your main.cpp:
QApplication a(argc, argv);
a.setQuitOnLastWindowClosed(false);
MainWindow w;
QObject::connect(&w, &MainWindow::exited, &a, &QApplication::quit);
w.show();
return a.exec();
exited signal would be used to exit the application after closing the main window.
In your MainWindow you should reimplement closeEvent like this:
void MainWindow::closeEvent(QCloseEvent *event)
{
QMainWindow::closeEvent(event);
emit exited();
}
Don't forget to add exited() to MainWindow signals section;
Finally, code for creating new windows should be the following:
child1 = new ChildWindow;
child1->setAttribute(Qt::WA_DeleteOnClose);
connect(child1, &QObject::destroyed, this, &QWidget::show);
child1->show();
hide();
Note that I don't pass this as a parent for a ChildWindow, this allows it to appear in the task bar.
So, creating a new ChildWindow will hide the main window, closing the ChildWindow will delete it automatically (that's another reason why you don't need to pass parent to ChildWindow constructor) and closing MainWindow will close the whole application thanks to our exited signal.

Qt How to update GUI in the loop

I need to update screen to show how button is moving.
Here is my code:
void mouseReleaseEvent(QMouseEvent *event){
double b=(button->x()*event->y())/(button->x()-1);
double k=(button->y()-b)/button->x();
int time=0;
fnl=false;
if(event->button()==Qt::LeftButton)
{
while(!fnl)
{
int mX=button->x()-1;
int mY=k*(button->x()-1)+b;
button->setText(QString::number(b));
button->move(mX,mY);
QThread::sleep(1);
//here I need to update screen and show button
}
}
}
But it does not update GUI. It simply plays inside the loop.
Never use QThread::sleep() in the GUI thread, It will block the GUI thread from doing anything. Instead, You can use QTimer to schedule something to run at a later point in time. Also, Your slots/functions should be as short and optimized as possible in order to return control to the event loop and be able to handle other events that may happen.
You may want to have a look at this question for a similar problem.
The same technique can be applied here to solve the problem by replacing your while loop with a slot and a QTimer whose interval is set to 0. But Qt can do all that job using the animation framework, Here is an example of a button that gets moved when clicked:
#include <QtWidgets>
int main(int argc, char* argv[]){
QApplication a(argc, argv);
//create and show button
QPushButton button("Animated Button");
button.move(QPoint(100, 100));
button.show();
//create property animator object that works on the position of the button
QPropertyAnimation animation(&button, "pos");
//set duration for the animation process to 500ms
animation.setDuration(500);
//when the button is clicked. . .
QObject::connect(&button, &QPushButton::clicked, [&]{
//set the starting point of the animation to the current position
animation.setStartValue(button.pos());
//set the ending point to (250, 250)
animation.setEndValue(QPoint(250, 250));
//start animation
animation.start();
});
return a.exec();
}
Qt also provides many examples for using the animation framework. . .
A timer is the best option. If you want to use brute force you can call
qApp->processEvents();
inside your loop. Ugly but gets the job done.

How to receive event whenever a QWidget is added to QApplication's widget tree?

I want to inspect something application wide. Specifically I'd like to inspect every widget that is added to the application.
Similar thing can be done in JavaScript/HTML, where you can add DOM mutation listener which fires on DOM changes.
Can I listen on added widgets upon QApplication? Specifically, capture widgets added to QApplication object as children OR widgets added to any of the top level widgets or their children, subchildren, etc...
If not possible, what is best workaround available?
The most stable solution is likely to be to walk the object hierarchy every so often, starting with the QApplication as the root, and check each object with your predicate. This is going to be inefficient, which is why I asked for more information about what objects you actually want to query. On the other hand, for a test framework, you may not care so much about efficiency.
It might be possible to do something else, however. You can install event-filtering objects on any QObject, which define how they respond when they receive an event from Qt's event system. You could install an event filter on the root QApplication object (and recursively on any children created) that would check if the event is a QChildEvent and if added() returns true for it. If that's true, meaning a new child was added, you could then install this event filter onto that child as well. All other events would be passed on untouched. You can read more about installing event filters here. Essentially, a filter is a QObject subclass that defines the eventFilter() function, which returns true for events that should be filtered (stopped) and false otherwise. When this returns true, run your predicate on the newly-created object and install the same event filter on it.
One issue to be aware of with this is that the eventFilter() function only receives QObjects. You can learn if the object is a QWidget by calling isWidgetType(), but you don't know more than that. So as long as your predicate can make do with only methods and data defined for these general base classes, that should be OK.
As per #bnaecker's answer, here is some code:
AddChildEventFilter.h
#include <QObject>
class QEvent;
class AddChildEventFilter: public QObject {
Q_OBJECT
public:
AddChildEventFilter(QObject* parent=0);
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
};
AddChildEventFilter.cpp
#include "AddChildEventFilter.h"
#include <QEvent>
#include <QDebug>
#include <QWidget>
#include <QChildEvent>
AddChildEventFilter::AddChildEventFilter(QObject* parent):QObject(parent) {}
bool AddChildEventFilter::eventFilter(QObject* obj, QEvent* event) {
if(QWidget* widget = dynamic_cast<QWidget*>(obj)) {
if(QChildEvent* chevent = dynamic_cast<QChildEvent*>(event)) {
if(chevent->added()) {
QObject* child = chevent->child();
qDebug()<<"Child added: "<<child->metaObject()->className()<<"to"<<widget->metaObject()->className();
child->installEventFilter(new AddChildEventFilter(child));
}
}
}
return false;
}
Usage:
#include "AddChildEventFilter.h"
#include <QWidget>
#include <QApplication>
void PrintAllEvents()
{
for(QWidget* widget: QApplication::allWidgets()) {
widget->installEventFilter(new AddChildEventFilter(widget));
}
}

How do you access an objects properties?

Sorry if this is a stupid question. I'm not being lazy. I am following along with C++ GUI Programming with Qt 4 and one of the examples in the book diverges from how Qt is working for me.
It has me make a dialog and then hook up some signals and slots. Well that does not work and so I tried some reality checks. I can't even edit the objects properties manually.
Here is main
#include <QApplication>
#include <QDialog>
#include <iostream>
#include "ui_gotocelldialog.h"
using namespace std;
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Ui::GoToCellDialog ui;
QDialog *d = new QDialog;
ui.setupUi(d);
d->show();
return app.exec();
}
and here is my dialog .cpp file
#include "gotocelldialog.h"
#include "ui_gotocelldialog.h"
#include <iostream>
using namespace std;
GoToCellDialog::GoToCellDialog(QWidget *parent) :
QWidget(parent),
ui(new Ui::GoToCellDialog)
{
cout << "!!!!!!!!!!!!!!!!!!!!!"; // i never see this
ui->setupUi(this); // but if i comment out this it doesnt init
ui->okButton->setEnabled(true); // this does nothing
}
GoToCellDialog::~GoToCellDialog()
{
delete ui;
}
Here is the GoToCellDialog form in design mode
I am just trying to enable okButton, which is disabled by default. Also, if I cout << "abc"; it doesn't show up. Can you please shed some light on this?
There are several issues in your code.
You've created a GoToCellDialog class and implemented its constructor. But you don't create an object of this class. Of course, your constructor is not being executed. You need to replace QDialog to GoToCellDialog in your main function.
If you really need QDialog, not QWidget, you need to derive GoToCellDialog class from QDialog, not QWidget. There is an option for that while you create a designer form class in Qt Creator.
You create a Ui::GoToCellDialog in the main function, but another one is created in the GoToCellDialog class (note the ui private member). If you would instantiate your class, it would give you 2 instanses of Ui::GoToCellDialog which shouldn't be done. Remove Ui::GoToCellDialog from your main function.
I think you've confused GoToCellDialog class defined by you and Ui::GoToCellDialog class generated internally. Ui::GoToCellDialog ui is private member of GoToCellDialog, you don't need to instantiate it anywhere else.
See also: Calculator Form Example.

Please let me know its an functionality behaviour or not

I have a listview filled with items. By default, the 0th item will be selected.
If I try to navigate the list using the mobile keypad, it's not gaining focus - instead I need to use my mobile select key for focus. In this process my mobile left soft key gets changed to “Done”. Why is the "Done" menu appearing?
How do I provide default focus to the listview? And how do I avoid the display of “Done” at left soft key?
Here is my code:
#include "Test_Doco.h"
#include <QtGui>
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QListView *listui = new QListView();
listui->setSelectionMode(QAbstractItemView::SingleSelection);
listui->viewport()->setFocusPolicy(Qt::WheelFocus);
listui->viewport()->setFocus();
QStandardItemModel* listModel = new QStandardItemModel();
for(int i =0; i<10;i++)
{
QStandardItem *item1 = new QStandardItem("AOL");
listModel->appendRow(item1);
}
QModelIndex index = listModel->index(0,0);
listui->setCurrentIndex(index);
listui->setModel(listModel);
listui->showMaximized();
return a.exec();
}
Edit: I have updated the code. Please check it.
For the default focus, stop calling listui->viewport()->setFocus(); and call listui->setFocus() to give it focus when it is created.
As for the display of "Done", I'm not too sure, but you might need to post more code to show the dialog you are creating. Most have a set of default buttons or a button set to default. The "Done" key might be related to that. As seen here "Exit" is the softkey shown.
The issue is w.r.t Qt 4.6.2 and the issue is fixed in Qt 4.6.3

Resources