qTreeView scrollToBottom() ignored - c++

Overflowers!
I'm getting crazy trying using scrollTo() in qTreeView (or QListView) widget. To make my question simple I've reduced my code to a simple scrollToBottom() which I can't manage to use as well. Here is the mainWindow code:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <iostream>
#include <qfilesystemmodel.h>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QFileSystemModel *model = new QFileSystemModel(this);
QModelIndex modelRootIndex = model->setRootPath(QDir::rootPath());
ui->treeView->setModel(model);
ui->treeView->setRootIndex(modelRootIndex);
ui->treeView->scrollToBottom();
if(modelRootIndex.isValid()) std::cout << "validIndex" << std::endl;
}
MainWindow::~MainWindow()
{
delete ui;
}
As far as I know it's all ok (I get the "ValidIndex" string on standard output), but the widget doesn't scroll to bottom at all.
I'm using Desktop QT5.0.2 msvc2010 32bit.
Any Idea? Thanks. L

QFileSystemModel and the QFileSystemWatcher are kept up-to-date in a separate thread. Thus, simply setting the model on the tree view does not ensure that the model will be fully populated by the time you make the call to scrollToBottom. Use a single shot timer with a small delay to give the model time to populate.
QTimer::singleShot(1000, ui->treeView, SLOT(scrollToBottom()));
Additionally, (and I don't know your application, so this may or may not be true) it may be confusing to your users that the data they need to see is at the bottom of the view anyway. You may think about whether you can sort the view items in reverse order (thus having the data you need at the top) to avoid scrolling and potentially make the usage more intuitive.

This is because of the asynchronous way that QFileSystemModel works, and what appears to be a bug in Qt that was never fixed: https://bugreports.qt.io/browse/QTBUG-9326
You can work around it by doing QApplication::sendPostedEvents() immediately before the call to scrollTo(), but you must call them in a function that is connected to the directoryLoaded signal:
MyFileBrowser::MyFileBrowser(QWidget *parent) : QWidget(parent), ui(new Ui::MyFileBrowser) {
//...
connect(model, SIGNAL(directoryLoaded(QString)), this, SLOT(dirLoaded(QString)));
QModelIndex folderIndex = model->index("path/to/dir");
files->setCurrentIndex(folderIndex);
files->expand(folderIndex);
}
void WFileBrowser::dirLoaded(QString dir) {
if (dir == model->filePath(files->currentIndex())) {
QApplication::sendPostedEvents(); // booyah!!
files->scrollTo(files->currentIndex(), QAbstractItemView::PositionAtTop);
}
}

Related

Qt3D input handling system works differently depending on the place it was created

I was implementing a custom camera controller for Qt3DRender::QCamera and I faced a rather strange behavior of the Qt3DInput::QMouseHandler. Depending on the environment it was created it either responds to mouse events or not. There are two cases: create both the device and the handler in the MainWindow object or create them inside my camera controller class (it only works in the first case).
First case:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
auto* window = new Qt3DExtras::Qt3DWindow();
auto* window_container = QWidget::createWindowContainer(window, this, Qt::Widget);
setCentralWidget(window_container);
auto* scene = new Qt3DCore::QEntity();
window->setRootEntity(scene);
auto* mouse_device = new Qt3DInput::QMouseDevice(scene);
auto* mouse_handler = new Qt3DInput::QMouseHandler(scene);
mouse_handler->setSourceDevice(mouse_device);
connect(mouse_handler, &Qt3DInput::QMouseHandler::positionChanged,
[](Qt3DInput::QMouseEvent* event)
{
qDebug() << "I am actually printing to console!!!";
});
}
Second case:
class CustomCameraController final : public Qt3DCore::QNode
{
Q_OBJECT
public:
explicit CustomCameraController(Qt3DCore::QNode* parent = nullptr)
: Qt3DCore::QNode(parent),
mouse_device(new Qt3DInput::QMouseDevice(this)),
mouse_handler(new Qt3DInput::QMouseHandler(this))
{
mouse_handler->setSourceDevice(mouse_device);
connect(mouse_handler, &Qt3DInput::QMouseHandler::pressAndHold, this,
&CustomCameraController::MousePositionChanged);
}
public slots:
void MousePositionChanged(Qt3DInput::QMouseEvent* event)
{
qDebug() << "I am not printing anything...";
}
protected:
Qt3DInput::QMouseDevice* mouse_device;
Qt3DInput::QMouseHandler* mouse_handler;
};
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
auto* window = new Qt3DExtras::Qt3DWindow();
auto* window_container = QWidget::createWindowContainer(window, this, Qt::Widget);
setCentralWidget(window_container);
auto* scene = new Qt3DCore::QEntity();
window->setRootEntity(scene);
auto* controller = new CustomCameraController(scene);
}
All the unnecessary code was removed. The main.cpp file was automatically generated by Qt Framework
I searched all through the Qt documentation and found nothing that would help me in this scenario. I also noticed that if the device and the handler are initialized in the constructor with parent as the parameter it does not help. Furthermore, I tried to create the device and the handler in the MainWindow scope and pass them to the controller via some setter function but it does not help neither.
So the questions I want to ask: what is the proper way of using Qt3DInput::QMouseDevice and Qt3DInput::QMouseHandler? Is there a better workaround for implementing input handling for my custom camera controller?
Update 1:
You should add the controller class declaration to the header instead of main window source file. Here are all the includes and the qmake options you are going to need:
#include <Qt3DCore/QNode>
#include <Qt3DInput/QMouseDevice>
#include <Qt3DInput/QMouseHandler>
#include <Qt3DExtras/Qt3DWindow>
#include <Qt3DCore/QEntity>
3dinput 3dcore 3dextras
Looks like you just connected to different signals.
In first case you are using &Qt3DInput::QMouseHandler::positionChanged which will be sended quite often. In second one - &Qt3DInput::QMouseHandler::pressAndHold which will be sended when a mouse button is pressed and held down.
After fix both logging functions are called.

