I'm creating an application which uses QSqlDatabase and QSqlTableModel for inserting and retaining data from an SQLite database file.
The database instance is being created in MyApplication:
MyApplication.h
#include <QSqlDatabase>
// ...
class MyApplication
{
public:
// ....
private:
QSqlDatabase _database;
};
MyApplication.cpp
MyApplication::start()
{
// ...
_database = QSqlDatabase::database();
// ...
}
For data model handling I'm using an overloaded class of QSqlTableModel:
SqlContactModel.h
#include <QSqlTableModel>
class SqlContactModel : public QSqlTableModel
{
public:
// ...
void setDatabase(const QSqlDatabase& database) { _database = database };
private:
QSqlDatabase _database;
};
SqlContactModel overloads the typical methods such as data(), roleNames() etc.
My instance of SqlContactModel is used in QML, thus I'm creating the instance in my main.cpp as follows:
main.cpp
int main()
{
// ...
qmlRegisterType<SqlContactModel>("io.taibsu.qxmt", 1, 0, "SqlContactModel");
}
Now since it's being created via qmlRegisterType, I can't pass any parameters in the constructor to set a different database in the SqlContactModel.
I need to pass a different database to it since I'm using multiple different overloaded QSqlTableModel classes (e.g., the other class is called SqlConversationModel and shall use the same _database instance which is used in MyApplication).
Now there are two ways to solve this: either pass the database somehow to my SqlTableModel subclasses via QML or find another way to tell the TableModel to not use its own database instance but the one already there.
Now my questions are:
How can I use a single database in different table models?
Is there a way to pass parameters when constructing a class instance in QML?
The best way probably is to set up your database from a controller class in C++, give that controller a (read only & constant) properly contactsModel and expose the controller as a singleton to QML. This way you can setup the database from C++ (where your business logic belongs) while you can access your data from QML. There probably isn’t even a need to have that property be another type than QAbstractItemModel* so you keep the freedom to modify it to another model type if needed in the future without affecting your QML. Such decoupling is desirable.
Related
Right up-front, I'll apologize: This is a monster question, but I wanted to provide what I hope is all of the pertinent details.
I've got a QML-based GUI that I was tasked with taking over and developing from proof-of-concept to release. I believe the GUI is based on an example provided by QT (Automotive, maybe?). The GUI is being compiled for web-assembly (emscripten) and features a "back-end data-client" which communicates with our hardware controller via a socket and communicates with the GUI via signals. The GUI is accessed via web browser and communicates with the Data_Client via QWebSocket.
The GUI proof was initially created with a very "flat" hierarchy where every element is created and managed within a single ApplicationWindow object in a single "main" QML file. A Data_Client object is instantiated there and all the other visual elements are children (at various levels) of the ApplicationWindow:
ApplicationWindow {
id: appWindow
//various properties and stuff
Item {
id: clientHolder
property Data_Client client
}
ColumnLayout {
id: mainLayout
anchors.fill: parent
layoutDirection: Qt.LeftToRight
//And so on...
The Data_Client C++ currently emits various signals in response to various things that happen in the controller application. In the main .QML the signals are handled as follows:
Connections {
target: client
onNew_status_port_data:
{
textStatusPort.text = qdata;
}
onNew_status_data_act_on:
{
imageStatusData.source = "../imagine-assets/ledGoodRim.png";
}
//and so on...
What I'm trying to do is create a ChannelStatusPanel object that holds the various status fields and handles the updates to those fields (text, images, etc.) when it receives information from the Data_Client backend. There are multiple instances of this ChannelStatusPanel contained in a MainStatusPanel which is made visible or not from the main ApplicationWindow:
Having said all of that (Phew!), I come finally to my question(s). What is the correct way to signal a specific instance of the ChannelStatusPanel object from the Data_Client with the various data items needed to drive changes to the visual elements of the ChannelStatusPanel?
I thought I was being clever by defining a ChannelStatusObject to hold the values:
Item {
id: channelStatusObject
property int channel
property int enabled //Using the EnabledState enum
property string mode
property int bitrate
property int dataActivity //Using the LedState enum
//and more...
property int packetCount
}
In the ChannelStatusPanel.qml, I then created a ChannelStatusObject property and a slot to handle the property change:
property ChannelStatusObject statusObject
onStatusObjectChanged: {
//..do the stuff
From the Data_Client C++ I will get the information from the controller application and determine which "channel" I need to update. As I see it, I need to be able to do the following things:
I need to determine which instance of ChannelStatusPanel I need to update. How do I intelligently get a reference to the instance I want to signal? Is that just accomplished through QObject::findChild()? Is there a better, faster, or smarter way?
In the Data_Client C++, do I want to create an instance of ChannelStatusObject, set the various fields within it appropriately, and then set the ChannelStatusPanel instance's ChannelStatusObject property equal to the newly created ChannelStatusObject? Alternatively, is there a mechanism to get a reference to the Panel's ChannelStatusObject and set each of its properties (fields) to what I want? In C++, something like this:
QQmlComponent component(&engine, "ChannelStatusObject.qml");
QObject *statObj= component.create();
QQmlProperty::write(statObj, "channel", 1)
QQmlProperty::write(statObj, "bitrate", 5000);
QQmlProperty::write(statObj, "enabled", 0);
//Then something like using the pointer from #1, above, to set the Panel property
//QObject *channelPanel;
QQmlProperty::write(channelPanel, "statusObject", statObj)
Is there some other, more accepted or conventional paradigm for doing this? Is this too convoluted?
I would go about this using Qt's model-view-controller (delegate) paradigm. That is, your C++ code should expose some list-like Q_PROPERTY of channel status objects, which in turn expose their own data as properties. This can be done using a QQmlListProperty, as demonstrated here.
However, if the list itself is controlled from C++ code -- that is, the QML code does not need to directly edit the model, but only control which ones are shown in the view and possibly modify existing elements -- then it can be something simpler like a QList of QObject-derived pointers. As long as you do emit a signal when changing the list, this should be fine:
class ChannelStatus : public QObject
{
Q_OBJECT
public:
Q_PROPERTY(int channel READ channel CONSTANT)
Q_PROPERTY(int enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
// etc.
};
class Data_Client : public QObject
{
Q_OBJECT
public:
Q_PROPERTY(QList<ChannelStatus*> statusList READ statusList NOTIFY statusListChanged)
// ...
};
The ChannelStatus class itself must be registered with the QML type system, so that it can be imported in QML documents. Additionally, the list property type will need to be registered as a metatype, either in the main function or as a static variable. Otherwise, only lists of actual QObject pointers are recognised and you would have to provide yours as such.
qmlRegisterUncreatableType<ChannelStatus>("LibraryName", 1, 0,
"ChannelStatus", "Property access only.");
qRegisterMetaType<QList<ChannelStatus*>>();
You then use this property of the client on the QML side as the model property of a suitable QML component, such as a ListView or a Repeater inside a container like RowLayout. For example:
import LibraryName 1.0
ListView {
model: client.statusList
delegate: Column {
Label { text: modelData.channel }
Image { source: modelData.enabled ? "foo" : "bar" }
// ...
}
}
As you can see, the model data is implicitly attached to the delegate components. Any NOTIFYable properties will have their values automatically updated.
Below is how I've setup my application.
Front-end is in QML. Back-end is in Qt C++. H/W Controller application is in C.
In Qt C++ back-end, I have QObject derived database and databasePoint classes.
database object holds a QMap of databasePoint objects.
Each databasePoint object has a unique point name, which is used as an identifier.
database object is created in main.cpp and exposed to QML as a context property.
database class has a method to return a pointer to databasePoint object.
In QML, this method is used to create databasePoint objects.
When this method is called, a databasePoint object is created and added to QMap if it doesn't already exist.
databasePoint class has read-write value properties.
These properties are used for communication between UI, back-end and controller.
In a timer, latest value is polled from controller periodically, whenever there's a change, the value property is updated.
When the value property is updated from UI, the value is written to controller.
class database : public QObject
{
public slots:
databasePoint* getDbpointObject(QString pointName);
private:
QMap<QString, databasePoint*> dbPointMap;
};
class databasePoint : public QObject
{
Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged)
public:
QVariant value(void);
void setValue(QVariant value);
QVariant my_value;
signals:
void valueChanged();
};
DatabasePointComponent.qml:
Item {
required property string pointName
property var pointObj: database.getDbpointObject(pointName)
}
MyScreen.qml:
DatabasePointComponent{
id: dbTEMP
pointName: "TEMP"
}
TextInput {
text: dbTEMP.pointObj.value
onAccepted: dbTEMP.pointObj.value = text
}
In Qt-creator, I created SQLite database in a class called databaseManager, as follow:
QString DatabaseManager::open_db()
{
QSqlDatabase db;
QString path = "/Users/me/Documents/workspace/Muasaa/";
db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName(path+"Database v.1");
if (db.open()){
return "Database is created, open, and ready ...";
} else {
return db.lastError().text();
}
}
Then I define the following in the header file of the MainWindow class:
Public:
DatabaseManager *db_manager;
In the source file, I call it as follow:
db_manager->open_db();
which creates and open the database.
However, I would like to use a reference to same database to use it in many functions in the MainWindow source file. How can I do that ?!
Move the QSqlDatabase db variable into your class header, and add a method for getting it. As long as you create an instance of your databaseManager class and maintain a pointer to it in MainWindow you'll be able to retrieve it.
QSqlDatabase::database() is a static function that returns a QSqlDatabase.
If you have more than one database, you have to provide a connection name in addDatabase() and in database()
Maybe a design solution may help?
Whatever your goal is, consider turning your DatabaseManager class into a Singleton. This way you'll be able to use your manager in all the gui classes. Something like DatabaseManager::instance()->your_method()
Advantages:
You are sure the connections are managed the right way
Less oportunities for build problems
By the way, I'm not sure, but your program crash may be the result of using your db_manager pointer before it was initialized, in a slot maybe, or (more probable) it's an internal connection error. Have you checked the same connection attributes in a minimum possible example?
In my QMainWindow scope I have an instance of a class AgilentSweeper which inherits QDockWindow, and initialize and display it with:
sweeper = new AgilentSweeper(this);
addDockWidget(Qt::RightDockWidgetArea,sweeper);
This class has a Qt Creator-made .ui form which has several widgets in it and the constructor looks like:
AgilentSweeper::AgilentSweeper(QWidget *parent) :
QDockWidget(parent),
ui(new Ui::AgilentSweeper)
{
ui->setupUi(this);
}
where Ui::AgilentSweeper *ui is public.
From other functions in the AgilentSweeper scope I can access the AgilentSweeper widgets and do things like double power = ui->powerSpinBox->value(); normally. However, I can't figure out how to access the AgilentSweeper widgets from within the scope of the main Ui. It seems like this should be possible because sweeper is a member I thought I should be able to do something like double power = sweeper->ui->powerSpinBox->value();, but despite messing around with it for a while I can't figure out how to access anything from sweeper's ui. I can think of several work-arounds, but this seemed like it should be possible.
ui Object is defined as private member of AgilentSweeper by default. So normally, you can't access it.
Some solutions:
very bad: declare QMainWindow as friend
bad: Change in AgilentSweeper.h private: by public: for the Ui::AgilentSweeper
one I would prefer:for each method of your UI-widgets you want to expose, create a public method in AgilentSweeper.h
So you can access the spinbox-value by
//in AgilentSweeper.h
double powerSpinBoxValue()
{
return ui->powerSpinBox->value()
}
//call from outside:
double power = sweeper->powerSpinBoxValue();
EDIT Explanation for 2
The object Ui::AgilentSweeper *ui is defined in AgilentSweeper.h usinf forward declaration. So in a file including AgilentSweeper.h alone, no information is given, how to create an instance of the object and which methods it provides.
This information is provided by autogenerated file ui_AgilentSweeper.h. so in order to use the way 2, include also ui_AgilentSweeper.h.
Why is this solution bad? It looks so flexible right?
In the first line exposing members as public is bad. A user could delete the instances: e.g. delete sweeper->ui->powerSpinBox.
Besides, there is no way to log or lock the access to the member objects.
I am using the Qt libraries in a C++ project but I have a design question: where should a database be declared? I would prefer not to declare global variables.
Currently I am dealing with this problem in this way. I have a mainwindow and I have declared the DB in there so I perform the queries in the main window and pass the results to the dialogs using different signals and slots.
I start the DB when the main window starts and close it when the window has been closed. I don't know if this is ok
Now I need the DB connection in another class as well so I can pass a reference to the DB or make the DB global
I don't like these solutions.. is there a standard pattern to deal with this situation?
edit:
My class now looks like:
class Database
{
public:
bool open(void);
bool close(void);
static Database* getDatabase(void);
// various methods like loadThisTable(), saveThisTable() etc
private:
Database(); // disable constructor
~Database(); // disable destructor
Database(const Database&); // disable copy constructor
Database& operator=(const Database&); // disable assignment
static Database* instance_; // database instance
QSqlDatabase qtDB; // qt db database
}
If I want I can add the add and remove methods but I have a single DB instance.
If you're using QSqlDatabase, you don't really need to make it a global variable. Just set up the connection when you first start your application, then use the static QSqlDatabase methods to access the connection when you need it in different modules.
Example
QSqlDatabase db; // set up the default connection
// alternative: set up a named connection
// QSqlDatabase db("conn-name");
// set the connection params and open the connection
// ... later on
QSqlDatabase db = QSqlDatabase::database(); // retrieve the default connection
// alternative: retrieve the named connection
// QSqlDatabase db = QSqlDatabase::database("conn-name");
From the docs:
QSqlDatabase is a value class. Changes made to a database connection via one instance of QSqlDatabase will affect other instances of QSqlDatabase that represent the same connection. Use cloneDatabase() to create an independent database connection based on an existing one.
Note: If you're application is multi-threaded, you have to be careful to only use a connection in the thread in which it was created.
You need a singleton pattern. It's a global class which have only one instance. Someone calls it antipattern (and sometimes it is), but it is the best way to handle resources like database connections. And dont forget that you can use QSqlDatabase QSqlDatabase::database ( const QString & connectionName = QLatin1String( defaultConnection ), bool open = true ) [static] method to get QSqlDatabase instance by name (name can be set via QSqlDatabase QSqlDatabase::addDatabase ( QSqlDriver * driver, const QString & connectionName = QLatin1String( defaultConnection ) ) [static] method) to avoid creating singleton just for storing QSqlDatabase instances.
I'm writing a Qt application to allow the visualization of very heavy data sets.
So, I have a SourceDataModel class, inheriting from QAbstractItemModel that seems to work properly (currently, I only display it in QTableView/QTreeView but later on, I'll create some custom views).
Now, I would like to be able to filter this data, that is
being able to have different data
resolution (i.e. only exposing 1
data item out of 2)
being able to
apply some filters on the data (i.e.
displaying unix timestamps as
dd/MM/yyyy hh:mm:ss)
So I started to create a ProxySourceDataModel class, which inherits from my SourceDataModel and stores one instance, and basically delegates everything to the instance. From this ProxySourceDataModel, I noticed that no data was displayed when I used it in a QTableView. After some investigation, it seems that it was because i had to forward the signals and slots from the underlying SourceDataModel. No problem, i did it.
But still 2 problems remain, and I can't figure out how to handle them:
I am not able to select the data in the views. If I use the SourceDataModel directly, no problem. But using the ProxySourceDataModel i can't select anything.
The data is not filtered at all! I overloaded data() in ProxySourceDataModel, and forward all the other calls to the underlying SourceDataModel. But still, only SourceDataModel::data() is called.
Here is some code to illustrate what I'm doing:
class SourceDataModel : public QAbstractItemModel
{
//...
};
class ProxySourceDataModel : public SourceDataModel
{
public:
ProxySourceDataModel(SourceDataModel& model)
: model_(model)
{
// For all QAbstractItemModel's signals emitted by the underlying model,
// I propagate them like this
QObject::connect( &model_, SIGNAL( the_signal()),
this, SLOT (forward_the_signal())) ;
}
slots:
void forward_the_signal()
{
emit the_signal();
}
public:
// For all QAbstractItemModel's virtual function, I do something like this
virtual void the_function()
{
model_.the_function();
}
// This is where I was hoping to do the filtering
virtual QVariant data( const QModelIndex& index,int role=Qt::DisplayRole )
{
return filter( model_.data(index,role) );
}
private:
SourceDataModel& model_;
};
SourceDataModel sourceDataModel;
QTableView view;
view.setModel( new ProxySourceDataModel(sourceDataModel) );
Any help or advice greatly appreciated, thanks for reading!
-------------------- EDIT ------------------------
I found it!
The problem was that the view do not use QAbstractItemModel::data() from its model, but rather calls QModelIndex::data() on its items, which in turn calls the QAbstractItemModel::data() of the item's underlying model.
And since my proxy returned model indexes from the underlying model, that is why the SourceDataModel::data() was always called instead of ProxySourceDataModel()!
I just reimplemented ProxySourceDataModel::index() to return local indexes, and it works like a charm.
Visit In QT, chaining models does not work as expected for more informations.
Thanks!
The problem was that the view do not use QAbstractItemModel::data() from its model, but rather calls QModelIndex::data() on its items, which in turn calls the QAbstractItemModel::data() of the item's underlying model.
And since my proxy returned model indexes from the underlying model, that is why the SourceDataModel::data() was always called instead of ProxySourceDataModel()!
I just reimplemented ProxySourceDataModel::index() to return local indexes, and it works like a charm.
Visit In QT, chaining models does not work as expected for more informations.