PDA

View Full Version : Model view Controller Library



luisvaldes88
28th September 2011, 03:36
Well, I work as a software developer in a company. The system we maintain is an Enterprise Resource Planning, it is build on Delphi.
We work with Oracle Database and the application is only deployed on Windows.
My native language is Spanish, so sorry if my English is not good.

I am a big fan of C++ and Qt, now I am working on a project to reach a bigger market that the current system can’t reach, this application should acomplish some new requirements:

-> Must be easy to creade CRUD Forms to watch, create and edit information of the database.
I am trying to make a base library on the “MVC pattern”. This MVCLib depends on Qt and “QxOrm library” to work with the database.

The CRUD form has a panel with some action buttons, a tabbed pane with a Form that act as a List of the rows contained in the database, this list
is always in the index 0 (zero) of the tabbed pane.
We can think of this CRUD form as a QWidget that has a group of buttons inside a QLayout, we will call this form ‘CRUDForm’.
The second form is a QWidget that has a list of filters, widgets that work with QSortFilterProxyModel and a QSqlQueryModel to show information of the database, this information
is showed in a QTableView and whenever the user double clicks a row, it should create another widget to show the more specific information. This form is called ‘CRUDListView’.
The more specific widget to show the information of a ‘CRUDListView’ double clicked item is a form created by the programmer that implements the specialized form. But this form
already have three widgets inside, they show a base and standard information of the entity, this form is called ‘CRUDEntityView’, all the widgets that can edit data
have the same name of the datasource property that it edits, these widgets are handled by an DinamicWidget object that receive events from the widget.
The DinamicWidget is an adapter class, it has a list of slots that are connected to diferent kind of signals, and it convert every signal received to a setValue(QVariant) slot
that emits changeValue(QVariant) signal, setValue(QVariant) receives new values from the widget and emits a changeValue(QVariant) signal that is connected to a DinamicItem object.
The DinamicItem object is inside ‘CRUDEntityModel’ and updates the data of the model.
The ‘CRUDForm’ is able to show many diferent entities at the same time, it does not show an entity twice. The form can edit data of only one entity at a time.
Theese Forms are the base widgets of the library. I did not have time to make a better explanation, but is a good start.

I designed a base QxOrm class that will be the base of all concrete entities of the application, so all the entities will have the same base information always.

The library is divided in 5 (five) packages: * global: here I have the Q_DECL_EXPORT and Q_DECL_IMPORT #defines * core: here are the clases that handle the events between the model and view, is not exactly the controller, but is something like that, although there is a class that connects to the database and bring the idValue of a new entity at the time it is inserted. * models: here are the models ‘CRUDListModel’ and ‘CRUDEntityModel’ * views: here are the views ‘CRUDListView’ and ‘CRUDEntityView’ * factories: here is the AbstracFactory that has the methods to create an instance of ‘CRUDListView’ and ‘CRUDEntityView’

This part is the one I want to discuss in this thread.

-> Must be self updateable over the internet
The main application is the ERP system, but in my plan is a second application, an Server/Client application that changes files of a new release.
That is why the application must be sort of a plugin based application, to allow it to be upgraded easily without changing the main application.

This part is not for discussing in this thread.

-> Must have a help system included.

This part is not for discussing in this thread.

As I said, ia am big fan of C++ and Qt, but I am not an expert, that is why I came to this forum, to search for the opinions of experts.
May by there is a better and diferent aproach, I read about the ItemDelegates, but they do not meet the requirements I have.

I hope you could have time to share your knowleadge with me!

Best Regards.

////////////////////-->> Orm Package Begin <<--////////////////////
@
/**
* @Description: OrmClass is the base class for al concrete entities in the application, it has three base properties
* > idValue: is the primary key of the QxOrmClass mapped table.
* > dateOfInsertion: is the date of insertion (system date).
* > dateOfUpdate: is the date of the last update.
* Theese three properties are called 'Base Properties' and are part of an internal pattern where every concrete entity
* must have a column with an integer value as an id, a column with a date time value as date of insertion of the row,
* and a column with date time value as date of the last update of the row. Also they are mandatory, not updateable and read only.
**/
class OrmClass : QObject
{
Q_OBJECT
Q_PROPERTY(int idValue
READ idValue
WRITE setIdValue)
Q_PROPERTY(QDateTime dateOfInsertion
READ dateOfInsertion
WRITE setDateOfInsertion)
Q_PROPERTY(QDateTime dateOfUpdate
READ dateOfUpdate
WRITE setDateOfUpdate)
public:
int m_idValue;
QDateTime m_dateOfInsertion;
QDateTime m_dateOfUpdate;

//getters
int idValue();
QDateTime dateOfInsertion();
QDateTime dateOfUpdate();
//setters
void setIdValue(int value);
void setDateOfInsertion(QDateTime value);
void setDateOfUpdate(QDateTime value);
}
@
////////////////////-->> Orm Package End <<--////////////////////

