I'm trying to display a sequence of images through a Qlabel using setPixmap. I have a QStringList containing the image file names and a for loop which iterates through the images with a 5 second wait after each image. However, only the last image file is ever being displayed. Currently the screen remains blank during the wait of the first iterations until the last image is finally shown. I've read that using a for loop wont work and that I should be using signals and slots instead. I'm new to this concept though and I would really appreciate an example to point me in the right direction.
Here is my current code:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),ui(new Ui::MainWindow){
ui->setupUi(this);
QStringList images;
QString imageName;
images << "redScreen.png" << "blueScreen.png" << "greenScreen.png";
for(int x=0; x < images.size(); x++){
imageName = images.at(x);
this->displayScreen(imageName, 5);
}
}
void MainWindow::displayScreen(QString imageName, int wait){
QTimer t;
QEventLoop loop;
QPixmap myImage;
myImage.load(":/images/" + imageName);
ui->imageLabel->setPixmap(myImage);
ui->imageLabel->repaint();
// 5 second wait between next iteration
t.start(wait*1000);
connect(&t, SIGNAL(timeout()), &loop, SLOT(quit()));
loop.exec();
}
The reentrant wait-via-eventloop hack is a great source of hard-to-diagnose bugs. Don't use it. It's very, very rare that you'll need to instantiate your own event loop. Even rather complex projects can entirely avoid it.
You should simply run a timer and react to timer ticks. Here's one example:
#include <QApplication>
#include <QImage>
#include <QGridLayout>
#include <QLabel>
#include <QBasicTimer>
class Widget : public QWidget {
QGridLayout m_layout;
QLabel m_name, m_image;
QStringList m_images;
QStringList::const_iterator m_imageIt;
QBasicTimer m_timer;
void timerEvent(QTimerEvent * ev) {
if (ev->timerId() == m_timer.timerId()) tick();
}
void tick() {
display(*m_imageIt);
m_imageIt ++;
const bool loop = false;
if (m_imageIt == m_images.end()) {
if (loop)
m_imageIt = m_images.begin();
else
m_timer.stop();
}
}
void display(const QString & imageName) {
QImage img(":/images/" + imageName);
m_name.setText(imageName);
m_image.setPixmap(QPixmap::fromImage(img));
}
public:
Widget(QWidget * parent = 0) : QWidget(parent), m_layout(this) {
m_images << "redScreen.png" << "blueScreen.png" << "greenScreen.png";
m_imageIt = m_images.begin();
m_layout.addWidget(&m_name, 0, 0);
m_layout.addWidget(&m_image, 1, 0);
tick();
m_timer.start(5000, Qt::CoarseTimer, this);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
A code similar to the below must work for the task you mentioned. ( needs cleaning/class organization though )
QTimer timer;
int x=0;
QStringList images;
QString imageName;
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),ui(new Ui::MainWindow){
ui->setupUi(this);
images << "redScreen.png" << "blueScreen.png" << "greenScreen.png";
connect( &timer, SIGNAL(timeout()), this, SLOT(ChangeImageSlot()) );
timer.start(5000);
}
void ChangeImageSlot()
{
imageName = images.at(x++);
this->displayScreen(imageName, 5);
if( x < images.size() )
timer.start(5000);
}
Best solution is DeepBlack with a couple of QTimer, but if you want at you risk you can try insert an processEvent() function inside the for loop of display image.
Related
I need to calculate max number of items in current view of QListView.
I wrote code like this:
void MyListView::resizeEvent(QResizeEvent *event)
{
QListView::resizeEvent(event);
QFontMetrics fm (this->font());
int fontHeight = fm.lineSpacing();
QRect cr = contentsRect();
int windowHeight = cr.bottom() - cr.top();
int maxItemsCount = windowHeight / fontHeight;
qDebug()<<"max items in view: "<< maxItemsCount;
}
but calculated max number of items is is incorrect.
E.g. in case of my window height and font height I get 32 max items in view when in fact current view has 28 items. Perhaps someone can suggest something, how to calculate it properly?
My idea is to use QListView::indexAt() (inherited from QAbstractView) to obtain the row index for
the top-left corner
the bottom-left corner
of the list view viewport and determining the number of visible items by difference of them.
To check this out, I made an MCVE testQListViewNumVisibleItems.cc:
// Qt header:
#include <QtWidgets>
class ListWidget: public QListWidget {
public:
ListWidget(QWidget *pQParent = nullptr): QListWidget(pQParent) { }
virtual ~ListWidget() = default;
ListWidget(const ListWidget&) = delete;
ListWidget& operator=(const ListWidget&) = delete;
int getNumVisibleItems() const
{
const QSize size = viewport()->size();
const QModelIndex qMIdx0 = indexAt(QPoint(0, 0));
const QModelIndex qMIdx1 = indexAt(QPoint(0, size.height() - 1));
//qDebug() << "qMIdx0:" << qMIdx0 << "qMIdx1:" << qMIdx1;
return qMIdx0.isValid()
? (qMIdx1.isValid() ? qMIdx1.row() : count()) - qMIdx0.row()
: 0;
}
};
const int MaxItems = 20;
// main application
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup GUI
ListWidget qLst;
qLst.resize(200, 200);
qLst.show();
// timer to populate list view
using namespace std::chrono_literals;
QTimer qTimer;
qTimer.setInterval(1000ms);
// install signal handlers
int n = 0;
QObject::connect(&qTimer, &QTimer::timeout,
[&]() {
qLst.addItem(QString("item %0").arg(++n));
qDebug() << "Visible items:" << qLst.getNumVisibleItems();
if (n >= MaxItems) qTimer.stop();
});
// runtime loop
qTimer.start();
return app.exec();
}
and a CMakeLists.txt:
project(QListViewNumVisibleItems)
cmake_minimum_required(VERSION 3.10.0)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(Qt5Widgets CONFIG REQUIRED)
include_directories("${CMAKE_SOURCE_DIR}")
add_executable(testQListViewNumVisibleItems testQListViewNumVisibleItems.cc)
target_link_libraries(testQListViewNumVisibleItems Qt5::Widgets)
built and tested in VS2017 on Windows 10:
After having implemented what came in my mind, I googled a bit to possibly see other approaches. (I admit I should've done before.)
Thereby I found the following possible duplicate:
SO: Simple way to get all visible items in the QListView
The accepted answer doesn't contain much more than the hint for indexAt and a link to a Qt-FAQ article:
How can i get hold of all of the visible items in my QListView?
In order to get hold of the visible items in a QListView http://doc.qt.io/qt-5/qlistview.html, then you can iterate over them using indexAt() http://doc.qt.io/qt-5/qlistview.html#indexAt. You can get hold of the first visible item using indexAt(QPoint(0, 0)), then in order to get the index at the next position then use visualRect() http://doc.qt.io/qt-5/qlistview.html#visualRect to find out what your next call to itemAt() should be. This position would be:
visualRect.y() + visualRect.height() + 1 effectively.
See the following example for an illustration:
#include <QtGui>
QList <QModelIndex>myList;
class ListView : public QListView
{
Q_OBJECT
public:
ListView()
{
QStringListModel *myModel = new QStringListModel(this);
QStringList list;
list << "a" << "b" <<"c" <<"d" <<"e" <<"f" <<"g" <<"h" <<"i" <<"j" <<"k";
myModel->setStringList(list);
setModel(myModel);
QTimer::singleShot(3000, this, SLOT(test1()));
}
public slots:
void test1()
{
QModelIndex firstIndex = indexAt(QPoint(0, 0));
if (firstIndex.isValid()) {
myList << firstIndex;
} while (viewport()->rect().contains(QPoint(0, visualRect(firstIndex).y() + visualRect(firstIndex).height() + 1 ))) {
firstIndex = indexAt(QPoint(0, visualRect(firstIndex).y() + visualRect(firstIndex).height() + 1 ));
myList << firstIndex;
}
qDebug() << myList.count() << "are visible";
}
};
#include "main.moc"
int main(int argc, char** argv)
{
QApplication app(argc, argv);
ListView window;
window.resize(100, 50);
window.show();
return app.exec();
}
I am looking for an easy way to link hand coded design to the UI for widgets applications in Qt. I plan to use the UI builder to easily adjust the layouts and obtain proper spacing, which I find hard to do without the UI builder.
I want to create a 3x3 grid of buttons for which I plan to use QVector< QVector<QPushButton*> > (I am not sure how I would do this in UI builder.)
Here is what I have tried, Why are the buttons not displayed even when I set the parent of each button to the widget ?
main.cpp
#include "window.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.show();
return a.exec();
}
window.h
#ifndef WINDOW_H
#define WINDOW_H
#include <QWidget>
#include <QPushButton>
namespace Ui {
class Window;
}
class Window : public QWidget
{
Q_OBJECT
public:
explicit Window(QWidget *parent = 0);
~Window();
private:
Ui::Window *ui;
static const int tiles = 50, height = 600, width = 500;
QVector< QVector<QPushButton*> > cells;
};
#endif // WINDOW_H
window.cpp
#include "window.h"
#include "ui_window.h"
Window::Window(QWidget *parent) :
QWidget(parent),
ui(new Ui::Window)
{
ui->setupUi(this);
this->resize(width, height);
int j = 0;
for(auto &row : cells)
{
int i = 0;
for(auto &col : row)
{
col = new QPushButton(this);
col->setGeometry(i, j, tiles, tiles);
i += tiles;
}
j += tiles;
}
}
Window::~Window()
{
delete ui;
for(auto &row : cells)
{
for(auto &col : row)
{
delete col;
}
}
}
Any help would be appreciated.
The vectors are empty, so you iterate over nothing. Instead of managing these manually, you could leverage the grid layout.
Alas, you're complicating things unnecessarily with all the manual memory management and geometry management. It's unnecessary. All you need to do is to add widgets to the layout you allude to. And even then, I don't see how relegating the layout to a .ui file helps you since there the layout must be empty. So yes: you can set spacings, but you won't see them until you run the code. So it seems like a pointless exercise, unless you have other elements you're not telling us about (why aren't you - you went so far already).
Below is a minimum example that simplifies it as much as practicable, but see this answer and the links therein for more idea.
// https://github.com/KubaO/stackoverflown/tree/master/questions/button-grid-43214317
#include <QtWidgets>
namespace Ui { class Window {
public:
// Approximate uic output
QGridLayout *layout;
void setupUi(QWidget * widget) {
layout = new QGridLayout(widget);
}
}; }
class Window : public QWidget
{
Q_OBJECT
Ui::Window ui;
QPushButton * buttonAt(int row, int column) {
auto item = ui.layout->itemAtPosition(row, column);
return item ? qobject_cast<QPushButton*>(item->widget()) : nullptr;
}
public:
explicit Window(QWidget *parent = {});
};
Window::Window(QWidget *parent) : QWidget(parent) {
ui.setupUi(this);
for (int i = 0; i < 5; ++i)
for (int j = 0; j < 6; ++j)
{
auto b = new QPushButton(QStringLiteral("%1,%2").arg(i).arg(j));
ui.layout->addWidget(b, i, j);
}
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.show();
return a.exec();
}
#include "main.moc"
I have a set of QImages which come to a function (after a fixed interval of 4 seconds) and the function's job is to update the QLabel to show the new image.
While doing this, I can see a very obvious delay in the image rendering.
I had also followed the suggestions on the link:
Efficient way of displaying a continuous stream of QImages
But, even with using ImageDisplay in the link above, I can see a delay in image rendering.
Can anyone please suggest the best way to do this?
Below is the code.. The images required for the code to run are located at:
https://www.dropbox.com/sh/jiqdfqoiimjs7ei/AAAXezUeeCFyZXjNNOTmWZVga?dl=0
#include <QDialog>
#include <QtGui>
#include <QtCore>
#include <QApplication>
#include <QWidget>
#include <QImage>
class imageDisplay : public QWidget
{
public:
imageDisplay(QWidget*);
~imageDisplay();
void setImage(QImage* img);
private:
QImage* m_image;
protected:
void paintEvent(QPaintEvent* evt);
};
imageDisplay::imageDisplay(QWidget* parent) : QWidget(parent)
{
m_image = 0;
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
}
imageDisplay::~imageDisplay()
{
}
void imageDisplay::setImage(QImage* img)
{
m_image = img;
repaint();
}
void imageDisplay::paintEvent(QPaintEvent*)
{
if(!m_image) return;
QPainter painter(this);
painter.drawImage(rect(), *m_image, m_image->rect());
}
////////////////////////////////////
//
int main(int arc, char ** argv)
{
QApplication theApp(arc, argv, true);
QDialog* dlg = new QDialog();
imageDisplay* wgt = new imageDisplay(dlg);
wgt->resize(600,400);
dlg->show();
for(int i = 0 ; i <= 19; ++i)
{
sleep(1);
QString fileName = "aaa" + QString::number(i) + ".png";
QImage* img = new QImage(fileName);
wgt->setImage(img);
}
return theApp.exec();
}
So basically what I am trying to do is the following:
I want to create a directional arrows pad on the screen. When the user presses the up or 8 key, the UI should react as if I clicked the up button. I have googled and searched all over, but as I just started using QTCreator (and C++), I am very inexperienced and any help would be appreciated.
So far I have
class GamePadWidget : public QWidget
{
public:
GamePadWidget(QWidget *parent = 0);
protected:
virtual void keyPressEvent(QKeyEvent *event);
};
GamePadWidget::GamePadWidget(QWidget *parent)
: QWidget(parent)
{
int buttonWidth = 75;
int buttonHeight = 75;
QPushButton *down = new QPushButton(("Y-"), this);;
down->setGeometry(100, 200, 100, 100);
QIcon downicon;
downicon.addFile(QString::fromUtf8("C:/arrows/Aiga_downarrow.png"), QSize(),QIcon::Normal, QIcon::Off);
down->setIcon(downicon);
down->setIconSize(QSize(buttonWidth,buttonHeight));
down->setFocusPolicy(Qt::NoFocus);
QPushButton *up = new QPushButton(("Y+"), this);;
up->setGeometry(100, 50, 100, 100);
QIcon upicon;
upicon.addFile(QString::fromUtf8("C:/arrows/Aiga_uparrow.png"), QSize(),QIcon::Normal, QIcon::Off);
up->setIcon(upicon);
up->setIconSize(QSize(buttonWidth,buttonHeight));
up->setFocusPolicy(Qt::NoFocus);
}
void GamePadWidget::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_8 || event->key() == Qt::Key_Up ) {
printf("key event in board");
}
else if (event->key() == Qt::Key_9 || event->key() == Qt::Key_Down ) {
qApp->quit();
}
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
GamePadWidget widget;
widget.show();
return app.exec();
}
with my current code, if I press down or 2, the app exits as expected, yet here is part in which I am stuck at.
I want the same functionality as if I pressed the down (or up key), the pushbutton should light up briefly then shoot off a signal to who knows where
I realize it should have something to do with connect(quit, SIGNAL(clicked()), qApp, SLOT(quit()));
but cannot quite wrap my mind / find it.
Thank you for your time.
You can call a slot on an object as if it was a normal method (which it is as far as C++ is concerned). Obv you'll need to make your pushButton a member though, so you have access to it outside of the constructor.
Then yes, just connect the button's clicked() signal to the app's quit() slot. The code below should work for you (not tested though):
GamePadWidget::GamePadWidget(QWidget *parent)
: QWidget(parent)
{
...
mDownButton = new QPushButton(("Y-"), this);;
...
connect(mDownButton, SIGNAL(clicked()), qApp, SLOT(quit()));
}
void GamePadWidget::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Down ) {
qDebug() << "Down key pressed";
mDownButton.click();
}
}
I am trying to display and get the result a message box from outside of a QObject class. I seem to be able to generate the dialog like this:
#include <iostream>
#include <QApplication>
#include <QtConcurrentRun>
#include <QMessageBox>
class DialogHandler : public QObject
{
Q_OBJECT
signals:
void MySignal();
public:
DialogHandler()
{
connect( this, SIGNAL( MySignal() ), this, SLOT(MySlot()) );
}
void EmitSignal()
{
emit MySignal();
}
public slots:
void MySlot()
{
QMessageBox* dialog = new QMessageBox;
dialog->setText("Test Text");
dialog->exec();
int result = dialog->result();
if(result)
{
std::cout << "ok" << std::endl;
}
else
{
std::cout << "invalid" << std::endl;
}
}
};
#include "main.moc" // For CMake's automoc
void MyFunction(DialogHandler* dialogHandler)
{
dialogHandler->EmitSignal();
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
DialogHandler* dialogHandler = new DialogHandler;
MyFunction(dialogHandler);
return app.exec();
}
To get the result back in MyFunction, it seems to work to do simply pass an object to fill with the result like this:
#include <iostream>
#include <QApplication>
#include <QtConcurrentRun>
#include <QMessageBox>
class DialogHandler : public QObject
{
Q_OBJECT
signals:
void MySignal(int* returnValue);
public:
DialogHandler()
{
connect( this, SIGNAL( MySignal(int*) ), this, SLOT(MySlot(int*)), Qt::BlockingQueuedConnection );
}
void EmitSignal(int* returnValue)
{
emit MySignal(returnValue);
}
public slots:
void MySlot(int* returnValue)
{
std::cout << "input: " << *returnValue << std::endl;
QMessageBox* dialog = new QMessageBox;
dialog->addButton(QMessageBox::Yes);
dialog->addButton(QMessageBox::No);
dialog->setText("Test Text");
dialog->exec();
int result = dialog->result();
if(result == QMessageBox::Yes)
{
*returnValue = 1;
}
else
{
*returnValue = 0;
}
}
};
#include "main.moc" // For CMake's automoc
void MyFunction(DialogHandler* dialogHandler)
{
int returnValue = -1;
dialogHandler->EmitSignal(&returnValue);
std::cout << "returnValue: " << returnValue << std::endl;
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
DialogHandler* dialogHandler = new DialogHandler;
QtConcurrent::run(MyFunction, dialogHandler);
std::cout << "End" << std::endl;
return app.exec();
}
Does that seem reasonable? Is there a better way to do it?
This isn't possible quite like you have it, but with a bit of work it could be done. One option, of course, would be to convert your class to a QObject, at which point you could send signals. It doesn't help for the delay during exec, however. If that is necessary, you could have a messaging class that lives in the main UI thread, but can be called from other threads. The function called from other threads would need to lock, make a semaphore, and send an event to itself with the semaphore and message to be displayed. Then, in customEvent (which would be in the UI thread), you would create the message box, exec it, and trigger the semaphore after the message box is cleared.
Of course, things get a bit more complicated if you need to send information back the other way as well. Then you'll need a complete subsystem for your program, instead of just one basic class like I describe here.