Passing variable data to QWidgets - c++

I have a qt application with different widgets. All widgets should use the same data, so I declare a fixed list of classes for this data (different data from different events). The active data got selected with a QComboBox.
This data get passed as an reference to one widget, which contains a QtPlot with a QAbstractTableModel to visualize the data.
mainwindow.h:
MyClass currentData;
QList<MyClass> dataList;
plotWidgetPointer* plotWidget;
mainwindow.cpp:
MainWindow::MainWindow( QWidget* parent ) : QMainWindow( parent )
{
plotWidgetPointer = new plotWidget( currentData, this );
}
void MaindWindow::selectionChange(int index)
{
if( index != -1)
{
currentData = dataList.at( index );
}
}
plotWidget.h:
public:
explicit plotWidget(MyClass& myClass, QWidget* parent = 0);
plotWidget.cpp:
plotWidget::plotWidget(MyClass& myClass, QWidget* parent) : QWidget(parent)
{
}
After starting the programm and switching the data over the combobox the application crashes. Does anybody know why?
Thank you.

Related

What event is fired when a widget is 'ready'?

I'm subclassing a QFrame widget created using the Qt Designer, this frame contains some QPushButtons as child.
class Frame : public QFrame
{
Q_OBJECT
public:
Frame(QWidget* parent = 0) : QFrame(parent)
{
QList<QPushButton*> list = this->findChildren<QPushButton*>(); // <- at this point list is empty.
};
bool event(QEvent *event)
{
if (event->type() == QEvent::ShowToParent)
{
QList<QPushButton*> list = this->findChildren<QPushButton*>(); // <- now list is populated.
//...
}
return QWidget::event(event);
}
}
When I call this->findChildren<QPushButton*>() from the subclass constructor, the QList is empty, probably because the buttons have not been added to it yet.
My question is: there's any event that is fired only once that indicates that a widget is 'ready'?
I mean, the widget has finished adding everything.
I'm also aware that at this point:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
QList<QPushButton*> list = ui.frame->findChildren<QPushButton*>();
}
The widget is 'ready', but I would like to identify it from inside of the subclass.
I did some tests with the event QEvent::ShowToParent but I'm not sure if this event is called only once during the entire application execution.

Retrieve QComboBox index from QCompletion popup

I have a QComboBox with a QCompleter. The QCompleter displays suggestions in a popup list and I'm drawing the elements in a custom way.
The problem is: I can't get the index in the original combobox even if I did store it into the UserData of the item.
Complete code follows:
class MyItemDelegate: public QItemDelegate {
public:
MyItemDelegate(QWidget *parent) :
QItemDelegate(parent) {}
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index ) const Q_DECL_OVERRIDE
{
if (index.row() == 0) { // If this is the first item in the POPUP VIEW
auto comboIndex = index.data(Qt::UserRole).toInt();
qDebug() << "Combobox index is " << comboIndex; // WRONG!!
}
... custom styling code omitted since off-topic ...
}
};
MyWindow::MyWindow(QStringList words, QMainWindow *parent) :
QDialog((QWidget*)parent),
ui(new Ui::MyWindow) {
ui->setupUi(this);
int comboBoxIndex = 0;
for(auto& word : words) {
ui->myComboBox->addItem(word, comboBoxIndex); // This is stored in UserData
++comboBoxIndex;
}
completer = new QCompleter(words, this);
// Use a TreeView for the popup of the completer
QTreeView *treeView = new QTreeView(this);
completer->setPopup(treeView);
treeView->setItemDelegate(new MyItemDelegate(this));
}
No matter what element I have first in the QCompletion popup list, the qDebug() line always returns 0 as index.
Isn't index.data() referring to the original item data of the combobox?
As i see, your problem is that you retrieve comboIndex from model where you didn't set UserData. You set UserData for items of internal QComboBox model (see QComboBox), but when you try to retrieve this data, you do that in code of MyItemDelegate, which you set for QTreeView for QCompleter. That's why index in paint method of delegate is index of QCompleter internal model, not index of QComboBox internal model. And that's why comboIndex is always 0.
To solve your problem you can add UserData in QCompleter internal model. For example, you can create your own model and set this model for QCompleter with method setModel.

Qt signals and slots passing data