////////////////////-->> Core Package Begin <<--////////////////////
@
class DinamicObject : QObject
{
Q_OBJECT
Q_PROPERTY(QVariant value
READ value
WRITE setValue
NOTIFY valueChanged)
QVariant m_value;
QVariant::Type m_type;
public:
QVariant value();
QVariant::Type type();
public slots:
void setValue(QVariant value);
signals:
void valueChanged(QVariant value);
}


class DinamicItem : public DinamicObject
{
Q_OBJECT
public:
/**
DinamicItem es un objeto utilizado para manipular los datos de un datasource
de manera que al crear un DinamicWidget, los eventos del widget sean pasados al
DinamicItem, y este ultimo sea el encargado de alterar los datos.

@param name - the property name
@param label - the prompt or label you want the user to see
@param defaultValue - the default value (if any)
@param type - the type of the answer you want to get back
*/
explicit DinamicItem(QString name, QString label, QVariant defaultValue = QString(),
QVariant::Type type=QVariant::String, QObject *parent = 0);

/**
DinamicItem es un objeto utilizado para manipular los datos de un datasource
de manera que al crear un DinamicWidget, los eventos del widget sean pasados al
DinamicItem, y este ultimo sea el encargado de alterar los datos.

@param name - the property name
@param label - the prompt or label you want the user to see
@param defaultValue - the default value (if any)
@param type - the type of the answer you want to get back
*/
explicit DinamicItem(QString name, QVariant::Type type,
QVariant defaultValue, QObject *parent = 0);

QString label() const {return m_Label;}

protected:
void setLabel(QString label) ;
QString m_Label;

public slots:
void setDataSourceValue(QVariant value);
virtual void setValue(QVariant value);
signals:
void dataSourceValueChanged(QVariant value);
};

/* << Adapter Pattern >> */
class DinamicWidget : DinamicObject
{
Q_OBJECT
Q_PROPERTY(bool mandatory
READ mandatory
WRITE setMandatory)
Q_PROPERTY(bool updateable
READ updateable
WRITE setUpdatable)
Q_PROPERTY(bool readOnly
READ readOnly
WRITE setReadOnly)
QWidget *m_widget;
public:
explicit DinamicWidget(QWidget *widget, QObject *parent = 0);
public slots:
void setValue(QString value);
void setValue(QDateTime value);
void setValue(QDate value);
signals:
void stringValueChanged(QString value);
void dateTimeValueChanged(QDateTime value);
void dateValueChanged(QDate value);
}

// DinamicWidget::DinamicWidget implementation
DinamicWidget::DinamicWidget(QWidget *widget, QObject *parent):
DinamicObject(parent), m_mandatory(true), m_updatable(true), m_readOnly(false)
{
m_widget = widget;
QString className(widget->metaObject()->className());
if(className == "QLineEdit")
{
QLineEdit *lineEdit = qobject_cast<QLineEdit *>(widget);
connect(lineEdit, SIGNAL(textChanged(QString)),
this, SLOT(setValue(QString)));
connect(this, SIGNAL(stringValueChanged(QString)),
lineEdit, SLOT(setText(QString)));
}
else if(className == "QDateTimeEdit")
{
QDateTimeEdit *dateTimeEdit = qobject_cast<QDateTimeEdit *>(widget);
connect(dateTimeEdit, SIGNAL(dateTimeChanged(QDateTime)),
this, SLOT(setValue(QDateTime)));
connect(this, SIGNAL(dateTimeValueChanged(QDateTime)),
dateTimeEdit, SLOT(setDateTime(QDateTime)));
}
}
@
////////////////////-->> Core Package End <<--////////////////////

more code

