Qt C++ GUI QSpinBox Storing Input? - c++

How would I take the user input from the a spinbox and use that as a value? In other words if I wanted to store the input from the QSpinBox into a variable how would I go about doing this. Im really new at Qt GUI so any input would be greatly appreciated.

To react to GUI elements in Qt, you connect to the signals that those elements give off. Also if you have a pointer to the instance of it, you can query and change its states and properties.
Here is a quick example of what you are looking for
#include <QApplication>
#include <QVBoxLayout>
#include <QLabel>
#include <QSpinBox>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// The widget, contains a layout
QWidget * w;
w = new QWidget;
// The layout arranges and holds
// all the children of the widget
QVBoxLayout * vbox;
vbox = new QVBoxLayout;
// The user input element, the spinbox!
QSpinBox * spinbox;
spinbox = new QSpinBox();
spinbox->setValue(5);// example of using a pointer to edit its states
// now add it to the layout
vbox->addWidget(spinbox);
// add in an element to connect to,
// the infamous QLabel
QLabel * label;
label = new QLabel("spinbox output");
// add it also to the layout
vbox->addWidget(label);
// connection can happen anytime as long as the two elements
// are not null!
// This connection takes advantage of signals and slots
// and making their connection at runtime.
// if a connect call fails you should be able to see why in the
// application output.
QObject::connect(spinbox, SIGNAL(valueChanged(QString)),
label, SLOT(setText(QString)));
// associate the layout to the widget
w->setLayout(vbox);
// make the widget appear!
w->show();
return a.exec();
}
I usually put most of the initializing and connecting of GUI elements into the constructor or a method of the main QWidget or the QMainWindow. I often take the signal from a GUI input element, like a spinbox and I connect it to a custom slot defined on my subclassed QWidget. Then if I want to display it with the input value differently or increase the output by 2, I can do so easily.
// in the constructor of my Widget class
// after spinbox has been initialized
QObject(m_spinbox, SIGNAL(valueChanged(int)),
this, SLOT(on_spinboxValueChanged(int)));
void Widget::on_spinboxValueChanged(int i)
{
// here m_label is a member variable of the Widget class
m_label->setText(QString::number(i + 2));
// if accessing the value in this function is inconvenient, you can always
// use a member variable pointer to it to get its stored value.
// for example:
int j = m_spinbox->value();
qDebug() << "Spinbox value" << j;
}
Identical things can be done in QML and Qt Quick, and for many people it is easier and more intuitive because of how close it is to javascript and css.
Also Qt Creator has a tool for generating forms, and it provides another way to create your widgets with layouts and then when you access your elements you do it through a ui variable.
Also the Qt docs and examples are awesome. Taking time to learn them and read up on them is well worth the effort.
Hope that helps.

Related

Do you need to delete widget after removeItemWidget from QTreeWidget?

