Do an action while any checkbox state is modified with Qt - c++

In my programm, I fill my interface with a lot of checkbox by this way :
void VGCCC::addMaterialToUI(QDomNodeList _materialNodeList, QWidget* _areaWidget, QLayout* _layout, QWidget* _layoutWidget, int _maTable)
{
for(int i=0; i< _materialNodeList.count();i++)
{
QDomElement materialElement = _materialNodeList.at(i).toElement();
QString elementFile = materialElement.attribute("file");
QString elementId = materialElement.attribute("id");
QString elementLabel = elementId;
elementLabel += " - ";
elementLabel += materialElement.attribute("label");
QCheckBox* checkbox = new QCheckBox(elementLabel);
_layout->addWidget(checkbox);
_layoutWidget->adjustSize();
_areaWidget->setMinimumHeight(_layoutWidget->height());
_areaWidget->setMinimumWidth(_layoutWidget->width());
configuration c;
c.path = (m_igmPath+elementFile).toStdString();
c.id = elementId.toInt();
c.name = elementLabel.toStdString();
if(_maTable==0)
{
m_materialSectionMap[checkbox] = c;
}
else
{
m_materialPostMap[checkbox] = c;
}
}
}
I would like to know how to retrieve these "abstract" checkbox. More exactly, if one of these checkbox is checked, I would like to call another function like this :
connect(anyCheckbox,SIGNAL(stateChanged(anyCheckbox)), this, SLOT(doSomethingFunctionIfCheckboxIsChecked()));
The difficulty is that in my UI, these checkbox didn't exist, so I can't connect them to my function. How can I solve it ?

You can e.g. collect pointers to your checkbox objects to a list so can access or "retrieve" them later.
You can connect each checkbox's stateChanged signal to a same slot which is then called when state of any of the checkboxes is changed. In the slot you can cast the sender() to a checkbox if you need to know which specific checkbox is in question. Another alternative is to use QSignalMapper.
In your class declaration:
private slots:
void checkboxStateChanged(int state)
private:
QList<QCheckBox*> m_checkboxes;
In your class definition:
void VGCCC::addMaterialToUI(QDomNodeList _materialNodeList, QWidget* _areaWidget, QLayout* _layout, QWidget* _layoutWidget, int _maTable)
{
...
QCheckBox* checkbox = new QCheckBox(elementLabel);
m_checkboxes.append(checkbox);
connect(checkbox, SIGNAL(stateChanged(int)), this, SLOT(checkboxStateChanged(int)));
...
}
void VGCCC::checkboxStateChanged(int state)
{
// Here your can e.g. call doSomethingFunctionIfCheckboxIsChecked()
QCheckBox* checkbox = qobject_cast<QCheckBox*>(sender());
if (checkbox)
{
// checkbox points to the object whose state changed
}
}

Related

How to access ui from main window in another qdialog?