I'm pretty new to c++ and qt. I'm not sure if i use the right terminology describe what I want to achieve. But here it goes.
My application spawns and removes widgets in a gridlayout when the user pushes buttons. Managed to do this successfully. However when the user uses the spawned widgets I want the widgets to interact with each other.
QList<QLineEdit*> m_ptrLEPathList;
QList<QPushButton*> m_ptrPBList;
qint8 m_noFields;
void MainWindow::on_pbIncFields_clicked()
{
//create widgets and place on a new row in a gridLayout
QLineEdit *lineEditPath = new QLineEdit(this);
QPushButton *pushButton = new QPushButton(this);
//storing pointers in lists to be able to delete them later.
m_ptrLEPathList.append(lineEditPath);
m_ptrPBList.append(pushButton);
ui->gridLayout->addWidget(m_ptrLEPathList.last(),m_noFields,0);
ui->gridLayout->addWidget(m_ptrPBList.last(),m_noFields,1);
connect(m_ptrPBList.last(), SIGNAL(clicked(bool), this, SLOT(on_addPath()));
m_noFields++;
}
void MainWindow::on_pbDecFields()
{
//delete last spawned widgets
}
void MainWindow::on_addPath()
{
QFileDialog getPath();
getPath.exec();
//somehow set the text of the line edit spawned on the same row as the pushbutton
}
So my slot is executed when I push any spawned button but I have no idea how to store the data from the file dialog in the related lineEdit.
Is the basic idea of what I'm trying to do ok or is there any other solution to achieve the fuctionality I'm looking for?
In on_addPath slot you can use QObject::sender method to get the clicked button, and, assuming m_ptrLEPathList and m_ptrPBList lists are equal, you can easily get the corresponding QLineEdit:
void MainWindow::on_addPath()
{
QFileDialog dialog;
if (!dialog.exec())
{
return;
}
QStringList fileNames = dialog.selectedFiles();
if (fileNames.isEmpty())
{
return;
}
QPushButton *btn = qobject_cast<QPushButton*>(sender());
if (!btn)
{
return;
}
Q_ASSERT(m_ptrPBList.size() == m_ptrLEPathList.size());
int index = m_ptrPBList.indexOf(btn);
if (index == -1)
{
return;
}
QLineEdit *edit = m_ptrLEPathList.at(index);
edit->setText(fileNames.first());
}
You are including 'on_addPath' function out of the scope of the 'MainWindow' class, so when the slot is called you have not access to member elements in the class.
Try to include the slot function into the class and check if you have direct access to the member elements. Also, the 'lineEditPath' element must be a member object, so it must be included into the class definition.
Something like this:
void MainWindow::on_addPath()
{
QFileDialog getPath();
getPath.exec();
QStringList fileNames = dialog.selectedFiles();
if (fileNames.isEmpty())
{
return;
}
m_lineEditPath->setText(fileNames.first());
}
First off, void on_addPath() must be void MainWindow::on_addPath()
As for linking the data from QFileDialog it is simple. Try this:
void MainWindow::on_addPath() {
/* Get the push button you clicked */
QPushButon *btn = qobject_cast<QPushButton*>( sender() );
/* Make sure both the lists have the same size */
Q_ASSERT(m_ptrPBList.size() == m_ptrLEPathList.size());
/* If the sender is a button in your list */
if ( m_ptrPBList.contains( btn ) ) {
/* Get the index of your push button */
int idx = m_ptrPBList.indexOf( btn );
/* Get the corresponding line edit */
QLineEdit *le = m_ptrLEPathList.at( idx );
/* Get your path */
QString path = QFileDialog::getOpenFileName( this, "Caption", "Default Location" );
/* If you path is not empty, store it */
if ( not path.isEmpty() )
le->setText( path );
}
}
Add a map to private section
QMap<QPushButton*, QLineEdit*> map;
Then
QLineEdit *lineEditPath = new QLineEdit(this);
QPushButton *pushButton = new QPushButton(this);
map.insert(pushButton, lineEditPath);
You can use sender() method like follow:
void on_addPath()
{
QFileDialog getPath();
getPath.exec();
QObject* obj = sender();
QPushButton *pb = 0;
if((pb = qobject_cast<QPushButton *>(obj)) != 0) {
QLineEdit* lineEdit = map->value(pb, 0);
if( lineEdit != 0 )
lineEdit->setText( getPath.<some function to get selected file name> );
}
}
I think the cleanest solution would be to contain the QLineEdit and QPushButton in a custom widget class, if it suits your project. That way you could use the file dialog inside this class, and you won't have to store the widgets in lists. It is hard to give you all the information, as you didn't really provide any details what your application is supposed to do. But in any case, the custom widget class would look something like this (you should define all the functions inside a .cpp file):
#ifndef WIDGETCONTAINER_H
#define WIDGETCONTAINER_H
#include <QWidget>
#include <QLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QFileDialog>
class WidgetContainer : public QWidget
{
Q_OBJECT
public:
WidgetContainer(QWidget *parent = 0) : QWidget(parent)
{
setLayout(new QHBoxLayout);
button.setText("BUTTON");
layout()->addWidget(&lineEdit);
layout()->addWidget(&button);
connect(&button, SIGNAL(clicked()), this, SLOT(buttonPressed()));
}
private:
QLineEdit lineEdit;
QPushButton button;
private slots:
void buttonPressed()
{
QString filename = QFileDialog::getSaveFileName();
lineEdit.setText(filename);
}
};
#endif // WIDGETCONTAINER_H

Cannot display a tree (QTreeView) inside a QWidget placed inside another QWidget

I have a problem with a QWidget that contains a tree (QTreeView): I can display it if it is created as a widget on its own, and I cannot do the same if it is a subwidget of another higher level widget. In this second case, what I get is an empty widget without the tree.
This works:
QApplication testApp(argc, argv);
MyTree myTree;
testApp.exec();
This does not work:
class MainWindow : public QMainWindow
{
Q_OBJECT
QSplitter *spl1, *spl2;
QMdiArea *mdiArea;
QTableWidget *other;
public:
MainWindow();
void LoadTree();
MyTree *myTree;
};
MainWindow::MainWindow(QWidget *_parent)
: QMainWindow(_parent), myTree(0)
{
mdiArea = new QMdiArea;
other = new QTableWidget;
spl1 = new QSplitter(Qt::Vertical, this);
spl1->addWidget(mdiArea);
spl1->addWidget(other);
LoadTree();
spl2 = new QSplitter(Qt::Horizontal, this);
spl2->addWidget(myTree);
spl2->addWidget(spl1);
setCentralWidget(spl2);
}
void MainWindow::LoadTree()
{
myTree = new MyTree(this);
}
Here is the code common to the two cases (which should be OK):
class MyTree : public QWidget
{
Q_OBJECT
public:
explicit MyTree(QWidget *_parent = 0);
int RefreshTree();
private slots:
void HandleTreeWidgetEvent(QModelIndex);
private:
QWidget *parent;
QTreeView *pjrTree;
QTreeView *GetNewTree();
};
MyTree::MyTree(QWidget *_parent) :
QWidget(_parent),
parent(_parent)
{
pjrTree = GetNewTree();
if(pjrTree) {
if(parent == 0)
pjrTree->show();
}
else {
// Never gets here
}
}
QTreeView* MyTree::GetNewTree()
{
QFileSystemModel *model = new QFileSystemModel;
model->setReadOnly(true);
model->setRootPath("/my/path/");
QTreeView* pjrTree = new QTreeView;
pjrTree->setModel(model);
pjrTree->setRootIndex(model->index("/my/path/"));
QModelIndex index;
index = model->index(4, 1); // temp values - no effect
return pjrTree;
}
Is the tree view the only widget that does not display? I would suggest passing the splitter it will be contained in as the parent, rather than the main window, when you instantiate the tree.
Deriving the class MyTree from QTreeView, instead of having a pointer to QTreeView as a member variable, fixed my problem.

Mapping QSlider::valueChanged signals in custom QWidget with multiple sliders

I have a custom QDialog with a set of custom sliders (i.e., QWidgets each consisting of a slider and an associated label) on it. In essence, this dialog is used to change the coordinates of an object in a 3D scene by adjusting the sliders for each dimension.
Currently, the QDialog itself stores a pointer to the scene object it modifies. Thus, the slot that takes care of object movement when a slider emits valueChanged is also part of the QDialog class. Since it has no way of knowing which slider was moved, the movement function (rather inefficiently) just loops through all of the sliders on the dialog, gathers their values, and assigns a new configuration to the 3D object.
Ideally, only the dimension that changed would have to be re-assigned when a slider is moved. So I tried using QSignalMapper to identify each slider with a numerical index. This would require the ability to send a valueChanged signal with two parameters: one identifying the sender slider, and one giving the new value itself. Unfortunately, as I learned here, QSignalMapper can't do this.
Another way to get the functionality I want would perhaps be to use the sender() method. But, according to the documentation, that's bad practice--it violates the principle of modularity.
I can think of a couple of other solutions: allow the custom slider class to store its parent dialog (seems bad in the same say that sender() is bad), or maybe even store the movable object itself as a static member of the custom slider class instead of (non-statically/as it is now) in the overall dialog.
Which of these approaches, if any, would be the best way to go here? What alternatives should I consider?
Possible solution is connect QSlider signal sliderReleased(), emitted when the user releases the slider with the mouse, with QSignalMapper map() and store sliders id with pointer on some list. When value has hanged, QDialog could emit another signal with information of slider id and new value.
QSignalMapper *mapper = new QSignalMapper(this);
connect(slider_0, SIGNAL(sliderReleased()), mapper, SLOT(map()));
mapper->setMapping(slider_0, 0);
tab_s[0] = slider_0;
connect(slider_1, SIGNAL(sliderReleased()), mapper, SLOT(map()));
mapper->setMapping(slider_1, 1);
tab_s[1] = slider_1;
connect(slider_2, SIGNAL(sliderReleased()), mapper, SLOT(map()));
mapper->setMapping(slider_2, 2);
tab_s[2] = slider_2;
connect(mapper, SIGNAL(mapped(int)),
this, SLOT(checkSlider(int)));
and in some slot:
void SomeDialog::checkSlider(int id)
{
emit valueChanged(id, tab_s[id]->value());
}
The ideal solution would be to subclass QSlider and re-emit the valueChanged() signal with added parameters (x/y/z axis). Let's say MySlider which constructs with given axis index (0/1/2):
class MySlider : public QSlider
{
Q_OBJECT
public:
MySlider(int axis, QWidget *parent);
signals:
void valueChanged(int axis, int value);
private slots:
void reemitValueChanged(int value);
private:
int m_axis;
};
MySlider::MySlider(int axis, QWidget *parent)
: QSlider(parent)
{
m_axis = axis;
connect(this, SIGNAL(valueChanged(int)),
this, SLOT(reemitValueChanged(int)));
}
void MySlider::reemitValueChanged(int value)
{
emit valueChanged(m_axis, value);
}
MySlider intercepts valueChanged(int) signal from QSlider, and emits its own signal valueChanged(int,int) with the axis index (0/1/2). You can use MySlider in the application now:
for (int i=0; i<3; i++) {
sliders[i] = new MySlider(i, this);
connect(sliders[i], SIGNAL(valueChanged(int,int)),
this, SIGNAL(updateScene(int,int)));
}
Of course, you'll have to arrange these sliders in a layout or something. Here's the source of this approach.
I think using the sender() method is fine.
I'd write something like this:
enum Axes
{
axisX,
axisY,
axisZ
}
class MyDialog : public QDialog
{
...
signals:
void valueChanged(int value, int type);
}
MyDialog::MyDialog(QWidget *parent) :
QDialog(parent)
{
...
connect(xSlider, SIGNAL(valueChanged(int)),
this, SLOT(onSliderChange(int));
connect(ySlider, SIGNAL(valueChanged(int)),
this, SLOT(onSliderChange(int));
connect(zSlider, SIGNAL(valueChanged(int)),
this, SLOT(onSliderChange(int));
}
void MyDialog::onSliderChange(int value)
{
int axis = -1;
QSlider *slider = dynamic_cast<QSlider*>(sender());
if (slider == xSlider)
{
axis = axisX;
}
else if (slider == ySlider)
{
axis = axisY;
}
else if (slider == zSlider)
{
axis = axisZ;
}
else
{
qWarning() << "Wrong sender";
}
if (axis != -1)
{
emit valueChanged(value, axis);
}
}
//signalMapper.h
//--------------
#ifndef SIGNALMAPPER_H
#define SIGNALMAPPER_H
#include <QWidget>
#include <QGridLayout>
#include <QSlider>
#include <QLabel>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
private:
QGridLayout* m_pGridLayoutMain;
QLabel* m_pLabel;
private slots:
void setLabelText(QWidget *pWidget);
};
#endif // SIGNALMAPPER_H
//signalMapper.cpp
//----------------
#include "signalMapper.h"
#include <QSignalMapper>
#include <QString>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
setMinimumSize(400, 200);
QSignalMapper* pSignalMapper = new QSignalMapper(this);
m_pGridLayoutMain = new QGridLayout(this);
m_pGridLayoutMain->setContentsMargins(10, 10, 10, 10);
m_pGridLayoutMain->setSpacing(10);
m_pLabel = new QLabel(this);
m_pLabel->setMinimumSize(150, 20);
m_pLabel->setAlignment(Qt::AlignCenter);
m_pLabel->setFrameStyle(QFrame::Box | QFrame::Sunken);
m_pGridLayoutMain->addWidget(m_pLabel, 0, 0);
for(int i=1; i < 10; i++)
{
QSlider* pSlider = new QSlider(this);
QString strObjName = "Slider " + QString().setNum(i);
pSlider->setObjectName(strObjName);
pSlider->setMinimum(0);
pSlider->setMaximum(100);
pSlider->setSingleStep(1);
pSlider->setOrientation(Qt::Horizontal);
pSlider->setValue(35);
connect(pSlider, SIGNAL(valueChanged(int)), pSignalMapper, SLOT(map()));
pSignalMapper->setMapping(pSlider, pSlider);
m_pGridLayoutMain->addWidget(pSlider, i, 0);
}
connect(pSignalMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setLabelText(QWidget*)));
}
Widget::~Widget()
{
}
void Widget::setLabelText(QWidget *pWidget)
{
QSlider* pSlider = dynamic_cast<QSlider*>(pWidget);
if(pSlider)
{
qDebug("Success");
m_pLabel->setText(pSlider->objectName()+" value changed to "+QString().setNum(pSlider->value()));
}
else
{
qDebug("Failure");
}
}