I have a QTreeWidget with two columns: one for property name and one for property value. The value can be edited via a widget. For example one property is Animal. When you double click the property value column I make a (custom) combobox with different animal types via this code:
QTreeWidgetItemComboBox* comboBox = new QTreeWidgetItemComboBox(treeItem, 1);
// treeitem is a pointer to the row that is double clicked
comboBox->addItems(QStringList() << "Bird" << "Fish" << "Ape");
ui.treeWidget->setItemWidget(treeItem, 1, comboBox);
When the row loses focus I remove the widget again (and the value is put as text of the QTreeWidgetItem). For removing I use
ui.treeWidget->removeItemWidget(treeItem, 1);
Now I'm wondering, since I've used new, do I neww to also delete the widget. I know this is the case if you use takeChild(i) for example. But I didn't see something similar for an itemWidget.
Do I need to delete it what would be the right order?
QTreeWidgetItemComboBox* comboBox = ui.treeWidget->itemWidget(treeItem,1);
// Do I need a cast here since the return type is QWidget*
ui.treeWidget->removeItemWidget(treeItem, 1);
delete comboBox;
or
QTreeWidgetItemComboBox* comboBox = ui.treeWidget->itemWidget(treeItem,1);
// Do I need a cast here since the return type is QWidget*
delete comboBox;
ui.treeWidget->removeItemWidget(treeItem, 1);
When the widget is added ot the QTreeWidget, it indeed takes ownership of the widget. But it only implies that the widget will be deleted when the parent is destroyed.
So if you just want to remove the widget while keeping the parent QTreeWidget alive, you indeed have to delete it manually.
The correct solution is the first one, remove the widget from the QTreeWidget first, and then delete it with one of the following ways:
delete comboBox;
comboBox = nullptr;
or:
comboBox.deleteLater();
The second one is preferred.
EDIT:
I don't change the answer since it could be a dishonest to change what was already accepted, ...
But as #Scopchanov mentioned, by reading the source code, the QTreeWidget::removeItemWidget() already calls the deleteLater() method on the old widget. We don't have to do it manually.
Anyway, the documentation says it is safe to call deleteLater() more than once:
Note: It is safe to call this function more than once; when the first deferred deletion event is delivered, any pending events for the object are removed from the event queue.
Therefore, manually deleting the widget after calling QTreeWidget::removeItemWidget() becomes useless.
You are not allowed to delete the item widget as the tree is the owner of the widget once it has been passed to the tree with setItemWidget().
From the documentation of setItemWidget():
Note: The tree takes ownership of the widget.
EDIT: In case you want a new widget, simply call setItemWidget() once more or call removeItemWidget() in case you do not need the widget anymore. The tree will ensure that no memory gets lost.
Explaination
You should not manually delete a widget, added to a QTreeWidget, since it is automatically deleted either by
destructing its parent tree widget
This is a direct consequence of the Qt's parent-child mechanism.
calling QTreeWidget::removeItemWidget anytime the tree widget still lives.
This one is not so obvious, since the documentation simply sais:
Removes the widget set in the given item in the given column.
However, looking at the source code it becomes pretty clear what is indeed happening, i.e.
QTreeWidget::removeItemWidget calls QTreeWidget::setItemWidget with a null pointer (no widget)
inline void QTreeWidget::removeItemWidget(QTreeWidgetItem *item, int column)
{ setItemWidget(item, column, nullptr); }
QTreeWidget::setItemWidget in turn calls QAbstractItemView::setIndexWidget
void QTreeWidget::setItemWidget(QTreeWidgetItem *item, int column, QWidget *widget)
{
Q_D(QTreeWidget);
QAbstractItemView::setIndexWidget(d->index(item, column), widget);
}
Finally QAbstractItemView::setIndexWidget checks if there is already a widget at this index, and if there is one, calls its deleteLater method
if (QWidget *oldWidget = indexWidget(index)) {
d->persistent.remove(oldWidget);
d->removeEditor(oldWidget);
oldWidget->removeEventFilter(this);
oldWidget->deleteLater();
}
Simply put (and this should be made clear in the documentation of both methods of QTreeWidget), any call to QTreeWidget::setItemWidget or QTreeWidget::removeItemWidget deletes the widget (if any) already set for the item.
Example
Here is a simple example I have prepared for you in order to demonstrate the described behaviour:
#include <QApplication>
#include <QBoxLayout>
#include <QTreeWidget>
#include <QComboBox>
#include <QPushButton>
struct MainWindow : public QWidget
{
MainWindow(QWidget *parent = nullptr) : QWidget(parent) {
auto *l = new QVBoxLayout(this);
auto *treeWidget = new QTreeWidget(this);
auto *item = new QTreeWidgetItem(treeWidget);
auto *button = new QPushButton(tr("Remove combo box"), this);
auto *comboBox = new QComboBox();
comboBox->addItems(QStringList() << "Bird" << "Fish" << "Ape");
treeWidget->setItemWidget(item, 0, comboBox);
l->addWidget(button);
l->addWidget(treeWidget);
connect(comboBox, &QComboBox::destroyed, [](){
qDebug("The combo box is gone.");
});
connect(button, &QPushButton::clicked, [treeWidget, item](){
treeWidget->removeItemWidget(item, 0);
});
resize(400, 300);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
Result
The described ways of destroyng the widget could be tested with the application
Simply closing the window destroys the tree widget together with its child combo box, hence the combo box's destroyed signal is emitted and the lambda prints
The combo box is gone.
After pressing the button the lambda function connected to its clicked signal is called, which removes the combo box from the tree widget. Because the combo box is deleted (automatically) as well, the lambda from the second connect statement is called, which also prints
The combo box is gone.

QT - connecting QPushButtons with QCheckBoxes

I am new to QT. I started creating a TODO app and I want to somehow connect my PushButtons that are placed in vector with CheckBoxes that are also placed in a different vector.
std::vector <QPushButton*> buttons;
std::vector <QCheckBox*> checks;
I thought that the best way to do that will be to make a for loop connecting every element of mentioned vectors
Something like:
for(int i=0; i<buttons.size(); ++i){
connect(buttons[i], SIGNAL(???), checks[i], SLOT(???));
}
But idea is the only thing that I have. I tried putting different things into SIGNAL() and SLOT() but none of them worked. By "none of them worked" I mean the fact that when button is clicked nothing happens. Program is normally compiled without any error.
What about just clicked(bool) for SIGNAL and toggle() for SLOT?
Something like that:
connect(pushButton, SIGNAL(clicked(bool)), checkBox, SLOT(toggle()));
Works for me - and you can store the widgets directly in a std::list: that avoids the need to mess with manual memory management. Let the libraries do it for you.
#include <QtWidgets>
#include <list>
int main(int argc, char **argv) {
QApplication app{argc, argv};
QWidget win;
QGridLayout layout{&win};
std::list<QPushButton> buttons;
std::list<QCheckBox> checkboxes;
QPushButton addButton{"Add"};
layout.addWidget(&addButton, 0, 0, 2, 1);
auto const clicked = &QAbstractButton::clicked;
auto const toggle = &QAbstractButton::toggle;
auto const add = [&,clicked,toggle]{
int const col = layout.columnCount();
auto const text = QString::number(col);
auto *button = &(buttons.emplace_back(text), buttons.back()); //C++11, not 14
auto *checkbox = &(checkboxes.emplace_back(text), checkboxes.back());
layout.addWidget(button, 0, col);
layout.addWidget(checkbox, 1, col);
QObject::connect(button, clicked, checkbox, toggle);
};
add();
QObject::connect(&addButton, clicked, add);
win.show();
return app.exec();
}
With Qt-5 you can now use lambda functions as slots (see connect version 5)
You can also do away with the need for the SIGNAL macro, and instead use member function pointers.
QObject::connect(buttons[i], &QPushButton::clicked, [=]
{
// toggle the check state
checks[i]->setChecked(!checks[i]->isChecked());
});
The first two parameters are a pointer to an object, and a member function pointer
buttons[i] is of type QPushButton*
&QPushButton::clicked is a member function pointer of the signal you want to connect to
The second parameter is a C++11 lambda, which captures checked and i by value, and then sets the QCheckBox checked state to the inverse of its previous value

QScrollArea messing up with QGridLayout : QGridLayout hidden and no scroll

I am trying to make a QGridLayout scrollable. It may contain several custom widgets, the number of widgets is not fixed. QGridLayout must be scrollable when there are more than x widgets, x being an arbitrary number.
The problem is, when I use QScrollArea, QScrollArea seems to hide the whole layout (only the background color of scroll area is shown). When I use QGridLayout alone, my view is - of course - not scrollable but everything works as it should do.
I am probably missing something, my guesses are:
I must fix somehow the size of the scrollable area, but I am not sure it's necessary or if I already have done it unconsciously
Parent Widgets or Child Widgets of the existing QScrollArea prevent QScrollArea by an unusual implementation to act normally.
Here's a piece of the involved code :
QScrollArea *scrollArea = new QScrollArea;
QWidget *resultsPage = new QWidget;
booksGrid = new QGridLayout;
booksGrid->setSizeConstraint(QLayout::SetMinAndMaxSize);
resultsPage->setLayout(booksGrid);
scrollArea->setBackgroundRole(QPalette::Dark);
scrollArea->setWidget(resultsPage);
mainWidget->addWidget(scrollArea);
Also, booksGrid is declared as a class attribute, mainWidget is a QStackedWidget.
Any help is welcomed, let me know if you need more information !
As I couldn't see anything suspicious in your code fragment, I made an MCVE to reproduce your issue:
#include <QtWidgets>
int main(int argc, char **argv)
{
qDebug() << "Qt Version: " << QT_VERSION_STR;
// main application
QApplication app(argc, argv);
// setup GUI
QMainWindow qWin;
QScrollArea qScrArea;
QWidget qScrView;
QGridLayout qGrid;
enum { nCols = 4 };
#define MAKE_LABEL(I) \
QLabel qLbl##I(QString::fromUtf8("Label "#I)); \
qGrid.addWidget(&qLbl##I, I / nCols, I % nCols)
MAKE_LABEL(0); MAKE_LABEL(1); MAKE_LABEL(2); MAKE_LABEL(3); MAKE_LABEL(4);
MAKE_LABEL(5); MAKE_LABEL(6); MAKE_LABEL(7); MAKE_LABEL(8); MAKE_LABEL(9);
MAKE_LABEL(10); MAKE_LABEL(11); MAKE_LABEL(12); MAKE_LABEL(13); MAKE_LABEL(14);
MAKE_LABEL(15); MAKE_LABEL(16); MAKE_LABEL(17); MAKE_LABEL(18); MAKE_LABEL(19);
MAKE_LABEL(20); MAKE_LABEL(21); MAKE_LABEL(22); MAKE_LABEL(23); MAKE_LABEL(24);
MAKE_LABEL(25); MAKE_LABEL(26); MAKE_LABEL(27); MAKE_LABEL(28); MAKE_LABEL(29);
MAKE_LABEL(30); MAKE_LABEL(31); MAKE_LABEL(32); MAKE_LABEL(33); MAKE_LABEL(34);
MAKE_LABEL(35); MAKE_LABEL(36); MAKE_LABEL(37); MAKE_LABEL(38); MAKE_LABEL(39);
#undef MAKE_LABEL
qScrView.setLayout(&qGrid);
qScrArea.setWidget(&qScrView);
qWin.setCentralWidget(&qScrArea);
qWin.show();
// run-time loop
return app.exec();
}
Compiled and tested in VS2013, Qt 5.9.2 on Windows 10 (64 bit):
a)
b)
c)
The snapshots are taken after start (a), after resizing (b), and after scrolling (c).
For me, everything looks and works like expected.
You may compile and test the sample on your side as well. If it shows the same broken behavior like your application then something is wrong in your Qt version (otherwise something in your application).
The solution - resultsPage was a personalized widget containing several other widgets, with unspecified sizes, arranged in a QGridLayout.
Widgets were encapsulated like this :
QMainWidget -> QScrollArea -> personalized QWidget resultsPage -> QGridLayout -> personalized QWidgets result(s) with unspecified size
In the end the only thing I had to do was to set a fixed size in the constructor of the QWidget result with
setFixedSize(int w, int h);