Loading QStringList value received from signal slot

In my qt c++ application a QStringList is sent from one cpp file(MainWIndow) to another cpp file(Dialog) via signal and slots mechanism! I want to display the elements in the qtringList on a combo box in the Dialog.ui when the interface gets loaded(no button click)!
following is my code
#include "dialog.h"
#include "ui_dialog.h"
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
for(int i=0;i<subMessages.size();i++){
ui->comboBox->addItem(subMessages[i]);
}
}
//slot which is used to get the qstringList
void Dialog::receiveSubMessages(QStringList List){
subMessages=List;
}
The QStringList is received successfully through the slot(already verified).Though I used a for loop and tried display (as in the code) nothing was displayed on the combo box! How can I fix this issue?
In order to get a working code you need to place your for llop inside you slot:
#include "dialog.h"
#include "ui_dialog.h"
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
}
//slot which is used to get the qstringList
void Dialog::receiveSubMessages(QStringList List){
ui->comboBox->addItems (List);
}
If you want to fill the comboBox with the contents of some QStringList upon Dialog construction then you should either pass this list as constructor argument:
Dialog::Dialog(QStringList List, QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
ui->comboBox->addItems (List);
}
or call Dialog::receiveSubMessages() right after Dialog object construction:
// somewhere in code, where you construct Dialog object
// ...
auto parent = SomeQWidgetDerivedClass (...);
QStringList someStringList {
"string 1",
"string 2"
};
// ...
auto dialog {new Dialog ()};
dialog->receiveSubMessages (someStringList);
// ...
The code that you have provided will never allow you to achieve the desired result because your for loop that is supposed to fill the QComboBox is executed on your Dialog object creation. At that point your subMessages is empty. The slot that you have created is not called before the constructor - the slot is just a member function that can only be called after the object is created. You can only call a member function without an object if the function itself is static, in which case it is definitely not a slot.
I did this answer rather to show you how to solve your problem. (I've the feeling that I even didn't understand what your actual problem is.)
When asking a question the chances to get a helpful answer increase if an MCVE is provided. (Please, follow this link. It teachs you really basic skills every S/W developer shouldmust have. I would also recommend to follow-up to How to debug small programs.)
As I did understand your problem I made such an MCVE. This is the code testQComboBox:
#include <QtWidgets>
int main(int argc, char **argv)
{
// build appl.
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// a QStringList with items
QStringList items;
items
<< QString::fromUtf8("first item")
<< QString::fromUtf8("second item")
<< QString::fromUtf8("third item");
// build GUI
QDialog dlg;
QVBoxLayout vBox;
QComboBox cmbBox;
cmbBox.addItems(items);
vBox.addWidget(&cmbBox);
dlg.setLayout(&vBox);
dlg.show();
app.exec();
// done
return 0;
}
I compiled it in VS2013 with Qt 5.9.2 on Windows 10 (64 bit). This is how it looks:
As you see, the usage of combobox is rather easy – no secret trap doors to use it. The actual code which is directly related to QComboBox is exactly 4 lines of code:
QVBoxLayout vBox;
QComboBox cmbBox;
cmbBox.addItems(items);
vBox.addWidget(&cmbBox);
And there is exactly one line of code where items are added to the QComboBox:
cmbBox.addItems(items);
Note:
I used QComboBox::addItems() instead of QComboBox::addItem() as the former has already a loop built-in to add a complete QStringList. It doesn't make any difference to the loop you used in your code Dialog::Dialog().
So, finally I dare to do the following statement:
If your combobox doesn't show items then:
You added items from an empty list.
Or, you forgot to add the items from list.
Or, something very weird happend.
I would always bet for 1. or 2. reason – the 3. reason is for real emergency cases only (e.g. broken Qt installation).
Concerning 3. reason:
I saw many questions where some lines of innocent looking code were presented which looked exactly as they should but were claimed to fail. And finally almost everytimes it showed that these lines worked fine when isolated in an MCVE but they didn't in the original code. How can this happen? Either there is some context which changes the behavior of the code in your original program or there is UB – undefined behavior. Something else does bad things but instead of crashing your process immediately (which would mean you're lucky) it goes on for a while corrupting the data more and more until finally everything breaks completely. Looking into the core-dump doesn't help at all. Therefore my recommendation of How to debug small programs.

