I want my application to start maximized because the entire computer will be dedicated to that task, so I found the showMaximized() function that fills that requirement, instead of the standard show(). The problem is that I want my widgets to be ratiometric to the resulting useful size of the app window so that we can change hardware or window managers at some point in the future without touching the app at all.
The best way that I've found on my own to do it is as follows, but it relies on a race condition that usually works but sometimes causes the entire UI to be crammed into the top-left corner. Restarting the app fixes it, but I'd rather not have to tell the non-technical users to do that.
main.h:
#ifndef MAIN_H
#define MAIN_H
#include <QtWidgets>
class ResizeFilter : public QObject
{
Q_OBJECT
public:
ResizeFilter();
protected:
bool eventFilter(QObject* obj, QEvent* event);
signals:
void ResizeEvent(QSize size);
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
static MainWindow* GetInstance();
private:
static MainWindow* singleton;
MainWindow();
~MainWindow();
ResizeFilter* filter;
private slots:
void FinishInit(QSize size);
};
#endif // MAIN_H
main.cpp:
#include "main.h"
#include <QApplication>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
MainWindow::GetInstance();
return app.exec();
}
ResizeFilter::ResizeFilter()
: QObject()
{
}
bool ResizeFilter::eventFilter(QObject* obj, QEvent* event)
{
if(event->type() == QEvent::Resize)
{
QResizeEvent* resizeEv = static_cast<QResizeEvent*>(event);
emit ResizeEvent(resizeEv->size());
}
return QObject::eventFilter(obj, event);
}
MainWindow* MainWindow::singleton = 0;
MainWindow* MainWindow::GetInstance()
{
if(singleton == 0)
{
singleton = new MainWindow();
}
return singleton;
}
MainWindow::MainWindow()
: QMainWindow(0)
{
filter = new ResizeFilter();
installEventFilter(filter);
showMaximized(); //do this before connecting so we miss the first resize event (wrong size) and catch the second (right size)
connect(filter, SIGNAL(ResizeEvent(QSize)), this, SLOT(FinishInit(QSize)));
}
void MainWindow::FinishInit(QSize size)
{
disconnect(filter, SIGNAL(ResizeEvent(QSize)), this, SLOT(FinishInit(QSize)));
delete filter;
filter = 0;
//build widgets based on size argument
}
MainWindow::~MainWindow()
{
}
I'm also open to a less rube-goldberg way of doing it. This looks a bit messy to me, but it's as compact as I think I can get it with my present knowledge.
(The singleton architecture is for another part of the project.)
You can get the geometry and size of the screen using the DesktopWidget.
Related
I have a Qt project (in Win10) that requires a non-member function in my MainWindow cpp file. It's a callback function that is triggered automatically from an external library when data from an attached sensor arrives. I like to display this data on a MainWindow widget (a plainTextEdit), but of course I cannot access any MainWindow elements from my callback function, and also no signal-slot concept works so far. Below is a minimal example of the problem:
MainWindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QMutex>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void displayData();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
MainWindow.cpp:
#include "mainwindow.h"
#include "ui_mainwindow.h"
// define global variables
QMutex mutex;
bool new_data_arrived = false;
QString newDataStr;
void newData() {
// This is a callback function triggered when data from a sensor attached via USB arrives
// I want to pass the result as a QString to MainWindow::displayData() to display it on screen
mutex.lock();
new_data_arrived = true;
newDataStr = "new data";
mutex.unlock();
};
void MainWindow::displayData()
{
mutex.lock();
if (new_data_arrived == true) {
new_data_arrived = false;
ui->plainTextEdit->appendPlainText(newDatastr);
}
mutex.unlock();
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
The function MainWindow::displayData() is then running in a loop (but that’s unimportant for my problem, so I removed it from the minimal example above…).
As mentioned, newData() cannot access any elements of MainWindow, which is clear. The only workaround I found so far is to use global variables as shown above, to get data from the non-member function newData() into the member function displayData(). I also included a mutex to avoid possible share violation (no idea if that's necessary, but it doesn't harm...).
Is there a more elegant way? In principle, what I need is either a signal-slot concept, but newData() cannot emit signals of MainWindow, or to get this into the function newData(), so that ui elements can be accessed.
You should use QMetaObject::invokeMethod to call a slot of a QObject from a non QObject slot (your callback funtion).
Here is an example:
class MainWindow : public QMainWindow
{
Q_OBJECT
//...
public slots:
void displayData( QString szNewData );
};
//Cpp MainWindow.cpp file
static MainWindow * mainWindowObj = nullptr;
void setMainWindowObj( MainWindow * ptr ){
mainWindowObj = ptr;
}
void newData() {
// This is a callback function triggered when data from a sensor attached via USB arrives
// I want to pass the result as a QString to MainWindow::displayData() to display it on screen
if( mainWindowObj == nullptr )
return;
QMetaObject::invokeMethod( mainWindowObj, //pointer to MainWindow instance (see main.cpp)
"displayData", //slot name
Qt::QueuedConnection, //connection type
Q_ARG( QString, newDataStr ) ); //Param passed to the slot
};
void MainWindow::displayData( QString szNewData )
{
ui->plainTextEdit->appendPlainText( szNewData );
}
//main.cpp file or where MainWindow instance is initialized
void setMainWindowObj( MainWindow * ptr );
int main( int argc, char *argv[] ){
//....
QApplication app(argc, argv);
//....
MainWindow mainWindow;
setMainWindowObj ( &mainWindow );
//....
}
I have a ComboBox and set it to be edited.
QComboBox *myCombo = new QComboBox(this);
myCombo->setEditable(true);
myCombo->setStyleSheet("QComboBox::down-arrow{image: url(:/bulb.png);}");
myCombo->setCursor( QCursor( Qt::PointingHandCursor ) );
So now when i click onto the editing field, nothing happen. But what I need is, when I click onto the bulb (which is the down-arrow), something (like a table or a dialog....) should be appeared. How can I recognize this click event in this case? I looked at the list of signals for combo box but could not find any signal for that.
By overwriting the mousePressEvent() method you must use hitTestComplexControl() method to know that QStyle::SubControl has been pressed by issuing a signal if it is QStyle::SC_ComboBoxArrow.
#include <QtWidgets>
class ComboBox: public QComboBox
{
Q_OBJECT
public:
using QComboBox::QComboBox;
signals:
void clicked();
protected:
void mousePressEvent(QMouseEvent *event) override{
QComboBox::mousePressEvent(event);
QStyleOptionComboBox opt;
initStyleOption(&opt);
QStyle::SubControl sc = style()->hitTestComplexControl(QStyle::CC_ComboBox, &opt, event->pos(), this);
if(sc == QStyle::SC_ComboBoxArrow)
emit clicked();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ComboBox w;
w.setEditable(true);
w.setStyleSheet("QComboBox::down-arrow{image: url(:/bulb.png);}");
QObject::connect(&w, &ComboBox::clicked, [](){
qDebug()<<"clicked";
});
w.show();
return a.exec();
}
#include "main.moc"
Although showPopup() is a possible option this can be called directly without the down-arrow being pressed, for example by calling it directly: myCombo->showPopup() so it is not the correct option.
A possible solution is to subclass QComboBox and reimplement showPopup() virtual method:
.h:
#ifndef COMBOBOXDROPDOWN_H
#define COMBOBOXDROPDOWN_H
#include <QComboBox>
#include <QDebug>
class ComboBoxDropDown : public QComboBox
{
public:
ComboBoxDropDown(QWidget *parent = nullptr);
void showPopup() override;
};
#endif // COMBOBOXDROPDOWN_H
.cpp:
#include "comboboxdropdown.h"
ComboBoxDropDown::ComboBoxDropDown(QWidget *parent)
: QComboBox (parent)
{
}
void ComboBoxDropDown::showPopup()
{
//QComboBox::showPopup();
qDebug() << "Do something";
}
As you can find (if you look at few of mine previous questions) I am pretty new with Qt. I am making "read-from-excel-file-push-to-some-DB" module.
I want to get path wich push button returns in main.cpp
So, long story short here's my code:
fb_test.h
#ifndef FB_TEST_H
#define FB_TEST_H
#include <QtGui/QMainWindow>
#include "ui_fb_test.h"
class FB_test : public QMainWindow
{
Q_OBJECT
public:
FB_test(QWidget *parent = 0, Qt::WFlags flags = 0);
~FB_test();
private:
Ui::FB_testClass ui;
public slots:
QString on_pushButton_clicked();
};
#endif // FB_TEST_H
fb_test.cpp
#include <QtGui>
#include "fb_test.h"
FB_test::FB_test(QWidget *parent, Qt::WFlags flags)
: QMainWindow(parent, flags)
{
ui.setupUi(this);
}
FB_test::~FB_test()
{
}
QString FB_test::on_pushButton_clicked()
{
QString path;
path = QFileDialog::getOpenFileName(
this,
"Choose a file to open",
QString::null,
QString::null);
return path;
}
main.cpp
#include <QApplication>
#include <QtGui>
#include <QtSql>
#include <QAxObject>
#include <QAxWidget>
#include "fb_test.h"
bool initExcel(QAxObject* &excelApp);
void getTableHeaders(QAxObject* _worksheet, QAxObject* _excel);
bool readExcelFile(QAxObject* excel, QString& file_path, QString& selected_list);
void getTableHeaders(QAxObject* _worksheet, QAxObject* _excel);
//....
//here's methods above implementation
//....
void excelTest(){
QAxObject* excel;
QString path = QString("C:\\databases\\oilnstuff.xls");//gonna use GUI choose
QString list = QString("list1");
if(initExcel(excel)){
if (readExcelFile(excel, path, list)){
//
}else{
//error output
}
}
excel->dynamicCall("Quit");
delete excel;
}
int main(int argc, char** argv)
{
QApplication app(argc, argv);
QComboBox myCombo;
FB_test *test = new FB_test;
test->show();
excelTest();
return app.exec();
}
so here, instead of such right part
QString path = QString("C:\\databases\\oilnstuff.xls");
I want to get what QString FB_test::on_pushButton_clicked() returns.
How to make such thing?
UPD:
well, such way not works
QString mypath = test->ui.pushButton();
P.S.
Oh and btw, I am not sure about the way I degin this module.
Maybe I should move all working stuff from main.cpp into FB_test.cpp and get what button returns there and only call show() in main.cpp?
You could emit a signal, that a new path was selected and connect it to another component's slot for loading this file. With this, you can get a loose coupling between classes.
Signals are like:"Hey, something has happended. But I don't matter about who cares about that".
Slots are like: "I do that work, but I don't care about who wants me to do that".
fb_test.h
class FB_test : public QMainWindow
{
Q_OBJECT
public:
FB_test(QWidget *parent = 0, Qt::WFlags flags = 0);
~FB_test();
signals:
void newPathSelected(const QString& path);
private:
Ui::FB_testClass ui;
public slots:
void on_pushButton_clicked();
};
fb_test.cpp
// ...
void FB_test::on_pushButton_clicked()
{
QString path;
path = QFileDialog::getOpenFileName(
this,
"Choose a file to open",
QString::null,
QString::null);
if (!path.isEmpty())
emit newPathSelected(path);
}
An an excel class could look like this:
class MyExcelClass : public QObject
{
Q_OBJECT
public:
MyExcelClass(QObject *parent = 0);
~MyExcelClass();
public slots:
void readExcelSheet(const QString& path);
};
If you have instances of both classes in your main.cpp, this file would look like this:
int main(int argc, char** argv)
{
QApplication app(argc, argv);
FB_test fbTest;
MyExcelClass *excelClass = new MyExcelClass(&fbTest);
connect(&fbTest, SIGNAL(newPathSelected(QString)),
excelClass, SLOT(readExcelSheet(QString));
return app.exec();
}
Note that fbTest in main isn't dynamically allocated with new because this will result in a memory leak when main finishes. The excelClass object will get a pointer to the fbTest object in its constructor, this will ensure, that excelClass will get deleted as child object of fbTest.
If both the signal and the slot would be in the same class, you have to connect these in the constructor like this:
connect(this, SIGNAL(newPathSelected(QString)),
this, SLOT(readExcelSheet(QString));
Using Qt I create a QMainWindow and want to call a function AFTER the windows is shown. When I call the function in the constructor the function (a dialog actually) get's called before the window is shown.
If you want to do something while the widget is made visible, you can override QWidget::showEvent like this:
class YourWidget : public QWidget { ...
void YourWidget::showEvent( QShowEvent* event ) {
QWidget::showEvent( event );
//your code here
}
After analyzing the solutions above, it turns that all of them, including the heavily upvoted ones, are faulty.
Many recommend something like this:
class MyWidget : public QWidget {
// ...
};
void MyWidget::showEvent(QShowEvent* event) {
QWidget::showEvent(event);
DoSomething();
}
void MyWidget::DoSomething() {
// ...
}
This works as long as there is no QCoreApplication::processEvents(); in DoSomething. If there is one, it processes all events in the queue, including the QShowEvent which called MyWidget::showEvent in the first place. When it gets to the original QShowEvent, it calls MyWidget::showEvent again, causing an infinite loop.
If this happens, there are three solutions:
Solution 1. Avoid calling processEvents in MyWidget::DoSomething, instead call update or repaint when necessary. If DoSomething calls something else, these functions should avoid processEvents also.
Solution 2. Make DoSomething a slot, and replace direct call to DoSomething() by
QTimer::singleShot(0, this, SLOT(DoSomething()));
Since zero interval timer fires only when after all events in the queue are processed, it will process all events, including the original QShowEvent, remove them from the queue, and only then call DoSomething. I like it the most.
Since only zero interval timer fires only when after all events in the queue are processed, you should not try to "improve" it by lengthening the interval, for instance
QTimer::singleShot(50, this, SLOT(DoSomething())); // WRONG!
Since 50 ms is usually enough time for processing events in the queue, that would usually work, causing an error which is hard to reproduce.
Solution 3. Make a flag which prevents calling DoSomething the second time:
class MyWidget : public QWidget {
// ...
};
void MyWidget::showEvent(QShowEvent* event) {
if (is_opening)
return;
is_opening = true;
QWidget::showEvent(event);
DoSomething();
is_opening = false;
}
void MyWidget::DoSomething() {
// ...
}
Here, is_opening is a boolean flag which should be initialized as false in constructor.
try this:
in mainwindow.h:
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
protected:
void showEvent(QShowEvent *ev);
private:
void showEventHelper();
Ui::MainWindow *ui;
}
in mainwindow.cpp:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
void MainWindow::showEvent(QShowEvent *ev)
{
QMainWindow::showEvent(ev);
showEventHelper();
}
void MainWindow::showEventHelper()
{
// your code placed here
}
Follow Reza Ebrahimi's example, but keep this in mind:
Do not omit the 5th parameter of connect() function which specifies the connection type; make sure it to be QueuedConnection.
I.E.,
connect(this, SIGNAL(window_loaded), this, SLOT(your_function()), Qt::ConnectionType(Qt::QueuedConnection | Qt::UniqueConnection));
I believe that you'd achieve what you need if you do it this way.
There are several types in signal-slot connections: AutoConnection, DirectConnection, QueuedConnection, BlockingQueuedConnection (+ optional UniqueConnection). Read the manual for details. :)
Assuming you want to run your code in the UI thread of the window after the window has been shown you could use the following relatively compact code.
class MainWindow : public QMainWindow
{
// constructors etc omitted.
protected:
void showEvent(QShowEvent *ev)
{
QMainWindow::showEvent(ev);
// Call slot via queued connection so it's called from the UI thread after this method has returned and the window has been shown
QMetaObject::invokeMethod(this, "afterWindowShown", Qt::ConnectionType::QueuedConnection);
}
private slots:
void afterWindowShown()
{
// your code here
// note this code will also be called every time the window is restored from a minimized state
}
};
It does invoke afterWindowShown by name but that sort of thing is fairly common practice in Qt. There are ways of avoiding this but they're a bit more verbose.
Note that this code should work for any QWidget derived class, not just QMainWindow derived classes.
In theory it might be possible for a very quick user to invoke some sort of action on the UI of the displayed window before afterWindowShown can be called but it seems unlikely. Something to bear in mind and code defensively against perhaps.
I found a nice answer in this question which works well, even if you use a Sleep() function.
So tried this:
//- cpp-file ----------------------------------------
#include "myapp.h"
#include <time.h>
#include <iosteream>
MyApp::MyApp(QWidget *parent)
: QMainWindow(parent, Qt::FramelessWindowHint)
{
ui.setupUi(this);
}
MyApp::~MyApp()
{
}
void MyApp::showEvent(QShowEvent *event) {
QMainWindow::showEvent(event);
QTimer::singleShot(50, this, SLOT(window_shown()));
return;
}
void MyApp::window_shown() {
std::cout << "Running" << std::endl;
Sleep(10000);
std::cout << "Delayed" << std::endl;
return;
}
//- h-file ----------------------------------------
#ifndef MYAPP_H
#define MYAPP_H
#include <QtWidgets/QMainWindow>
#include <qtimer.h>
#include <time.h>
#include "ui_myapp.h"
class MyApp : public QMainWindow
{
Q_OBJECT
public:
MyApp(QWidget *parent = 0);
~MyApp();
protected:
void showEvent(QShowEvent *event);
private slots:
void window_shown();
private:
Ui::MyAppClass ui;
};
#endif // MYAPP_H
I solved it without a timer using Paint event. Works for me at least on Windows.
// MainWindow.h
class MainWindow : public QMainWindow
{
...
bool event(QEvent *event) override;
void functionAfterShown();
...
bool functionAfterShownCalled = false;
...
}
// MainWindow.cpp
bool MainWindow::event(QEvent *event)
{
const bool ret_val = QMainWindow::event(event);
if(!functionAfterShownCalled && event->type() == QEvent::Paint)
{
functionAfterShown();
functionAfterShownCalled = true;
}
return ret_val;
}
The best solution for me is count once paint event:
.H
public:
void paintEvent(QPaintEvent *event);
.CPP
#include "qpainter.h"
#include <QMessageBox> // example
int contPaintEvent= 0;
void Form2::paintEvent(QPaintEvent* event)
{
if (contPaintEvent ==0 )
{
QPainter painter(this);
QMessageBox::information(this, "title", "1 event paint"); // example
// actions
contPaintEvent++;
}
}
Reimplement method void show() like this:
void MainWindow::show()
{
QMainWindow::show();
// Call your special function here.
}
I have time consuming image loading (image is big), also some operations on it are done when loading. I do not want to block application GUI.
My idea is to load image in another thread, emit signal that image is loaded and then redraw view with this image.
My approach:
void Window::loadImage()
{
ImageLoader* loaderThread = new ImageLoader();
connect(loaderThread,SIGNAL(imageLoaded()),this,SLOT(imageLoadingFinished());
loaderThread->loadImage(m_image, m_imagesContainer, m_path);
}
void Window::imageLoadingFinished()
{
m_imagesContainer->addImage(m_image);
redrawView();
}
class ImageLoader : public QThread
{
Q_OBJECT
public:
ImageLoader(QObject *parent = 0) : m_image(NULL), m_container(NULL)
void loadImage(Image* img, Container* cont, std::string path)
{
m_image = img;
m_container = cont;
...
start();
}
signals:
void imageLoaded();
protected:
void run()
{
//loading image and operations on it
emit imageLoaded();
}
protected:
Image* m_image;
Container* m_container;
}
I was basing on quedcustomtype example from Qt writing this code. When googling and searching in stackoverflow I've also find out that subclassing QThread is not a good idea.
So the question is what is the correct way to do it? As I said I want non blocking GUI, loading and operations done in another thread and signal which says loading is finished. After signal is emited view should be redrawn.
I don't know much about multithreading however think to understand or have sufficient knowledge to understand basic ideas.
Use QtConcurent framework.
#include <QtConcurentRun>
#include <QFutureWatcher>
//....
class Window: public QWidget /*or something*/
{
//....
private:
QFutureWatcher<QImage> _wacther; //this object will signal when loading finished
};
//...
void Window::loadImage()
{
connect(&_watcher, SIGNAL(finished(), SLOT(finishLoading());
_wacther.setFuture(QtConcurent::run<QImage>(this, &Window::doLoadImage));
}
QImage Window::doLoadImage() //this function will be executed in the new thread. SHOULD BE Thread Safe
{
return someImage;
}
void window::finishLoading()
{
QImage result = _watcher.result();
}
I suppose this is the best way to go:
#include <QApplication>
#include <QLabel>
#include <QThread>
class ImageLoader : public QObject
{
Q_OBJECT
public:
ImageLoader() : QObject() {
moveToThread(&t);
t.start();
}
~ImageLoader() {
qDebug("Bye bye!");
t.quit();
t.wait();
}
void requestImage(QString absPath) {
QMetaObject::invokeMethod(this, "loadImage", Q_ARG(QString, absPath));
}
public slots:
void loadImage(QString absPath) {
// Simulate large image.
QImage image(absPath);
sleep(10);
qDebug("Image loaded!");
emit imageReady(image);
}
signals:
void imageReady(QImage image);
private:
QThread t;
};
class MyLabel : public QLabel
{
Q_OBJECT
public:
MyLabel() : QLabel() {}
void mousePressEvent(QMouseEvent* ev) {
Q_UNUSED(ev);
qDebug("I got the event!");
}
public slots:
void setImage(QImage image) {
setPixmap(QPixmap::fromImage(image));
resize(image.width(), image.height());
qDebug("Image shown!");
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyLabel label;
label.show();
ImageLoader imageLoader;
QObject::connect(&imageLoader, SIGNAL(imageReady(QImage)), &label, SLOT(setImage(QImage)));
imageLoader.requestImage(some_abs_path);
return a.exec();
}
#include "main.moc"
I also like QtConcurrent, but consider that its use is somehow discouraged: http://www.mail-archive.com/development#qt-project.org/msg07794.html.