////////////////////-->> Model Package Begin <<--////////////////////
@
/**
* @Description: CRUDEntityModel
**/
class CRUDEntityModel : public QAbstractTableModel
{
Q_OBJECT
OrmClass * m_ormClass;
QList<DinamicItem *> m_dinamicItems;
public:
// to allow the acces of m_dinamicItems to AbstractFactory
friend class AbstractFactory;
explicit CRUDEntityModel(QObject *parent = 0);
/**
* @param OrmClass *data:
* Creates a DinamicItem object for every property of 'data' except the 'objectName' property.
* Every DinamicItem is connected to the 'model' and when a change event raises it is handle properly.
**/
virtual void setDataSource(OrmClass *data);
// methods to search and investigate
virtual void save();
virtual void load();
virtual void insert();
public slots:
/**
* @param QVariant value:
* This slot is connected to the valueChaged signal any DinamicItem in the 'model'. It sets the correspondant
* property of the OrmClass object to its new value through the setDataSourceValue slot.
* This method is the one that will update the OrmClass object's information.
**/
void setDataSourceValue(QVariant value);
signals:
void dataSourceValueChanged(QVariant value);
};

// CRUDEntityModel::setDataSource implementation
void CRUDEntityModel::setDataSource(OrmClass *data)
{
const QMetaObject *metaObject = data->metaObject();

// i = 1 because the properyt at index '0' is 'objectName'
for(int i = 1; i < metaObject->propertyCount(); i++)
{
QMetaProperty property = metaObject->property(i);
QVariant value = data->property(property.name());
QString propertyName(property.name());

DinamicItem *item = new DinamicItem(propertyName, value.type(), value, this);

connect(item, SIGNAL(dataSourceValueChanged(QVariant)),
this, SLOT(setDataSourceValue(QVariant)));
//connect(this, SIGNAL(dataSourceValueChanged(QVariant)),
// item, SLOT(setDataSourceValue(QVariant)));
m_dinamicItems.append(item);
}

}

// CRUDEntityModel::setDataSourceValue implementation
void CRUDEntityModel::setDataSourceValue(QVariant value)
{
m_ormClass->setProperty(sender()->objectName().toLocal8Bit().data(), value);
//emit dataSourceValueChanged(value);
}
@
////////////////////-->> Model Package End <<--////////////////////

////////////////////-->> Factories Package Begin <<--////////////////////
@
/* << Abstract Factory Pattern >> */
class AbstractFactory
{
protected:
//constructor and copy constructor
public:
/**
* @param QWidget *widget:
* @param CRUDEntityModel *model:
* Compares the objectName of every DinamicItem object of the 'model' with the objectName of every child widget of the 'widget',
* when a match is found it creates a DinamicWidget object with the child as a parameter and thus connected to the child events.
* The DinamicWidget handle the events between the 'widget' and the 'model', to do this it connect the signal valueChanged(QVariant)
* to the setValue(QVariant) of the DinamicItem object and its correspondant DinamicWidget object the DinamicWidget is connected
* to the signal of the QWidget, and emits a valueChanged whenever it receives an event from the QWidget.
**/
static CRUDEntityView * createRegistroView(QWidget *widget, CRUDEntityModel *model);
}
@
// CRUDEntityView::createRegistroView implementation
@
CRUDEntityView * AbstractFactory::createRegistroView(QWidget *widget, CRUDEntityModel *model)
{
CRUDEntityView *CRUDEntityView = new CRUDEntityView;
// it performs a recorrido on the DinamicItem list of the CRUDEntityModel
// and creates a DinamicWidget * for each name coincidence by comparing a DinamicItem with a child of QWidget *widget.
// The signals of the child widget is connected to the slot of thw DinamicWidget, and the signal and slot of the Dinamic widget are connected
// to the singnal and slot of the DinamicItem
foreach(DinamicItem *dinamicItem, model->dinamicItems())
{
QWidget *child = widget->findChild<QWidget *>(dinamicItem->objectName());
if(child)
{
// el constructor de DinamicWidget se encarga de hacer las connections con el widget
// encargado de los eventos del usuario
DinamicWidget *dinamicWidget = new DinamicWidget(child);

connect(dinamicWidget, SIGNAL(valueChanged(QVariant)),
dinamicItem, SLOT(setValue(QVariant)));
connect(dinamicItem, SIGNAL(valueChanged(QVariant)),
dinamicWidget, SLOT(setValue(QVariant)));
CRUDEntityView->dinamicWidgets.append(dinamicWidget);
}
}
CRUDEntityView->setMainForm(widget);
return CRUDEntityView;
}@
////////////////////-->> Factories Package End <<--////////////////////