How to update QGraphicsScene in a QMainWindow

I'm trying to create a simple Mandlebrot viewer in Qt, I have a Mainwindow with a QGraphicsScene in it, I generate the QImage with the picture and then I have some buttons that I'd like to use to navigate the image (Move, zoom, etc.)
I can get the initial image to appear, however I'm not sure how to tell it to rerender after I've changed any of the coordinates.
For the life of me I can't work out how to refresh the QMainWindow, or alternatively remove the QGraphicsScene from the MainWindow and make a call to render it.
QImage renderImage(//loads in global variables)
{
//calculates the image and returns a QImage
}
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QGraphicsScene *graphic = new QGraphicsScene( this );
graphic->addPixmap(QPixmap::fromImage(renderImage()));
ui->graphicsView->setScene(graphic);
}
void MainWindow::on_Left_clicked()
{
// Changes global variables and try to rerender the scene.
update(); //does nothing
}
UPDATE: Solved!
Thank you so much goug, that helped a lot. I'm new to Qt, so couldn't work out where the loop was that would update things. I added the code you suggested and it worked perfectly. Thanks :)
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QGraphicsScene *graphic = new QGraphicsScene( this );
pixmap_item = graphic->addPixmap(QPixmap::fromImage(renderImage()));
ui->graphicsView->setScene(graphic);
}
void MainWindow::on_Left_clicked()
{
// Changes global variables and try to rerender the scene.
centerR -= 0.1;
pixmap_item->setPixmap(QPixmap::fromImage(renderImage()));
}
You don't show any code that changes any coordinates, or anything for that matter. For the built-in graphics items such as QGraphicsPixmapItem, which is what you create by call addPixmap, you generally don't have to force anything. Those objects repaint themselves as needed when you change something via their member functions.
I suspect that where you're going wrong is that you may be believing that there's a connection between the pixmap and the QGraphicsPixmapItem that you created in the constructor. There isn't; so if it's the pixmap that you're changing, then you need to reapply that pixmap to the pixmap item. You'll need a new member in your class to track:
QGraphicsPixmapItem *pixmap_item_;
And change your constructor code to:
pixmap_item_ = graphic->addPixmap(QPixmap::fromImage(renderImage()));
Then whenever you've updated your pixmap, reapply that pixmap to the graphics item you've created in the constructor:
pixmap_item_->setPixmap (QPixmap::fromImage(renderImage()));
The setPixmap call will trigger the pixmap item to repaint itself; you don't have to call update() separately. If this isn't the issue, then we need to see more of your code.