Central Widget occupies to much space and Dock Widgets to few after resize

I'm right now using QDockWidget to create a dynamic component for arranging some user defined plots. The plots should be changed in their sizes and can be arranged on top of each other.
The following code snippet illustrates what I'm trying to achieve:
#include <QApplication>
#include <QMainWindow>
#include <QLabel>
#include <QDockWidget>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
auto window = new QMainWindow;
window->setCentralWidget(new QLabel("Central Widget"));
for (int i = 1; i < 9; i++) {
auto dock = new QDockWidget(QString("Plot %1").arg(i));
dock->setWidget(new QLabel(QString("Plot %1").arg(i)));
dock->setAllowedAreas(Qt::AllDockWidgetAreas);
window->addDockWidget(Qt::BottomDockWidgetArea, dock);
}
window->show();
return app.exec();
}
The central widget merely serves as a simple placeholder and is just necessary to allow for dragging and rearranging the QDockWidget.
I'm already very satisfied with the achieved behavior besides a single drawback. After resizing window (making it larger), the central widget consumes all of the newly gained space, whereas the DockWidgets still occupy the same space like before.
The behavior is depicted below:
This is kind of annoying for user, as the central widget is just a placeholder. Actually, I just wanted to have the behavior the other way around, i.e. the central widget should keep its size, whereas the DockWidgets should be enlarged.
How can I achieve this?
Just resize your central widget to the desired size. Or even better, hide it (it seems that you only use QDockWidgets besides this one).
QLabel* label = new QLabel("Central Widget");
label->hide();
window->setCentralWidget(label);
window->setDockNestingEnabled(true);
centralWidget()->hide(); // enable full dock space
works for me w/ qt 5.8

