I want to create a custom widget that contains a QSpinBox. I want the custom widget to expose some of QSpinBox's properties as its own so it can work conveniently in Designer.
Is there a convenient way to do this kind of property proxying in Qt?
I want to reiterate that the relationship between my custom widget and the QSpinBox is containment, not inheritance. I am using pyqt, but will happily accept a pure qt answer.
To clarify, I want to create variations of standard widgets that have some extra decorations. For example, I will be adding a toggleable icon to sit beside my QSpinBox. However I still want to be able to configure the QSpinBox in designer (ie, set the suffix, upper and lower limits, increment, etc).
Alrite, so here's a way to do it.
It's better than writing a function manually for each property. Basically, you write a macro that expands the code for you.
I have a doubleSpinBox in my widget and its value is changed whenever I change the property. And you emit a signal whenever the property changes, which is connected to the doubleSpinBox->setValue(). Tested and works perfectly.
Complete code at this link.
#ifndef MYWIDGET_H_
#define MYWIDGET_H_
#include <QtGui/QWidget>
#include <QtGui/QLabel>
#include "ui_mywidgetform.h"
#define MYPROPERTY_SET_FUNC(_PROPCLASS_, _PROP_PARAM_NAME_, _PROP_SET_FUNC_NAME_, _PROP_NOTIFY_) \
void _PROP_SET_FUNC_NAME_(_PROPCLASS_ _PROP_PARAM_NAME_) \
{ \
emit _PROP_NOTIFY_(_PROP_PARAM_NAME_); \
}
#define MYPROPERTY_GET_FUNC(_PROPCLASS_, _PROP_READ_FUNC_NAME_, _PROP_VARIABLE_)\
_PROPCLASS_ _PROP_READ_FUNC_NAME_() const \
{ return _PROP_VARIABLE_; }
class MyWidget : public QWidget
{
Q_OBJECT
public:
MyWidget(QWidget *parent = 0);
~MyWidget();
enum Accuracy {Good, Bad};
signals:
void doubleSpinBoxValueChanged(double);
void accuracyChanged(Accuracy);
private:
Q_PROPERTY(Accuracy accuracy READ accuracy WRITE setAccuracy NOTIFY accuracyChanged)
Q_PROPERTY(double doubleSpinBoxValue READ doubleSpinBoxValue WRITE setDoubleSpinBoxValue)
private:
Accuracy m_accuracy; //custom class property
Ui::Form m_ui;
public:
//Getter functions
MYPROPERTY_GET_FUNC (double, doubleSpinBoxValue, m_ui.doubleSpinBox->value())
MYPROPERTY_GET_FUNC (Accuracy, accuracy, m_accuracy)
//Setter functions
MYPROPERTY_SET_FUNC(Accuracy, accuracy, setAccuracy, accuracyChanged)
MYPROPERTY_SET_FUNC(double, doubleSpinBoxValue, setDoubleSpinBoxValue, doubleSpinBoxValueChanged)
};
#endif // MYWIDGET_H_
Here is a PyQt version inspired by blueskin. The main operational difference here is that the custom widget class is composed at runtime rather than compile time. The advantage to this is that we can programmatically inspect the target metaobject and use that to generate the proxy properties.
Now PyQt is compiled from c++, so I have to believe that it is also possible to generate qt types at runtime in c++. I am betting that would be hellish, however.
from PyQt4 import QtGui, QtCore
def makeProxyProperty(childname, childtype, childpropname):
metaobject = childtype.staticMetaObject
metaproperty = metaobject.property(metaobject.indexOfProperty(childpropname))
def getter(self):
return metaproperty.read(getattr(self, childname))
def setter(self, val):
return metaproperty.write(getattr(self, childname), val)
return QtCore.pyqtProperty(metaproperty.typeName(), fget=getter, fset=setter)
class Widget1(QtGui.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.spinbox = QtGui.QSpinBox()
self.checkbox = QtGui.QCheckBox()
layout = QtGui.QHBoxLayout()
layout.addWidget(self.checkbox)
layout.addWidget(self.spinbox)
self.setLayout(layout)
spinbox_suffix = makeProxyProperty("spinbox", QtGui.QSpinBox, "suffix")
spinbox_prefix = makeProxyProperty("spinbox", QtGui.QSpinBox, "prefix")
spinbox_minimum = makeProxyProperty("spinbox", QtGui.QSpinBox, "minimum")
spinbox_maximum = makeProxyProperty("spinbox", QtGui.QSpinBox, "maximum")
So to make things a bit clear,
you will be using this custom widget in the designer.
You need the custom widget to have properties that expose QSpinBox properties as the widget's properties
So,
basically you would want to create a property in your custom widget
using 'Q_PROPERTY' and use setter and getter methods to change the
QSpinBox's properties I hope this example would help
http://doc.qt.nokia.com/latest/properties.html#a-simple-example
Related
I have the following classes:
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QStringList pluginsToStart, QWidget *parent = 0);
~MainWindow();
// some other stuff
public slots:
void on_timeDataChanged(logging::TimeValueVector<bool>& aData);
void on_importStarted();
}
and
class DataImporterWidget : public PluginWidget
{
Q_OBJECT
public:
explicit DataImporterWidget(QWidget *parent = 0);
~DataImporterWidget();
void initConnections(QMap<QString, PluginWidget*> pluginWidgetMap);
in the method initConnections, I want the widget to init the signal-slot connections like so:
void DataImporterWidget::initConnections(QMap<QString, PluginWidget*> pluginWidgetMap)
{
for(Importer* importer : this->getImporterMap().values())
{
connect(importer, SIGNAL(signal_timeDataChanged(logging::TimeValueVector<bool>&)),
parentWidget(), SLOT(on_timeDataChanged(logging::TimeValueVector<bool>&)));
}
connect(this, SIGNAL(signal_importStarted()), parentWidget(), SLOT(on_importStarted()));
}
Importer is a QGroupBox and a base class for derived sub classes specifying concrete data importer types.
It works like so: If I press a button, an DataImporterWidget is created and added to a QMdiArea as a QMdiSubWindow. When creating the DataImporterWidget I call the initConnections() method which sets up the signal-slot connections.
Now, when I run the program, I get the following message:
QObject::connect: No such slot
QMdiSubWindow::on_timeDataChanged(logging::TimeValueVector<bool>&) in src/dataimporter/DataImporterWidget.cpp:81
QObject::connect: No such slot QMdiSubWindow::on_importStarted() in src/dataimporter/DataImporterWidget.cpp:85
QObject::connect: (sender name: 'DataImporterWidget')
I do not understand why I get it because the slot is there. Even if I cast the parentWidget to the MainWindow, I get the same error.
PluginWidget is just a base class deriving from QWidget that holds some common functionality for my used plugins.
I put Q_OBJECT on each base and derived class but still get this error. However, if I set up the connections in the MainWindow, it works just fine, but I wonder why the above solution won't work.
Don't create the connection from child object, instead create it from parent object code after creating the child object.
This way you won't need to cast any type.
You did not shown a huge chunk of important code (like creating DataImporterWidget, setting MainWindow as its parent, the place where you call initConnections...). However, you said
If I use the new signal slot syntax, my program crashes with a
segmentation fault...
If it crashes, than you have to find a reason why. Using old signal-slot connect syntax does not cure the disease, it just delay its manifestation. According to this, the reason why you get a segfault can be parentWidget() == nullptr or parent is not initialized yet.
My advice, check your code, and make user the parent of DataImporterWidget is created and specified before your call initConnections().
I've found the problem. The reason is, that the MainWidget class holds a QMdiArea where I add my PluginWidgets. So, when I create the PluginWidget, I set the MainWidget as its parent, but as soon as I add it to the QMdiArea, it also becomes a child of QMdiSubWindow. The parentWidget was never null but it was the wrong one ...
I have written a customer Class (inheriting from QObject) in C++ and registered it's type successfully with QML. Currently I'm creating objects of this class statically in C++ and storing a pointer to them in a Model which implements QAbstractListModel. In QML in I can access the objects perfectly as items of the Model.
The customObject is a non-visual object.
I'm visualising in another part of the GUI application (QML) the objects in a ListView with a delegate.
However now I would like to create objects from my custom Class dynamically in QML and store them also in the Model. This is where I'm struggling. I hoped I could create a customObject like this:
import com.myProject.myCustomStuff 1.0
...
Button{
id: createObjBtn
text: "create new CustomObj"
onClicked:{
var obj = MyCustomObj;
myObjectManager.addObj(obj); // object holding the implemented QAbstactListModel
console.log(typeof(obj)); // returns [Object object]
console.log(Qt.isQtObject(obj)) // returns false
}
}
I would appreciate your thoughts. Maybe someone knows a way to do this correctly?
Thanks!
Update 1
As requested by Simon-Warta, here is the Constructor implementation of MyCustomObj.
MyCustomObj.cpp
MyCustomObj::MyCustomObj(QObject *parent) : QObject(parent)
{
QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
}
You are confusing the functionality intent of the classes. The QAbstractListModel is intended as a wrapper around a container, yes, you could put the container inside the QAbstractListModel derived class, but you don't really have to, the container can be just about any C++ class, not necessarily even QObject derived, it can be just a QVector<Something> that you can reach from the model via a pointer. Which is good for cases where you have many objects, and not all need to have models at all the time, since those models are pretty heavy.
You don't really need to concern yourself with the ownership, leave that at the C++ side, the same goes for the actual object creation, have a slot called that adds the new object to the container while also using the model's beginInsertRows() and endInsertRows() so that any views will be notified to update efficiently, the new object should also be created in that slot, you can pass any data needed for it from QML, just make sure all data is registered with the Qt meta system so it can work with QVariant for QML-C++ interop.
So it should be something like:
myObjectManager.create(/* any needed data goes here */)
And create() passes eventual data to the C++ side, where you create the object, call beginInsertRows(), add the object to the model's underlying storage, then call endInsertRows() and you are done.
I'd prefer to keep the ownership in the C++ side (and I don't mean explicitly), where I have control over it. Qt kind of sucks in a big way when dealing with object ownership shared between C++ and QML. Ideally, there should be a single shared pointer class that will work across both C++ and QML, so the object is deleted once all reference to it are gone. But that is just not the "Qt way" - the Qt shared pointers do not work with QML, nor do the standard C++ shared pointers, there is actually an entirely different shared reference class for QML, which is not even part of the public API IIRC, very ugly and shortsighted design that only widens the gap between C++ and QML and the associated inconvenience factor.
I don't know if this is the shortest way but this should do for you. I am starting with the basics for those other up-voters.
MyCustomObj.h
class MyCustomObj : public QObject
{
Q_OBJECT
public:
// ...
Q_INVOKABLE void funfunction();
MyCustomObj.cpp
void MyCustomObj::funfunction()
{
qDebug("Fun with QML");
}
main.cpp
qmlRegisterType<MyCustomObj>("com.myProject.myCustomStuff", 1, 0, "MyCustomObj");
app.qml
import com.myProject.myCustomStuff 1.0
ApplicationWindow {
id: mainWindow
Component {
id: myComponent
MyCustomObj {
}
}
Component.onCompleted: {
var obj = myComponent.createObject(mainWindow)
if (!obj) console.error("Error creating object")
console.log(typeof(obj))
console.log(Qt.isQtObject(obj))
obj.funfunction()
}
}
createObject optionally takes properties to be passed to the component.
Storing
Since you are responsible for deleting the objects now, I'd recommend to use shared pointers, such that the objects get destroyed when the List is destroyed.
Your implementation of QAbstactListModel, let's call it MyModel has an adder function like that:
#include <memory> // for std::shared_ptr
class MyModel : public QAbstractListModel
{
Q_OBJECT
public:
// ..
Q_INVOKABLE addObj(MyCustomObj* obj)
{
objectlist_.append(std::shared_ptr<MyCustomObj>(obj));
}
private:
QList<std::shared_ptr<MyCustomObj>> objectlist_
}
If a double value returned as EditRole by a model, then (supposedly) QDoubleSpinBox is used by QTableView as an editor. How can I change precision in that control?
The precision behavior of QDoubleSpinBox in a QTableView is explained here , so to solve the problem , you need to set your own QDoubleSpinBox, there are two ways to do this according to the end part of Subclassing QStyledItemDelegate:
using an editor item factory or subclassing QStyledItemDelegate. The latter way requires you to reimplement four methods of QStyledItemDelegate, I just fount it a bit lengthy, so I choose the first way , sample code in PyQt following :
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class ItemEditorFactory(QItemEditorFactory): # http://doc.qt.io/qt-5/qstyleditemdelegate.html#subclassing-qstyleditemdelegate It is possible for a custom delegate to provide editors without the use of an editor item factory. In this case, the following virtual functions must be reimplemented:
def __init__(self):
super().__init__()
def createEditor(self, userType, parent):
if userType == QVariant.Double:
doubleSpinBox = QDoubleSpinBox(parent)
doubleSpinBox.setDecimals(3)
doubleSpinBox.setMaximum(1000) # The default maximum value is 99.99.所以要设置一下
return doubleSpinBox
else:
return super().createEditor(userType, parent)
styledItemDelegate=QStyledItemDelegate()
styledItemDelegate.setItemEditorFactory(ItemEditorFactory())
self.tableView.setItemDelegate(styledItemDelegate)
self.tableView.setModel(self.sqlTableModel)
I have not been able to find a good way to get at those spin boxes. The default delegate for a QTableView is a QStyledItemDelegate. When creating a item in Qt::EditRole it uses items created by the default QItemEditorFactory class, which you can access using QItemEditorFactory::defaultFactory(). You could then register your own editor there, however I do not see a good way to edit the ones that are already there.
Instead most likely what you should do is implement your own delegate with a different precision specified. There is a example to make a delegate using a QSpinBox, which you would replace with a QDoubleSpinBox. Then in createEditor you would then use setDecimals to set the spin box to the precision that you want. You then apply that delegate to your table with setItemDelegate.
According to the documentation, the precision of QDoubleSpinBox can be changed by calling decimals.
Here is a minimal QStyledItemDelegate implementation that modifies the QDoubleSpinBox precision:
const int DOUBLESP_PRECISION = 6;
class SpinBoxDelegate : public QStyledItemDelegate
{
public:
QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const Q_DECL_OVERRIDE
{
auto w = QStyledItemDelegate::createEditor(
parent, option, index);
auto sp = qobject_cast<QDoubleSpinBox*>(w);
if (sp)
{
sp->setDecimals(DOUBLESP_PRECISION);
}
return w;
}
};
I am building a notepad and want to count the words in a dialog.
QString input = ui->textEdit->toPlainText();
int spaces = input.count(" ");
ui->NumWordsLabel->setNum(spaces);
This is my attempt so far.
However, I want to execute this code in my dialog so I need to pass the
ui->textEdit->toPlainText()
Into my dialog.
This is how I create my dialog...
void MainWindow::on_actionWord_Count_triggered()
{
word_count = new Word_count();
word_count->show();
}
How would I get the required information into the dialog?
Thanks.
Generally you can pass constructor arguments to pass data to your classes. For example:
Header file:
class Word_count : public QDialog
{
Q_OBJECT
public:
explicit Word_count(QString text, QObject *parent = 0);
...
}
Source file:
Word_count(QString text, QObject *parent)
: QDialog(parent)
{
ui->setup(this);
... figure out word count and set labels ...
}
How to use:
void MainWindow::on_actionWord_Count_triggered()
{
word_count = new Word_count(ui->textEdit->toPlainText());
word_count->show();
}
Important notes:
The QObject *parent argument should always be present in the constructor arguments. Make sure to only place the = 0 in the header file, or else you will get an error.
Your constructor should be marked explicit, unless you know you do not want that. Explicit prevents the C++ compiler from automatically casting to your type using a given constructor.
Pass the parent parameter to your inheriting class, whether that be QDialog, QWidget or QObject, using the constructor initializer list syntax. This is done in the source file example with : QDialog(parent).
You can add as many arguments as you need, but they should be before the parent argument. This is because the parent argument has a default value that can be implied. Because you must specify arguments in order, it can not be implied if there are required parameters after it.
This only will work for creating the dialog. If you want the dialog to dynamically update, you'll need to use a slot or method like suggested by others. Alternatively, if you don't want a dynamically updating dialog, consider using exec instead of show so that users must close your word count dialog before continuing with their work.
Add a slot like void setText( const QString& text ) to your Word_count class.
Then, you can emit a signal like void textChanged( const QString& text ) const from your MainWindowclass.
Don't forget to connect both.
I have derived QGraphicsItem and QGraphicsScene classes. I want the items to be able to call scene() and get a derviedGraphicsItem * instead of a QGraphicsItem *, so I reimplemented QGraphicsScene::itemAt to return a derived pointer.
DerivedItem* DerivedScene::itemAt( const QPointF &position, const QTransform &dt ) const
{
return qobject_cast< DerivedItem * >(
QGraphicsScene::itemAt(position, dt) );
}
I get the following error (Qt 4.6, GCC 4.4.3 on Ubuntut 10.4)
scene.cpp: In member function ‘DerivedItem* DerivedScene::itemAt(qreal, qreal, const QTransform&) const’:
scene.cpp:28: error: no matching function for call to ‘qobject_cast(QGraphicsItem*)’
I then noticed QGraphicsItem doesn't inherit QObject, so I made my derived QGraphicsItem class have multiple inheritance from QObject and QGraphicsItem, and after adding the Q_OBJECT macro and rebuilding the project I get the same error.
Am I going about this the wrong way? I know it's supposed to be bad design to try to cast a parent class as a child, but in this case it seems like what I want, since my derived item class has new functionality and its objects need a way to call that new functionality on items around themselves, and asking the items scene object with itemAt() seems like the best way - but I need itemAt() to return a pointer of the right type. I can get around this by having the derived items cast the QGraphicsItem * returned by QGraphicsScene::itemAt() using dynamic_cast, but I don't really understand why that works and not qobject_cast, or the benefits or disadvantages to using dynamic_cast vs. qobject_cast.
EDIT:
forgot to mention that I also reimplemented QGraphicsItem::scene() in my derived class to return a DerivedScene *, as
DerivedScene* DerivedItem::scene() const
{
return qobject_cast< DerivedScene * >( QGraphicsItem::scene() );
}
but this doesn't appear to be causing a compilation error...
There is no point in inheriting from QObject just for casting. The advantage of qobject_cast
over dynamic cast is summed up pretty much in the qobject_cast documentation:
The qobject_cast() function behaves similarly to the standard C++ dynamic_cast(), with the advantages that it doesn't require RTTI support and it works across dynamic library boundaries.
It's nice to have and useful if you have QObjects, but not worth to inherit from QObject if it is all you want from QObject.
Also, for QGraphicsIems there is qgraphicsitem_cast, which should do exactly what you want :)
You have to pass a QObject pointer to qobject_cast() and QGraphicsScene::itemAt returns a QGraphicsItem pointer. Since, as you mentioned, QGraphicsItem does not derive from QObject, the complier gave you that error.