PDA

View Full Version : Custom Model Advice Requested



mclark
17th September 2008, 22:51
I need some advice about custom models. My only experience is using QTableWidget so this model/view stuff is new to me. I'm currently using Qt 4.3.4 on Windows XP.

My application is designed to configure network devices. From an outside source, it receives data for each device found on the network. Some of this data is to be displayed in tables (some of the displayed data is to be editable). The application will have a QTabWidget for each type of device and a tab to display all devices found. Each tab will display a table containing a subset of it's data.

Issue: The base table will show only the common data for each device found. The other 2 device-specific tables will show the common data plus some device-specific data.

Example:
Type-A table will display:

Name
SubType
IP Address
Port1 Value
Port2 Value


Type-B table will display:

Name
SubType
IP Address
Group ID


Base table will display all Type-A and all Type-B devices:

Name
SubType
IP Address


My data sets are something like this:


class CBaseData
{
public:
CBaseData( int nDevType );
virtual ~CBaseData( void );

// Accessors
QString GetDeviceName( void ) { return m_sName; }
void SetDeviceName( QString sName );

bool IsDynamicIP( void ) { return m_bIsDynamicIP; }
uint1 GetIPMode( void ) { return m_bIsDynamicIP ? 1 : 0; }
void SetIPMode( bool bIsDynamic ) { m_bIsDynamicIP = bIsDynamic; }

int GetDevType( void ) { return m_nDevType; }
void SetDevType( int nDevType ) { m_nDevType = nDevType; }

QString GetID( void ) { return m_sID; }
void SetID( QString sID ) { m_sCID = sID; }

QString GetIPAddr( void ) { return m_sIPAddr; }
void SetIPAddr( QString sIPAddr ) { m_sIPAddr = sIPAddr; }

QString GetSubNetMask( void ) { return m_sSubNetMask; }
void SetSubNetMask( QString sMask ) { m_sSubNetMask = sMask; }

QString GetDefIP( void ) { return m_sDefIP; }
void SetDefIP( QString sDefIP ) { m_sDefIP = sDefIP; }

QString GetModelName( void ) { return m_sModelName; }
void SetModelName( QString sName );

QString GetSerialNum( void ) { return m_sSerialNum; }
void SetSerialNum( QString sSerialNum );

QString GetHWVer( void ) { return m_sHardwareVer; }
void SetHWVer( QString sVersion );

QString GetSWVer( void ) { return m_sSoftwareVer; }
void SetSWVer( QString sVersion );

protected:
int m_nDevType; // Device Type
bool m_bIsDynamicIP; // Static or dynamic IP address
QString m_sID; // Device ID
uint4 m_sIPAddr; // IP address of device
uint4 m_sSubNetMask; // Subnet Mask of device
uint4 m_sDefIP; // Default IP address of device
QString m_sName; // Name of device
QString m_sModelName; // Manufacturer model name for this device (read-only)
QString m_sSerialNum; // Device serial number
QString m_sHardwareVer; // Hardware version number (read-only)
QString m_sSoftwareVer; // Application software version number (read-only)
// ... extra network connection data

};

class CTypeAData : public CBaseData
{
public:
CTypeAData( int nDevType );
virtual ~CTypeAData( void );

// Accessors
uint4 GetPort1Num( void ) { return m_nPort1Num; }
void SetPort1Num( uint4 nNum ) { m_nPort1Num = nNum; }

uint4 GetPort2Num( void ) { return m_nPort2Num; }
void SetPort2Num( uint4 nNum ) { m_nPort2Num = nNum; }

protected:
uint4 m_nPort1Num; // value for port 1
uint4 m_nPort2Num; // value for port 2
// ...extra private data
};

class CTypeBData : public CBaseData
{
public:
CTypeBData( void );
virtual ~CTypeBData( void );

// Accessors
uint1 GetGroupID( void ) { return m_nGrpID; }
void SetGroupID( const uint1 nID ) { m_nGrpID = nID; }

protected:
uint1 m_nSMPTEGrpID; // Group ID - R/W
// ...extra private data
};

As you can see, each data class seems to map nicely to a device/table. My problem is deciding the model(s) to choose.

I would like to use QAbstractTableModel as the parent of a custom model. Because I'm dealing with different data sets it seems like I will need a separate model for each table BUT I don't want to duplicate any code for the the Base table model.

Possibilities (please note that I'm just exploring here):
Keep a data pointer to each type of data in my custom model. Somehow determine which device type is required for model function calls.
Create a separate model for each data type. (How to handle the Base data table?)
Create a 'base' model and use that as a parent for custom Type-A and Type-B models. (Can this even be done?)
Any other possibilities out there?

caduel
18th September 2008, 07:41
One simplistic approach would be:

underlying data
* store all your "devices" in a QList<CBaseData*> (or use smart pointers)
model
* create on large (subclass of) QAbstractTableModel on that data
* define (using enums) the superset of all columns possible (never use integer constants for columns... your code will break)
* an index's row() is all you need to get to the corresponding CBaseData*
* use RTTI or a some type designator to determine the "device type" of a row: return QVariant() if a row does not really have one of the columns
view(s):
* use QSortFilterProxyModels for the specific device views: restrict columns (only those making sense in the view) and rows (only those with matching device type).

This is, I think, about as simple as you can do it.
A drawback is that if you extend your device hierarchy this "allknowing" model has to be modified, too.

HTH

mclark
18th September 2008, 15:59
Thanks caduel, it does seem like a simple solution. And, it solves my problem of showing common data in the 'Base' table by using different views for each device type.

The only drawback for me is I now need to understand the QSortFilterProxyModel ;).

caduel
18th September 2008, 16:26
That is not really hard.
(Except that you now have two models with their own indexes. Thus you have now the possibility to use an index of model A with model B which would be not good at all. Just remember that an index you get (usually from a view's signals) is an index of that view's model. If you use a proxy, it is therefore an index of the proxy model. You have to map the index back to the source model with QSortFilterProxyModel::mapToSource() or, if possible, avoid the danger by writing


// tv being a QTreeView* and idx an index 'signaled' by it
tv->model()->data(idx);
idx.data()

// rather than
myModel.data(idx); // with myModel being your (proxy's source) model
// This second approach is unhappy if you introduce proxies (you could layer those...) later on.

)

Hope this gets you going.