Handling multiple windows in Qt

I use QStackedWidget to handle multiple windows/forms in a Qt application
according to this question.
I use this pattern:
Add all widget objects to the QStackedWidget in mainwindow.cpp
Get signal from sub-window on request to change window
mainwindow replaces the window (it updates the QStackedWidget in the right slot)
My question :
is this the right way to do this? I have a lot of windows in my applications and want to ensure this is the common best practice.
This pattern means that i have pointers to all of the windows in my main window.
piece of my code:
mainwindow.cpp:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
mnuWin = new Menu();
singlePulseWin = new SinglePulse();
repetitive = new Repetitive();
treatmentType = new TreatmentType();
//... and so on ....
connect(mnuWin,&Menu::updateMainWindowStackWidget,this,&MainWindow::onChangeWindowRequested);
connect(singlePulseWin,&SinglePulse::updateMainWindowStackWidget,this,&MainWindow::onChangeWindowRequested);
connect(repetitive,&Repetitive::updateMainWindowStackWidget,this,&MainWindow::onChangeWindowRequested);
connect(treatmentType,&TreatmentType::updateMainWindowStackWidget,this,&MainWindow::onChangeWindowRequested);
//... and so on ....
ui->pagesWidget->addWidget(mnuWin);
ui->pagesWidget->addWidget(singlePulseWin);
ui->pagesWidget->addWidget(repetitive);
ui->pagesWidget->addWidget(treatmentType);
//... and so on ....
ui->pagesWidget->setCurrentIndex(0);
}
void MainWindow::onChangeWindowRequested(int ind) //slot
{
ui->pagesWidget->setCurrentIndex(ind);
}
menu.cpp :
void Menu::on_btnMenuSinglePulse_clicked()
{
emit updateMainWindowStackWidget(1);
}
void Menu::on_btnMenuRepetitive_clicked()
{
emit updateMainWindowStackWidget(2);
}
void Menu::on_btnMenuBurst_clicked()
{
emit updateMainWindowStackWidget(3);
}
QStackedWidget is a good way to deal with Multi-window .why not put the buttons in the mainWindow ,so that you can change the pagesWidget's currentWidget more convenient instead of create signal-slot

Emit signal between two classes in Qt

A am trying to make a program that takes a signal from one class and with activation of that signal I want to activate a slot in second class.
In my case the first class is the mainWindow class, this class is subClass of QMainWindow, and in this class is the slot that I want to activate.
This is mainWindow.cpp:
mainWindow::mainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::mainWindow)
{
ui->setupUi(this);
}
mainWindow::~mainWindow()
{
delete ui;
}
void mainWindow::slotForStatusBarMessage(QString string)
{
statusBar()->showMessage(string);
}
The second class is the mainWidget class and it is a subclass of QWidget.
This is mainWidget.cpp:
mainWidget::mainWidget(QWidget *parent) :
QWidget(parent)
{
buttonAddNewRecord=new QPushButton("Add new record", this);
layoutButton=new QHBoxLayout();
layoutButton->addWidget(buttonAddNewRecord);
layoutMain=new QVBoxLayout();
layoutMain->addLayout(layoutButton);
functionDatabaseOpen();
setLayout(layoutMain);
}
The signal is emited from functionDatabaseOpen() function:
if (sqlDatabase.open())
{
emit signalForShowMessageInStatusBar("true");
}
else
{
emit signalForShowMessageInStatusBar("false");
}
I have made all the settings to the database but i didnt copy here because of space.
I have tried to make connection inside main.cpp but it seems it dosent work.
QObject::connect(mw, SIGNAL(signalForShowMessageInStatusBar(QString)), w, SLOT(slotForStatusBarMessage(QString)));
I cant make this signal/slot connection between classes to work. Can you give me any help.
If you have any question about the code please ask. Sorry for the bad english,I am not a native english speaker.
Thank you very much for your help.
You are emitting the signal from the constructor of mainWidget, and since the connection is only done after you return from that constructor, the signal doesn't go anywhere.
The easiest fix, not knowing what the rest of the code looks like, would be to move the call to functionDatabaseOpen() in main() after the signal/slot connection is made.