How to create different popup (context) menus for each *type* of QTreeWidgetItem

I was able to create a context menu for my QTreeWidget as below
QMenu* pContextMenu = new QMenu(this)
QTreeWidget* pTreeWidget = new QTreeWidget();
QAction* pOpenFile = new QAction(tr("Open A File"), pContextMenu);
pTreeWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
pTreeWidget->addAction(pOpenFile);
But I want a different popup for a branch than a leaf. How do I assign a different popup depending on the type of widgetitem clicked?
My tree:
Branch1 <-- Popup1
Leaf1
Leaf2 <-- Popup2
Branch2
Branch3
Leaf1
QWidget::actions() is not listed as virtual. Else I would have derived my own class from QTreeWidget & reimplemented actions().
Method 1: Override QTreeWidget
A context menu assigned to the QTreeWidget itself will not let you have different context menus for different items, as you have discovered.
As the Qt item views don't have special API for context menus, you have to implement this yourself. Fortunately, it's not very difficult; you just need to:
Create a subclass of QTreeWidget.
Connect the customContextMenuRequested(const QPoint&) signal to a custom slot.
Display the desired context menu.
I've posted a complete working example. Some details to note include:
QTreeWidgetItem provides a handy type property to let you identify items easily without casting, string parsing, or other awkward/fragile methods.
Custom QTreeWidgetItem type values should be greater than or equal to QTreeWidgetItem::UserType.
When displaying a context menu, you must pass a global position to exec(). To correctly map from a position in the widget's space in the slot, you must use the item's viewport widget.
Method 2: Override QItemDelegate (and QTreeWidget ...)
An alternate method is to implement your own QAbstractItemDelegate subclass, and assign it to your tree widget. In your item delegate, you can override editorEvent() to handle mouse presses in the same way.
Although this approach frres is actually more in line with Qt's item view API design, there are a few key disadvantages to this approach:
Item delegates use QModelIndex objects to represent items. To convert to a QTreeWidgetItem, you must use the QTreeWidget::itemFromIndex() method. Unfortunately, this is protected, so it will actually require you to subclass QTreeWidget anyway to provide this API for your delegate. This adds some more boilerplate complexity to your code.
The editorEvent() hook is invoked before the item view handles the event. This means that you can't easily display a context menu and allow the default behavior at the same time (such as selecting the item that was right-clicked).
Since the editorEvent() handler sees all kinds of different events, you must be even more careful to handle them correctly. You must also be careful not to let this monolithic handler grow out of control if your behaviors are complicated.
The core code is very similar, but again, there's a bit more boilerplate. I've posted an example of this approach, as well.
I've modified jmk's code slightly to show how this can be done with
setContextMenuPolicy(Qt::CustomContextMenu) and customContextMenuRequested(const QPoint&) signal.
mytreewidget.h
#include <QTreeWidget>
static const int ItemType1 = QTreeWidgetItem::UserType + 1;
static const int ItemType2 = QTreeWidgetItem::UserType + 2;
class MyTreeWidget : public QTreeWidget
{
Q_OBJECT
public:
MyTreeWidget(QWidget *parent = 0);
private slots:
void showContextMenu(const QPoint &pos);
};
mytreewidget.cpp:
#include "mytreewidget.h"
#include <QMenu>
#include <QTreeWidgetItem>
MyTreeWidget::MyTreeWidget(QWidget *parent)
: QTreeWidget(parent)
{
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, SIGNAL(customContextMenuRequested(const QPoint&)),
SLOT(showContextMenu(const QPoint&)));
}
void MyTreeWidget::showContextMenu(const QPoint &pos)
{
QMenu menu;
QTreeWidgetItem* item = itemAt(pos);
switch (item->type()) {
case ItemType1:
menu.addAction("This is a type 1");
break;
case ItemType2:
menu.addAction("This is a type 2");
break;
}
menu.exec(mapToGlobal(pos));
}
main.cpp:
#include <QApplication>
#include "mytreewidget.h"
int main(int argc, char** argv)
{
QApplication app(argc, argv);
MyTreeWidget w;
// Add test items.
w.addTopLevelItem(new QTreeWidgetItem(QStringList("A (type 1)"),
ItemType1));
w.addTopLevelItem(new QTreeWidgetItem(QStringList("B (type 1)"),
ItemType1));
w.addTopLevelItem(new QTreeWidgetItem(QStringList("C (type 2)"),
ItemType2));
w.addTopLevelItem(new QTreeWidgetItem(QStringList("D (type 2)"),
ItemType2));
w.show();
return app.exec();
}