I have a program which when it runs, at first the user is asked to initialize the system. In that question form, there are 3 checkboxes that the user can check them for specific person or every persons and the system initializes the items related to that checkbox for the person(s).
When a checkbox is selected, a specific function and subsequently the specific class is called and initialization is done.
In the mainwindow.cpp I have:
InitializeDialog *id=new InitializeDialog;
connect(id,&InitializeDialog::initializeSignal,this,&MainWindow::initializeSlot);
id->exec();
id is the question form which has 3 checkboxes in it. And:
void MainWindow::initializeSlot(QStringList persons, bool interests, bool plots, bool graphs)
{
initializeMBox->setWindowTitle(tr("Initializing System")+"...");
initializeMBox->setText(tr("Please wait until initialization has been done") + ".<br>");
initializeMBox->show();
initializeMBox->setStandardButtons(0);
if (interests)//checkbox 1 is checked
initializeInterests(persons);
if (plots)//checkbox 2 is checked
initializePlots(persons);
if(graphs)//checkbox 3 is checked
initializeGraphs(persons);
initializeMBox->setStandardButtons(QMessageBox::Ok);
}
And again:
void MainWindow::initializeInterests(QStringList persons)
{
for(int p=0;p<persons_comboBox_->count();p++)
{
persons_comboBox_->setCurrentIndex(p);
if (persons.contains(persons_comboBox_->currentText()))
{
//..
//create a specific class object and some specific functions
//..
//*
initializeMBox->setText(initializeMBox->text() + "<div><img src=\":/tickIcon.png\" height=\"10\" width=\"10\">" + " " + tr("Interests analyzed for the persons") + ": " + persons_comboBox_->currentText() + ".</div>");
}
}
}
initializePlots and initializeGraphs are similiar to initializeInterests.
The problem starts from here:
I want to show a message after initialization for every person (as I mentioned by star in initializeInterests) but my initializeMBox (is a QMessageBox) does not show the message continuously and when all persons are initialized, all messages are shown suddenly. It should be noted that I see my initializeMBox is getting bigger but it seems that my QMessageBox is Freezed.
I can't use QtConcurrent::run because my QMessageBox is updated from mainwindow (and so from the base thread) by the line that I mentioned by star.
How can I have a QMessageBox which be updated continuously?
Don't reenter the event loop. Replace id->exec() with id->show(). Manage the dialog's lifetime - perhaps it shouldn't be dynamically created at all.
Don't block in initializeInterests. Instead of changing the combo box, get its data, send it out to an async job, set everything up there, then send the results back.
Pass containers by const reference, not value.
Don't create strings by concatenation.
If the input persons list is long, sort it to speed up look-ups.
For example:
class StringSignal : public QObject {
Q_OBJECT
public:
Q_SIGNAL void signal(const QString &);
};
void MainWindow::initializeInterests(const QStringList &personsIn) {
auto in = personsIn;
std::sort(in.begin(), in.end());
QStringList persons;
persons.reserve(in.size());
for (int i = 0; i < persons_comboBox_->count(); ++i) {
auto const combo = persons_comboBox->itemText(i);
if (std::binary_search(in.begin(), in.end(), combo))
persons << combo;
}
QtConcurrent::run([persons = std::move(persons), w = this](){
StringSignal source;
connect(&source, &StringSignal::signal, w, [w](const QString & person){
w->initalizeMBox->setText(
QStringLiteral("%1 <div><img src=...> %2: %3.</div>")
.arg(w->initalizeMBox->text())
.arg(tr("Interests analyzed for the person"))
.arg(person)
);
});
for (auto &person : persons) { // persons is const
// create a specific object etc.
QThread::sleep(1); // let's pretend we work hard here
source.signal(person);
}
});
}
The creation of the "specific objects" you allude to should not access anything in the gui thread. If it doesn't - pass a copy of the required data, or access it in a thread-safe manner. Sometimes it makes sense, instead of copying the data, move it into the worker, and then when the worker is done - move it back into the gui, by the way of a lambda.
I'm adding some checkBoxes recursively in tableWidget cells (always in column 1) on a specific function. The final nummber of those checkboxes is not defined and depends on the user input.
QStringList matID;
for(int i=0; i<matID.size(); i++
{
ui->tableWidget->insertRow( ui->tableWidget->rowCount() );
QCheckBox *checkBox = new QCheckBox();
ui->tableWidget->setItem(ui->tableWidget->rowCount()-1,0,checkBox)
}
I would like to connect each one of them with a signal-slot connection which tells me which one has been checked.
Can anyone help me? I don't really understand how to do it...
thanks
Giuseppe
There are four solutions for this problem. I'll only describe the cleanest possiblity.
The best solution is to actually to make your item checkable. You can do this by setting the Qt::ItemIsUserCheckable flag on the item using setFlags(), remember to also keep the defaults.
QStringList matID;
for(int i=0; i<matID.size(); i++
{
ui->tableWidget->insertRow( ui->tableWidget->rowCount() );
QTableWidgetItem *item = ui->tableWidget->getItem(ui->tableWidget->rowCount()-1,0);
item->setFlags(item->getFlags() | Qt::ItemIsUserCheckable);
}
Then, as explained in Qt/C++: Signal for when a QListWidgetItem is checked? (ItemWidgets are similar enough), you can then listen on itemChanged and check using checkState().
The other methods would be:
set an delegate for your column
create a QSignalMapper to map your own QCheckBox pointers to some value identifying your rows
Use the QObject::sender() member function to figure out which checkbox was responsible, and resolve it back to the row on your own.
I would like to do a rudimentary automation test of my Qt application. It records mouse events and writes them to a file (f.e. mousepress(300, 400)). When starting the automation, it reads the coordinates from the file, sends the appropriate mouse events and would do a pixel comparison with a previously saved screenshot.
Currently, I have an overlay widget that spans the app and has transparent mouse events. All it does is track the coordinates.
When reading the data back in, that overlay paints a rectangle on the mouse press location.
I need help when sending mousePressEvents to Qt's event system. It draws the points on the correct location but never actually does the physical click.
Is there a way to do this with Qt or would I have to use Window's SendInput()?
Is there a way to pause and wait until the mouse event has finished? I would need to know when the event is complete in order to start the pixel by pixel comparison.
Widget::Widget( QWidget *parent )
: QFrame( parent )
, mPoint(QPoint(0,0))
{
setWindowFlags(Qt::WindowStaysOnTopHint);
setStyleSheet("background-color: rgba(0, 0,255, 2%);");
setAttribute(Qt::WA_TransparentForMouseEvents, true);
setGeometry(parent->geometry());
...
}
void Widget::run()
{
QFile file( "coordinates.txt", NULL );
if(!file.open( QIODevice::ReadOnly ))
return;
QTextStream in(&file);
int i = 0;
while (!in.atEnd())
{
QString line = in.readLine();
if(line.startsWith("mousepress"))
{
int startIndex = line.indexOf('(');
int endIndex = line.indexOf(')');
QString coord = line.mid(startIndex+1, line.size() - startIndex - 2);
QStringList nbr = coord.split(',');
mPoint = QPoint(nbr[0].toInt(), nbr[1].toInt());
QWidget *receiver = QApplication::widgetAt(mPoint);
QMouseEvent *event = new QMouseEvent(QEvent::MouseButtonPress, mPoint, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::postEvent(receiver, event); // same result with sendEvent()
QCoreApplication::processEvents();
update();
// wait till the event finished, then continue with next point
}
}
}
void Widget::paintEvent(QPaintEvent *event)
{
QPainter p( this );
QPen pen;
pen.setBrush(Qt::NoBrush);
if(!mPoint.isNull())
{
pen.setColor(Qt::red);
pen.setWidth( 2 );
p.setPen(pen);
p.drawRoundRect(mPoint.x(), mPoint.y(), 10, 10, 25, 25);
p.drawText(mPoint, QString::number(mPoint.x()) + ", " + QString::number(mPoint.y()));
}
}
[Edited]
I followed ddriver's suggestion and it works after a few changes: I save global and local positions in the file, to send to the QMouseEvent.
How could I be sure that the mouse click is complete before doing a screenshot and comparing it to a saved image?
void Widget::DoStep()
{
if(!mInStream.atEnd())
{
QString line = mInStream.readLine();
if(line.startsWith("MouseButtonPress"))
{
QPoint pos = parseGlobalPos();
QPoint localPos = parseLocalPos();
QWidget *receiver = QApplication::widgetAt(pos);
QMouseEvent *event = new QMouseEvent(QEvent::MouseButtonPress,localPos, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QApplication::postEvent(receiver, event);
QMouseEvent *eventRelease = new QMouseEvent(QEvent::MouseButtonRelease, localPos, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QApplication::postEvent(receiver, eventRelease);
// after successful click, take screenshot and compare them
}
}
if (!mInStream.atEnd())
QMetaObject::invokeMethod(this, "DoStep", Qt::QueuedConnection);
else
file.close();
}
If I understand the problem correctly, its source is the blocking while loop, which blocks the thread and doesn't allow the event loop to spin and process events. There is no way to "pause" as that would block the event loop as well, and it wouldn't be able to do the work either, but there is a way to split the work to be done one step at a time, one event loop cycle at a time.
The solution is to not use a blocking while loop but implement an event system driven loop. Then you process one line on every event loop cycle until you run out of work.
Move the file, stream and all that stuff outside of the function, make them class members so they persist. Then in run() you simply setup the input for reading, and move all the event posting stuff to a new function, for example doStep(), and in run() after you setup you have a:
QMetaObject::invokeMethod(this, "doStep", Qt::QueuedConnection);
In doStep(), aside from the event stuff, at the end you also do scheduling:
if (!in.atEnd()) QMetaObject::invokeMethod(this, "doStep", Qt::QueuedConnection);
else // we are done, close file, cleanup, whatever
This way only one step will be executed per event loop cycle, allowing the event loop to spin and process the events. While there is work, a step will be scheduled for the next event loop cycle. You can also use a single shot timer to do this, or even a queued connection, which you can trigger by emitting overloading the event to emit a signal on completed. You do not, and should not force events to be processed, using this approach there will be no need to.
The concept in general is about a non-blocking async event driven self scheduling worker, I have posted a fully implemented one here. The upsides are you can track progress, pause, cancel and all that good stuff.
Try sending event to QGraphicsView's viewport:
qApp->sendEvent(view->viewport(), &mousePressEvent);
I'm new using Qt and I'm having some troubles with it.
I'm doing a simple terminal application and I need to disable the buttons for a few seconds after they are selected.
I'm doing something like this:
void MainWindow::on_readcard_clicked(){
this->setEnabled(false);
//Send Command
QString commandString = "";
commandString.append('1');
commandString.append("\n");
QByteArray commandArray = commandString.toLocal8Bit();
serial->write(commandArray);
//Read Card
QByteArray data = serial->readLine(12);
QString dataString = 0;
dataString.append(data);
ui->cardnumber->setText(dataString);
dataString.clear();
data.clear();
QByteArray saldo = serial->readAll();
QString saldoString = 0;
saldoString.append(saldo);
ui->balance->setText(saldoString);
saldoString.clear();
saldo.clear();
this->setEnabled(true);}
I need this because if someone press the button twice before it finished processing the first tap I will get a "crazy" array with lots of trash.
I also tried use "waitForBytesWritten" and "waitForReadyRead" but it hasn't blocked the connection until everything was processed.
Regards
Try with QTimer::SingleShot() :
this->setEnabled(false);
QTimer::singleShot(2000, this, SLOT(enableMyButton()));
// you code
void enableMyButton()
{
this->setEnabled(true);
}
It'll enable the button 2secs after the call
I have a QCompleter using a QStringListModel for my QPlainTextEdit (check this example):
QStringListModel* model = new QStringListModel(names);
QCompleter* completer = new QCompleter(model);
completer->setCompletionMode(QCompleter::PopupCompletion);
completer->setModelSorting(QCompleter::UnsortedModel);
It works fine. Now I need some Icon, Tooltips for each suggestion I'm trying to use a QListWidget as custom popup:
QListWidget* w = new QListWidget();
foreach(name, names) {
QListWidgetItem* i = new QListWidgetItem(name);
i->setIcon(/*my Icon*/);
i->setToolTip("");
w->addItem(i);
}
completer->setPopup(w);
The popup ok, just like I need, but the completion no more work. I cannot type the text to make it filter the suggestion, just Up/Down key.
I have try:
completer->setModel(w->model());
but no help!
What is my misstake or just QStringListModel give me the ability to filter the suggestions? What do you suggest?
Thanks you!
I mostly deal with PyQt, but same deal. My syntax may be off, but you should use a QStandardItemModel vs. a QStringListModel. From there, you can leave it as the standard popup (QListView)
Something like:
QStandardItemModel* model = new QStandardItemModel();
// initialize the model
int rows = names.count(); // assuming this is a QStringList
model->setRowCount(rows);
model->setColumnCount(1);
// load the items
int row = 0;
foreach(name, names) {
QStandardItem* item = new QStandardItem(name);
item->setIcon(QIcon(":some/icon.png");
item->setToolTip("some tool tip");
model->setItem(row, 0, item);
row++;
}
completer->setModel(model);
completer->popup()->setModel(model); // may or may not be needed