I have a QSplashScreen made that runs through a bunch of images to resemble a gif and closes when the main window opens. This works fine on windows, but when I run it on mac it gets funky. Instead of closing when it's gone through all the pictures like it should it starts going through the images in revers order when clicked.
Here is header (splashscreen.h):
class SplashScreen : public QObject
{
Q_OBJECT
public:
explicit SplashScreen(QObject *parent = 0);
private:
QString filename0;
QString filename1;
QString filename;
int frameNum;
Qt::WindowFlags flags;
private slots:
void showFrame(void);
};
and here is implementation (splashscreen.cpp):
SplashScreen::SplashScreen(QObject *parent) :
QObject(parent)
{
QTimer *timer = new QTimer;
timer->singleShot(0, this, SLOT(showFrame()));
frameNum = 0;
}
void SplashScreen::showFrame(void)
{
QSplashScreen *splash = new QSplashScreen;
QTimer *timer = new QTimer;
frameNum++;
QString filename0 = ""; //first and second half of file path
QString filename1 = "";
splash->showMessage("message here", Qt::AlignBottom, Qt::black);
filename = filename0 + QString::number(frameNum) +filename1; // the number for the file is added here
splash->setPixmap(QPixmap(filename)); // then shown in the splashscreen
splash->show();
if (frameNum < 90)
{
timer->singleShot(75, this, SLOT(showFrame()));
}
else if (frameNum == 90)
{
splash->close();
flags |= Qt::WindowStaysOnBottomHint;
return;
}
}
and here is main file (main.cpp):
int main(int argc, char *argv[])
{
Application app(argc, argv);
SplashScreen *splash = new SplashScreen;
QSplashScreen *splashy = new QSplashScreen;
View view; //main window
QTimer::singleShot(10000, splashy, SLOT(close()));
splashy->hide();
QTimer::singleShot(10000, &view, SLOT(show()));
return app.exec();
}
I've got several different ways to close the splash screen but none of them seem to be working. Is this a bug in macs or is there something I can fix in my code?
There are created 90 different QSplashScreen objects. Only the 90th object is closed.
So, it is the main reason for observed behavior.
If you create a new splash screen QSplashScreen *splash = new QSplashScreen; for each frame then the previous screen should be closed and deleted. It is possible to store QSplashScreen *splash as a class member. Otherwise there is a memory leak.
You may consider to use only one instance of QSplashScreen splash as a private SplashScreen class member. The rest of the code may be unchanged (after replacement splash-> by splash.). It will be automatically deleted with deletion of SplashScreen.
Other issues
QTimer should not be instantiated each time to use its static member function. Each call of showFrame() and SplashScreen() creates a new QTimer object that is never deleted and never used.
The splashy also does not make any sense in main(). All three lines related to splashy may be deleted. Actual splash screens are triggered by new SplashScreen. By the way, it is also a leak. In that case it makes sense to instantiate it directly on the main() function stack: SplashScreen splash;
It looks that the private member SplashScreen::flags is not used.
Related
I have the following codes in my Qt project with the following main:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
The class Widget is a QWidget object with the following constructor:
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
m_Scene = new QGraphicsScene(this);
QGraphicsLinearLayout* layout = new
QGraphicsLinearLayout(Qt::Orientation::Vertical);
for(int i = 0; i < 10; i++)
{
std::string name = "m_" + std::to_string(i);
GraphicsTextItem* item = new GraphicsTextItem(nullptr, QString(name.c_str()));
layout->addItem(item);
}
QGraphicsWidget* list = new QGraphicsWidget;
list->setPos(0,0);
list->setLayout(layout);
m_Scene->addItem(list);
QGraphicsView* view = new QGraphicsView(this);
view->setScene(m_Scene);
// Why one of these lines must be uncommented?
//m_Scene->setSceneRect(0, 0, 1920, 768);
//QVBoxLayout *ttopLayout = new QVBoxLayout;
//ttopLayout->addWidget(view);
//setLayout(ttopLayout);
}
GraphicsTextItem is just a QGraphicsWidget for displaying text:
class GraphicsTextItem : public QGraphicsWidget
{
public:
QString m_Name;
QColor m_Color;
public:
GraphicsTextItem(QGraphicsItem * parent = nullptr, const QString& name = QString());
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
{
Q_UNUSED(option)
Q_UNUSED(widget)
QFont font("Times", 10);
painter->setFont(font);
painter->setPen(m_Color);
painter->drawText(0, 0, m_Name);
}
};
My question is that why my scene is not shown. I must either define a SceneRect or define a layout on my widget?
I made an even shorter MCVE for demonstration:
#include <QtWidgets>
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
QWidget qWinMain;
qWinMain.resize(320, 240);
QFrame qFrm(&qWinMain);
qFrm.setFrameStyle(QFrame::Box | QFrame::Raised);
qFrm.setLineWidth(0);
qFrm.setMidLineWidth(1);
qWinMain.show();
return app.exec();
}
compiled and started in cygwin64. This is how it looks:
There is a main window (with window manager decoration).
There is a child QFrame.
The child QFrame is "pressed" into the upper left corner.
How comes?
What QWidget does ensure: Child widgets are rendered (in front) when QWidget is rendered.
What QWidget is not (directly) responsible for: Layouting child widgets.
For this, a layout manager has to be plugged in:
#include <QtWidgets>
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
QWidget qWinMain;
qWinMain.resize(320, 240);
QVBoxLayout qVBox(&qWinMain);
QFrame qFrm(&qWinMain);
qFrm.setFrameStyle(QFrame::Box | QFrame::Raised);
qFrm.setLineWidth(0);
qFrm.setMidLineWidth(1);
qVBox.addWidget(&qFrm);
qWinMain.show();
return app.exec();
}
compiled and started again in cygwin64. This is how it looks:
Now, the QFrame qFrm is filling the QWidget qWinMain nicely. Resize events received in qWinMain will be forwarded to the layout manager qVBox which will re-layout the children of qWinMain (i.e. qFrm) again.
I strongly believe OP's GraphicsView is just not visible because it has no minimal size requirement. (It's just to small to be visible.)
Hence, adding a layout manager ensures that the GraphicsView fills the parent widget client area. Resizing the contents of GraphicsView (by m_Scene->setSceneRect(0, 0, 1920, 768);) is yet another option to fix this, albeit the worse one.
Finally, the link to Qt Doc.: Layout Management.
Layout Management
The Qt layout system provides a simple and powerful way of automatically arranging child widgets within a widget to ensure that they make good use of the available space.
Introduction
Qt includes a set of layout management classes that are used to describe how widgets are laid out in an application's user interface. These layouts automatically position and resize widgets when the amount of space available for them changes, ensuring that they are consistently arranged and that the user interface as a whole remains usable.
All QWidget subclasses can use layouts to manage their children. The QWidget::setLayout() function applies a layout to a widget. When a layout is set on a widget in this way, it takes charge of the following tasks:
Positioning of child widgets
Sensible default sizes for windows
Sensible minimum sizes for windows
Resize handling
Automatic updates when contents change:
Font size, text or other contents of child widgets
Hiding or showing a child widget
Removal of child widgets
I am kinda new to QT so i am wondering why is this invalid:
I have a progress bar and i want to update it by using a class that inherits QThread.
void mt::run(QProgressBar * asd){
for(int i = 0;i<100;i++){
asd->setValue(i);
QThread::sleep(100);
}
}
mt is a class that inherits QThread. run is overloaded with a QProgressBar argument. My main UI thread will send it's progressbar like this m.run(ui->progressBar);. If i will remove the QThread::sleep(100); then it will work fine but i won't be able to see the increment because the thread will be done so fast. But if i will put a little delay, my screen won't appear at all.
You can access and update GUI elements from the main thread only.
If you want to prepare some data inside a custom thread, you should use the signals/slots mechanism to send that data to your widgets.
Here's a basic example:
class MyThread : public QThread
{
Q_OBJECT
public:
MyThread(QObject *parent = 0);
signals:
void valueChanged(int value);
protected:
void run();
}
void MyThread::run()
{
for (int i = 0; i < 100; i++)
{
emit valueChanged(i);
QThread::sleep(100);
}
}
MyWidget::MyWidget(QWidget *parent) :
QWidget(parent)
{
QHbovLayout *layout = new QHbovLayout(this);
QProgressBar *pb = new QProgressBar;
layout->addWidget(pb);
MyThread *t = new MyThread(this);
connect(t, SIGNAL(valueChanged(int)), pb, SLOT(setValue(int)));
t->start();
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// we are in the main thread here, so we can create a widget
MyWidget w;
w.show();
return a.exec();
}
QThread::sleep(100);
You are telling it to sleep for a 100 seconds - that's quite a long time. Perhaps you meant QThread::msleep(100)?
I acquire images from a camera by using a SDK which returns the data in an unsigned char array. However this SDK doesn't offer functionality to display the data, so I tried to implement this by using Qt 4.8 under Ubuntu 12.04.
At the moment my code looks like this:
#include <QtGui/QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsPixmapItem>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
QGraphicsView view(&scene);
while (..) // condition which breaks after a number of images where grabbed
{
// Get the image from the third party SDK into an unsigned char array
// named pImageBuffer
QImage image ((unsigned char *) pImageBuffer,
GetWidth(),
GetHeight(),
QImage::Format_Indexed8);
QVector<QRgb> colorTable(256);
for(int i=0;i<256;i++)
colorTable[i] = qRgb(i,i,i);
image.setColorTable(colorTable);
QPixmap pixmap = QPixmap::fromImage(image);
QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap);
scene.addItem(item);
view.show();
a.exec();
}
}
This works like expected, the images are displayed properly. However a.exec() blocks the main thread until I close the QtWindow.
Is there any easy way to modify this, so the window stays open all the time and just updates the displayed image? Performance doesn't matter at all at the moment, but I need to keep the code simple.
While a call to QApplication::processEvents will work, it's just a hack and not the best solution.
Ideally the image grabber should run on a separate thread as an object derived from QObect. This object emits signals of the images that it receives, which are received by an object on the main thread.
The receiving object can then set the image on the QGraphicsPixmapItem object.
Note that the code in the question creates a new QGraphicsPixmapItem for every image that is received from the grabber. Assuming you're wanting to create an animated image, you should only be creating and adding a single QGraphicsPixmapItem to the scene.
Using QThread is very easy and if you've not done it before, I suggest you read this article, which clearly explains what to do, with example code.
class ImageGrabber
{
Q_OBJECT
public:
ImageGrabber(QPixmapItem* item) : _item(item)
{
connect( &timer, SIGNAL(timeout()), this, SLOT(grabImage()) )
_timer.start(33); // 33 ms between timeouts.
}
public slots:
void grabImage()
{
// Update image
QImage image(...);
_item->setPixmap( QPixmap::fromImage(image) );
}
private:
QPixmapItem* _item;
QTimer _timer;
};
int main(...)
{
QApplication a(argc,argv);
...
view.show();
QGraphicsPixmapItem* pixmapItem = scene.addPixmap(QPixmap());
ImageGrabber ig(pixmapItem);
return a.exec();
}
I'm writing a Qt GUI Application, but there's a strange error i can't figure out;
Here's the whole code:
main.cpp
#include "LevelEditor.h"
int main(int argc, char* argv[])
{
LevelEditor editor(argc, argv);
editor.go();
return 0;
}
LevelEditor.h
#ifndef LEVELEDITOR_H
#define LEVELEDITOR_H
#include <QtGui>
class LevelEditor
{
public:
LevelEditor(int argc, char* argv[]);
~LevelEditor();
void go();
protected:
QApplication* app;
QMainWindow* main_window;
QMenuBar* menu_bar;
QStatusBar* status_bar;
QWidget* central;
QMenu* menu_entry[3];
QFrame* about_frame;
QList<QAction*> file_actions;
QList<QAction*> edit_actions;
QList<QAction*> help_actions;
private:
};
#endif // LEVELEDITOR_H
LevelEditor.cpp
#include "LevelEditor.h"
#include <QStatusBar>
LevelEditor::LevelEditor(int argc, char* argv[])
{
//initialise main objects
app = new QApplication(argc, argv);
main_window = new QMainWindow();
menu_bar = main_window->menuBar();
status_bar = main_window->statusBar();
central = main_window->centralWidget();
about_frame = new QFrame();
//initialise menu entries and actions
menu_entry[0] = new QMenu(); //file
menu_entry[1] = new QMenu(); //edit
menu_entry[2] = new QMenu(); //about
//creating and connecting events to action
menu_entry[0]->setTitle("File");
file_actions.append(new QAction("New", menu_entry[0]));
file_actions.append(new QAction("Open", menu_entry[0]));
file_actions.append(new QAction("Save", menu_entry[0]));
file_actions.append(new QAction("Quit", menu_entry[0]));
QObject::connect(file_actions.back(), SIGNAL(triggered()), app, SLOT(quit()));
QObject::connect(file_actions.back(), SIGNAL(hovered()), status_bar, SLOT(showMessage("Quit this App"));
menu_entry[0]->addActions(file_actions);
menu_bar->addMenu(menu_entry[0]);
//edit menu
menu_entry[1]->setTitle("Edit");
edit_actions.append(new QAction("Cut", menu_entry[1]));
edit_actions.append(new QAction("Copy", menu_entry[1]));
edit_actions.append(new QAction("Paste", menu_entry[1]));
menu_entry[1]->addActions(edit_actions);
menu_bar->addMenu(menu_entry[1]);
//help menu
help_actions.append(new QAction("About", menu_entry[2]));
QObject::connect(help_actions.back(), SIGNAL(triggered()), about_frame, SLOT(show()));
menu_entry[2]->setTitle("Help");
menu_entry[2]->addActions(help_actions);
menu_bar->addMenu(menu_entry[2]);
about_frame->resize(400,300);
}
LevelEditor::~LevelEditor()
{
//dtor
}
void LevelEditor::go()
{
//nothing
main_window->showMaximized();
menu_bar->show();
status_bar->show();
app->exec();
}
This code compiles fine without errors.
Anyway, the debug console gives me a warning
QObject::connect : NO such slot &QStatusBar::showMessage("Quit this App")
The problem seems related to this line:
QObject::connect(file_actions.back(), SIGNAL(hovered()), status_bar, SLOT(showMessage("Quit this App"));
I've searched in "QStatusBar.h" for the showMessage function and it is declared, but can't be called neither with "." nor "->" (even if it's public). Also tried this:
QObject::connect(file_actions.back(), SIGNAL(hovered()), status_bar, SLOT(showMessage("Quit this App", 0));
and this:
QObject::connect(file_actions.back(), SIGNAL(hovered()), status_bar, SLOT(QStatusBar::showMessage("Quit this App"));
But to no avail, it just won't recognise the function.
Am i missing something here?
Thanks in advance.
EDIT: Solved, i was taking the hard way to show a status text instead of using QAction::setStatusTip, my bad.
You can't connect a signal to a slot with different signature. And you are not even using the proper connect syntax. The SLOT part should be:
SLOT(showMessage(const QString &))
It's to tell the meta object system what type(s) of parameters to send to the slot, not what concrete data to send.
In your case, you can't connect a signal with no parameter to a slot that expects one. You can achieve that by connecting the signal to your own slot and then call QStatusBar::showMessage from there.
You could use QSignalMapper to do what you want:
QSignalMapper * mapper = new QSignalMapper(this);
QObject::connect(file_actions.back(), SIGNAL(hovered()), mapper, SLOT(map()));
mapper->setMapping(file_actions.back(), "Quit this app");
connect(mapper, SIGNAL(mapped(const QString &)), statusBar, SLOT(showMessage(const QString &));
Using QSignalMapper allows you, to simply add another "hovered" messages without creating new slots for each. Simply for all other cases just use:
mapper->setMapping(yourAction/Button/Whater, "Your status message");
QObject::connect(yourAction/Button/Whater, SIGNAL(hovered/Other signal()), mapper, SLOT(map()))
According to the QT4 docs QDialog's sizeGrip is disabled by default, however mine has one anyways. So I try running setSizeGripEnabled(false) and I still have one. So, something else must be causing this, but I don't know what. If it matters my dialog box currently has no parent because I'm designing/testing it. I don't see why that should matter, but just mentioning it in case it does for some reason. Here's my full code:
#include "QtGui"
//#include "clposter.h"
void add_new_account()
{
// CHECK::Make sure this process is destroyed when it's supposed to be
// TODO::connect signals to slots
//
// Create Dialog box to add user account
QDialog *accountDialog = new QDialog();
accountDialog->setModal(true);
accountDialog->setWindowTitle("Add New Account");
accountDialog->setSizeGripEnabled(false);
// Create Main Layout
QVBoxLayout *mainVBox = new QVBoxLayout(accountDialog);
QLabel *accountNameLabel = new QLabel(accountDialog);
accountNameLabel->setText("Account:");
QLineEdit *accountName = new QLineEdit(accountDialog);
accountName->setMinimumWidth(250);
QLabel *accountPassLabel = new QLabel(accountDialog);
accountPassLabel->setText("Password:");
QLineEdit *accountPass = new QLineEdit(accountDialog);
accountPass->setEchoMode(QLineEdit::Password);
// NOTE::May want to use standard dialog buttons instead
QPushButton *okButton = new QPushButton("Ok", accountDialog);
QPushButton *cancelButton = new QPushButton("Cancel", accountDialog);
// Connect signals to slots
// Set layout
// CHECK::Should accountDialog be the parent for these? I get a warning that they cannot be set
// because accountDialog already has a layout, which is expected, but I want them to
// automatically be deleted when accountDialog is so it makes sense to make it the parent.
QVBoxLayout *labelsVBox = new QVBoxLayout(accountDialog);
labelsVBox->addWidget(accountNameLabel);
labelsVBox->addWidget(accountPassLabel);
QVBoxLayout *lineEditsVBox = new QVBoxLayout(accountDialog);
lineEditsVBox->addWidget(accountName);
lineEditsVBox->addWidget(accountPass);
QHBoxLayout *topHBox = new QHBoxLayout(accountDialog);
topHBox->addLayout(labelsVBox);
topHBox->addLayout(lineEditsVBox);
QHBoxLayout *buttonHBox = new QHBoxLayout(accountDialog);
buttonHBox->addStretch();
buttonHBox->addWidget(okButton);
buttonHBox->addWidget(cancelButton);
mainVBox->addLayout(topHBox);
mainVBox->addLayout(buttonHBox);
accountDialog->setLayout(mainVBox);
// Show Dialog
accountDialog->exec();
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
//CLPoster mainWin;
//mainWin.show();
add_new_account();
return app.exec();
}
Setting the parent should not affect anything.
You can get around this by setting the Dialog to a fixed size by using setFixedSize(width, height);
However this is definitely a work around.