I am having trouble accessing a QTextEdit from a main window in another form. Please help.
void properties::on_okWordPushButton_clicked()
{
if (ui->wordcombo->currentText() == "All Words") {
int wordCount = notepad->textEdit->toPlainText().split(QRegExp("(\\s|\\n|\\r)+"), QString::SkipEmptyParts).count();
ui->wordcountlabel->setText(QString::number(wordCount));
}
}
I am getting an error since I cannot read notepad->textEdit
You can use at least 2 possibilities:
Dirty way:
On form creation, pass pointer to your QTextEdit:
// mainwindow.cpp
auto myProperties = new properties(notepad->textEdit);
...
// properties.h
QTextEdit *outerEditor;
// properties.cpp
properties::properties(QTextEdit *editor) {
outerEditor = editor;
...
}
Then, on your slot you can use:
int wordCount = editor->toPlainText().split(QRegExp("(\\s|\\n|\\r)+"), QString::SkipEmptyParts).count();
Qt-way:
Remember - signals/slots are awesome.
Just after form creation, you can connect signal from MainWindow to properties passing text in your QTextEdit and store it locally:
// MainWindow.cpp
auto myProperties = new properties(notepad->textEdit);
connect(this->textEdit, QOverload<QString>::of(&QTextEdit::valueChanged), myProperties, GetNewValue);
// properties.h
void GetNewValue(QString val);
// properties.cpp
void properties::GetNewValue(QString val) {
ui->wordcountlabel->setText(QString::number(val.toPlainText().split(QRegExp("(\\s|\\n|\\r)+"), QString::SkipEmptyParts).count());
}
You can't do this, the ui is a private member of a widget, create a function that returns or sets what you need!

Is it possible to call a slot when any of the widgets in a dialog box emits a signal?

I am trying to create a configuration menu box for an application, and have used a QDialog box to display the options that the user can change. This box contains QComboBoxes and QLineEdits, but a lot of them (7 combo boxes and 12 line edits). There is a QPushButton in the bottom called "Apply Changes" that should get enabled only when any property in the box gets changed.
Do I have to link every signal from each widget with a slot to enable the button individually or is there a signal that the QDialog box itself emits when there is a change in its constituent widgets?
Right now I have this:
connect(Combo1,SIGNAL(activated(QString)),this,SLOT(fnEnable(QString)));
connect(Combo2,SIGNAL(activated(QString)),this,SLOT(fnEnable(QString)))
followed by 17 more lines of these connections.
void MyClass::fnEnable(QString)
{
ApplyButton->setEnabled(true); //It is initialised as false
}
I was wondering if there was a shorter way of doing this, maybe (like I mentioned before) a signal emitted by QDialog (I couldn't find one in the documentation)
I know that this does not speed up the program, as only the required connection is called, but it would make any further attempts at making more ambitious dialog boxes easier.
Actually there is no such signal, but one approach is to create a list of QComboBox, and make the connections with a for, for example:
QList <*QCombobox> l;
l<<combobox1<< combobox2<< ....;
for (auto combo: l) {
connect(combo, &QComboBox::activated, this, &MyClass::fnEnable);
}
The same would be done with QLineEdit.
You can iterate over an initializer list of widgets boxes and leverage C++11 to do all the boring work for you:
MyClass::MyClass(QWidget * parent) : QWidget(parent) {
auto const comboBoxes = {Combo1, Combo2, ... };
for (auto combo : comboBoxes)
connect(combo, &QComboBox::activates, this, &MyClass::fnEnable);
}
You can also automatically find all the combo boxes:
MyClass::MyClass(QWidget * parent) : QWidget(parent) {
ui.setupUi(this); // or other setup code
for (auto combo : findChildren<QComboBox*>(this))
connect(combo, &QComboBox::activated, this, &MyClass::fnEnable);
}
Or you can automatically attach to the user property's change signal. This will work on all controls that have the user property. The user property is the property of a control that contains the primary data the control is displaying.
void for_layout_widgets(QLayout * layout, const std::function<void(QWidget*)> & fun,
const std::function<bool(QWidget*)> & pred = +[](QWidget*){ return true; })
{
if (!layout) return;
for (int i = 0; i < layout->count(); ++i) {
auto item = layout->itemAt(i);
for_layout_widgets(item->layout(), fun, pred);
auto widget = item->widget();
if (widget && pred(widget)) fun(widget);
}
}
class MyClass : public QWidget {
Q_OBJECT
Q_SLOT void MyClass::fnEnable(); // must take no arguments
...
};
MyClass::MyClass(QWidget * parent) : QWidget(parent) {
// setup code here
auto slot = metaObject()->method(metaObject()->indexOfMethod("fnEnable()"));
Q_ASSERT(slot.isValid());
for_layout_widgets(layout(), [=](QWidget * widget){
auto mo = widget->metaObject();
auto user = mo->userProperty();
if (!user.isValid()) return;
auto notify = user.notifySignal();
if (!notify.isValid()) return;
connect(widget, notify, this, slot);
});
}
You can also keep the combo boxes in an array, by value. This minimizes the costs of indirect references and results in code that will take the least amount of memory possible and perform well:
class MyClass : public QWidget {
Q_OBJECT
QVBoxLayout m_layout{this};
std::array<QComboBox, 14> m_comboBoxes;
...
};
MyClass(QWidget * parent) : QWidget(parent) {
for (auto & combo : m_comboBoxes) {
m_layout.addWidget(&combo);
connect(&combo, &QComboBox::activates, this, &MyClass::fnEnable);
}
}

QT - Can I dynamically point to GUI's labels using a variable passed into a method?

I'm creating an inventory and want to reference my inventory Forms QLabels with the name of the item passed into my method.
The QLabels and the names of the items are the same so I wanted to reduce if statements by referencing a QLabel depending on the name of the item passed in.
void Zork::on_btnTake_clicked()
{
Item *item = new Item(ui->takeItem->currentText().toStdString());
Colin->addItemToInventory(item);
inventory_screen->addItem(item->getDescription()); //Enables the Item in the Inventory
currentRoom->deleteItem(item);
ui->takeItem->removeItem(ui->takeItem->currentIndex()); //Only remove the item in the foreground of combobox at the time take button was clicked
}
Calls this Method addItemToInventory(item):
void Inventory_Ui:: addItem(string itemName){
myUi->itemName->setText(QString::fromStdString(itemName));
}
I am unable to do so as itemName is not a member of Ui, although itemName does contain members of Ui.
Example:
In my Inventory_Ui Form I have 6 labels, one named broken_bottle. I want to pass broken_bottle into the method's parameter itemName and them use that as a reference so at run-time it would look like myUi->broken_bottle->setText...
I think I follow what you are asking to do. I would access it with findChild or findChildren.
http://doc.qt.io/qt-5/qobject.html#findChild
http://doc.qt.io/qt-5/qobject.html#findChildren
QList <QLabel *> labels = ui->myForm()->findChildren<QLabel*>();
QLabel* targetLabel = 0;
foreach(QLabel * label, labels)
{
if(label->text() == ui->takeItem->currentText())
{
targetLabel = label;
break;
}
}
if(targetLabel != 0)
{
// ... do something with targetLabel
}
or...
QList <QLabel *> labels = ui->myForm()->findChildren<QLabel*>();
foreach(QLabel * label, labels)
label->setObjectName(label->text());
// later
QLabel * targetLabel = ui->myForm()->findChild<QLabel*>("text of label");
if(targetLabel)
// use it
Or if you are connecting all these labels to the same slot, you can figure some of it out using the sender static method.
http://doc.qt.io/qt-5/qobject.html#sender
void MainWindow::on_takeItem_currentTextChanged()
{
QLabel* targetLabel = qobject_cast<QLabel*>QObject::sender();
if(targetLabel)
// do something with it
}
Hope that helps.

Extract menu action data in receiving function or slot

In my menu, I am setting data to the menu actions. How can I extract that data in my slot? Or even better, instead of connecting a slot, can I also connect a member function that is able to extract the action data (like in the 1st connect)? The action data is meant to identify each action. As a sidenode, I am not sure if I can use several menu action entries on only one openNote-action.
void Traymenu::createMainContextMenu() {
QAction *actionNewNote = m_mainContextMenu.addAction("Neue Notiz");
actionNewNote->setIcon(QIcon("C:\\new.ico"));
actionNewNote->setIconVisibleInMenu(true);
QObject::connect(actionNewNote,&QAction::triggered,this,&Traymenu::newNote);
QString menuEntryName;
QAction *openNote;
QVariant noteID;
for (int i = 0; i<m_noteList.count(); i++) {
std::string noteTitle = m_noteList[i].data()->getTitle();
menuEntryName = QString::fromStdString(noteTitle);
openNote = m_mainContextMenu.addAction(menuEntryName);
connect(openNote,SIGNAL(triggered()),this,SLOT(s_showNote()));
noteID.setValue(m_noteList[i].data()->getID());
openNote->setData(noteID);
}
m_mainIcon.setContextMenu(&m_mainContextMenu);
}
And the slot:
void Traymenu::s_showNote() {
QObject* obj = sender();
//int noteID = data.toInt();
//Search all notes in noteList for that ID and show it
}
Using QObject::sender()
You can use QObject::sender() to get the signal's sender, followed by qobject_cast to cast the sender pointer to the right type.
void Traymenu::s_showNote()
{
QAction* act = qobject_cast<QAction *>(sender());
if (act != 0)
{
QVariant data = act->data();
int noteID = data.toInt();
showNote(noteID); // isolate showNote logic from "get my ID" stuff
}
}
void Traymenu::showNote(int noteID)
{
// Do the real work here, now that you have the ID ...
}
As the Qt documentation warns, "This function violates the object-oriented principle of modularity." It's still a fairly safe and standard practice, though — just one with some shortcomings. In particular, note that you're committing to having a s_showNote method that only works when it's accessed as a slot (otherwise sender is 0).
Using QSignalMapper
Alternatively, you can use the QSignalMapper class to return a pointer to teh item or to associate a unique identifier (int or QString) with each item.
Something like this:
void Traymenu::createMainContextMenu()
{
signalMapper = new QSignalMapper(this); // (or initialize elsewhere)
// ... (create your newNote here same as before) ...
QString menuEntryName;
QAction *openNote;
int noteID;
for (int i = 0; i<m_noteList.count(); i++) {
std::string noteTitle = m_noteList[i].data()->getTitle();
menuEntryName = QString::fromStdString(noteTitle);
openNote = m_mainContextMenu.addAction(menuEntryName);
noteID = m_noteList[i].data()->getID();
openNote->setData(QVariant(noteID)); // (if you still need data in the QActions)
signalMapper->setMapping(openNote, noteID);
}
connect(signalMapper, SIGNAL(mapped(int)),
this, SLOT(showNote(int)));
m_mainIcon.setContextMenu(&m_mainContextMenu);
}
void Traymenu::showNote(int noteID) {
// Now you have the ID ...
}
This pattern has the benefit of isolating all the ugly "Wait, how do I get my identifier?" stuff in one spot, instead of having both the initialization code and the slot function having code for associating actions and IDs.
I would write it like:
void Traymenu::s_showNote() {
QObject* obj = sender();
QAction *action = qobject_cast<QAction *>(obj);
int id = action->data().toInt();
for (int i = 0; i < m_noteList.count(); i++) {
if (m_noteList[i].data()->getID() == id) {
[..]
}
}
}

Drag and drop issue in Qt: Pass parameter to receiving dropEvent

A healthPackButton is dropped on a mysquare. Now I would like to add a value to this button (because I have a number of healthPackButtons and I want to be able to differentiate them). I have tried changing the makeDrag function for it to accept an extra parameter but than my SIGNAL was no longer matched.
Question: How can I pass additional information (=> int value) to my dropEvent handler inside another class.
Dialog class
for (int i=0; i<healthPks.size(); i++){
int value = healthPks.at(i);
QPushButton *healthPackButton = new QPushButton(title,this);
connect(healthPackButton,SIGNAL(pressed()),this,SLOT(makeDrag()));
}
void Dialog::makeDrag(){
QDrag *drag = new QDrag(this);
QMimeData *mime = new QMimeData;
mime->setText("This is a test");
drag->setMimeData(mime);
drag->start();
}
mysquare class
void MySquare::dropEvent(QGraphicsSceneDragDropEvent *event){
isHealthPack=true;
int xCoord = curX/width;
int yCoord = curY/height;
int value = 0; //what's the value??
const QMimeData *mimeData = event->mimeData();
emit healthMapChanged(xCoord,yCoord,value);
update();
}
To get an additional parameter into your slot, you can use a QSignalMapper – the documentation has an example of adding a QString const& parameter, but you can use an int in exactly the same way to pass the value to a makeDrag(int).
You could then use QMimeType's setData (converting your int to a QByteArray using QByteArray::number for example) to get that value to the drop target.