PDA

View Full Version : Understanding Roles



drmacro
31st March 2017, 16:53
Hi,

I did some searches, difficult to say if I was getting limited results because of search terms though.

Roles are used a lot in Qt (in my case PyQt) and I would like to understand the basic concept in more detail.

I've used them, by example, but not felt I knew for sure what I'd done... :p

It's likely they are way simpler than I imagine and that may be why I don't completely understand.

Specifically, I don't think I get why/when the role is set by a caller.

Are they really just flags that indicate what, for example, a subclass I write can key on to decide what actions I take?

Pointers to sources I've not read yet happily accepted. :)

Regards,
Mac

d_stranz
31st March 2017, 19:31
I think you're going to have to be a whole lot more specific about what you mean by "role" before anyone can give you a sensible answer to this. Maybe you can give an example piece of code that uses what you are calling a "role". That would help.

drmacro
31st March 2017, 20:04
Ok, here's an example of some example code I've used with a sub class of a table model. I got the code from some example an muddled through getting the background color to change based on the value of the table cell.

But, I don't fully understand what is happening in the "data" method when the role is DisplayRole (I didn't change that part of the elif, it came with the example. (Why does the DisplayRole return code return empty QVariants?)

Again, from the example, the "setData" is as it came with the example (even the comment) and I'm getting data in my table so it's getting set elsewhere (actually, in the init for my sub class). So, is this method needed? And why would this have a role associated?

And why does "headerData" return an empty QVariant when the role isn't DisplayRole?


def data(self, index, role):
if not index.isValid():
return QVariant()
elif role == Qt.BackgroundColorRole:
if self.arraydata[index.row()][7] == 'Stage':
...
elif role != QtCore.Qt.DisplayRole:
return QtCore.QVariant()
return QtCore.QVariant(self.arraydata[index.row()][index.column()])


def setData(self, index, value, role):
pass # not sure what to put here


def headerData(self, col, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return QtCore.QVariant(self.headerdata[col])
return QtCore.QVariant()

d_stranz
31st March 2017, 20:53
OK, I suspected this what what you are referring to.

"Roles" as they are used in the Qt Model / View framework are simply a set of flags that are used to indicate which specific value is being asked for in a call to a model method. See the documentation of Qt::ItemDataRole for the list.

In your Python example, the role that is passed in is evaluated in a set of if / elif conditionals; in C++ this is typically done in a switch statement:



QVariant retVal;
switch( role )
{
case Qt::DisplayRole:
retVal = QString( "Here is the text I want to display" );
break;

case Qt::DecorationRole:
retVal = QIcon( "myIcon.png" );
break;
// ...
}
return retVal;


Whenever your (or a standard Qt model's) implementation of the data() method wants the model to do whatever is the default for a given role, it returns an empty QVariant. So, if I don't define tool tips for my custom model, then I return QVariant() when asked for Qt:: ToolTipRole. The default behavior in this case is to not display a tool tip.

For Qt:: DisplayRole you will return a QString (inside of a QVariant) that contains the text you want displayed in whatever view element (cell, row, list item, etc.) is displaying the content of that particular QModelIndex in the model. FontRole lets you return a QFont that specifies how the text is formatted. If you return an empty QVariant for this role, then the view's default font is used. Likewise, ForegroundRole lets you specify the text color, BackgroundRole lets you specify the background color behind the text.

In any case where you want the default behavior to occur, you simply return an empty QVariant. If you do this for DisplayRole, then you get an empty cell in your table, the same as if you had passed back an empty QString since the default behavior is to display nothing. ForegroundRole applies the default text color, BackgroundRole applies whatever color the whole view uses as a background.

This same set of ItemDataRole values is used in the setData() method, this time to change what is held in the model.

There is a special set of role values starting with 0X0100 (Qt:: UserRole) that lets you define your own roles. These are usually used with a custom view class that understands those roles (your generic Qt views don't know anything about UserRole values).

An example of using custom UserRole values: I have a table-based model that I display in a table view. There, I am mostly concerned with the DisplayRole, ForegroundRole, and BackgroundRole. The table view doesn't know anything about the custom UserRoles I have defined.

However, I also use the same table model to construct x-y scatter plots. In this case, the plot is told to select "x" data from some column (say column 3) in the table model, and "y" data from a different column. I could have the plot view ask for the DisplayRole for each of these, in which case it would get a formatted QString that it would then have to turn back into a number to give an x or y coordinate. Instead, I added a custom role with the value UserRole + 1, that returns a QVariant wrapping a floating point value. The plot view asks for this role when requesting the x or y coordinate. My plots also have the ability to vary the size of the dots, so for that there is another UserRole type (UserRole + 2) that returns the dot size. Another UserRole + 3 gives me the ability to define a custom marker for each point in the plot, and so on.

Qt could have implemented an alternative. Instead of a data() method that takes a Qt: ItemDataRole flag, it could have implemented a dozen or more separate methods: one named displayData(), one named iconData(), and so forth, that only took the QModelIndex as an argument. While this would work for the standard roles, it wouldn't work all that well for custom UserRole values because you can define any number of them in a custom model. It also leads to "interface bloat" - lots of little class methods where just one would do.



But, I don't fully understand what is happening in the "data" method when the role is DisplayRole (I didn't change that part of the elif, it came with the example. (Why does the DisplayRole return code return empty QVariants?)

In your particular example, the logic is as follows:

- if the index refers to an invalid (row, column) combination, then an empty QVariant is returned. Invalid indexes are used frequently for various reason and contain (-1, -1) as coordinates.
- if the role is BackgroundColorRole (which is obsolete, by the way), it returns a color if column 7 of the row contains the word "Stage"
- if the role is anything else -except- DisplayRole, it returns an empty QVariant and the view does whatever it does by default
- if the role is DisplayRole, it returns whatever is in the position (row, column) of the data array that underlies the custom model. Python coerces this into a displayable string in the case where it isn't a string to begin with. As you know, Python collections can contain a mixed bag of any data type, so element (0,0) might contain 1, (0,1) contains 3.14159, and (0,2) contains "Bananas". So the return values in these three cases would be strings: "1", "3.14159" and "Bananas", suitable for display in a table view.

drmacro
2nd April 2017, 17:53
That is a great explanation and how roles work is now much clearer!