Can two QStandardItem objects handle different attributes from the same object? - qt

I'm a newbie with the Model/View programming of Qt and have read the Editable Tree Model Example in the Qt documentation. The nice feature in this example is that a single object (TreeItem) encapsulates two pieces of information that later are displayed in a single row containing two columns (name and description) thanks to overriding of QModelIndex QAbstractItemModel::index and QVariant QAbstractItemModel::data.
Now, I also have a custom class (e.g. Foo) containing two pieces of information (Foo::m_name and Foo::m_description) that I want to display in a single row containing two columns, but instead of subclassing QAbstractItemModel I want to subclass QStandardItemModel because it has some much functionality. However, it seems I must create two QStandardItem objects for each of my Foo objects, one to handle m_name and another to handle m_description. How can I keep a single Foo object in memory and have these two QStandardItem objects refer to it?
In my question there's the implicit assumption that one must create a QStandardItem object for each (row, column) pair. Please let me know if this is wrong.

QStandardItemModel is all about storing the data in the model, so each cell is represented by a QStandardItem which holds that cell's data.
If the data is already stored elsewhere and should not be duplicated, then the QStandardItemModel is the wrong approach and a custom model is the way to go.
A custom model, in the case of a tree structure derived from QAbstractItemModel, is just an interface between the view and the data, so the data resides only once in memory.

A post in qtcentre suggested Chapter 4 of Advanced Qt Programming and lo and behold, there's a discussion of a tree subsclassing QstandardItemModel and QStandardIteml where each row of the tree is made up of three QstandardItem handling different properties of a single object.
The implementation source code is freely available
Basically, one has:
class myItem : public QStandardItem {
public:
myItem(Foo &afoo) : QStandardItem(afoo.getName()), m_foo(afoo) {
m_description = new QStandardItem(afoo.getDescription());
}
QstandardItem *m_description; // display m_description
private:
Foo &m_foo;
};
and then we insert a row of two QstandardItem in our model tree
class myModel: public QStandardItemModel {
StandardItem *myModel::appendRow(QStandardItem *parent, Foo &afoo)
{
auto *doublet = new myItem(afoo);
parent->appendRow(QList<QStandardItem*>() << doublet
<< double->m_description);
return nameItem;
}
}

Related

How to set data inside a QAbstractTableModel

I need to implement a table with Qt.
I believe I'll be suing a QAbstractTableModel, with a QTableView using this model.
I understand I'll have to edit the rowCount(), columnCount(), and data() functions of the model.
However, I don't understand how to exactly set the data inside the model, so that data() function can retrieve it..
Is the setData() function provided for this purpose? I have seen it takes EditRole as its parameter, which I don't want, as I don't want my table to be editable.
So, how do I "set" data inside the model, or have data for the model to get at, using data() function?
Also, how is the data() function called, i.e., who calls it and where would it need to be called?
Please help me with this.
Thanks.
How the actual data is kept in memory, generated or queried from a data store is completely up to you. If it's static data, you can use the Qt container classes or custom data structures.
You only need to reimplement the setData() method for editable models.
There are 4 methods you need to implement in a non-editable QAbstractTableModel subclass:
int rowCount()
int columnCount()
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole )
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole)
These methods are called from the view, usually a QTableView instance. The first two methods should return the dimensions of the table. For example, if rowCount() returns 10 and columnCount() returns 4, the view will invoke the data() method 40 times (once for each cell) asking for the actual data in your model's internal data structures.
As an example suppose you have implemented a custom slot retrieveDataFromMarsCuriosity() in your model. This slot populates a data structure and is connected to a QPushButton instance, so you get fresh data by clicking a button.
Now, you need to let the view know when the data is being changed so it can update properly. That's why you need to emit the beginRemoveRows(), endRemoveRows(), beginInsertRows(), endInsertRows() and its column counterparts.
The Qt Documentation has everything you need to know about this.
You don't need to use setData(...). Instead, you need to subclass QAbstractTableModel in such a way that its methods rowCount(), columnCount(), data(index) and potentially headerData(section, horizontalOrVertical) return the data you wish to display. Here's an example based on PyQt5:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
headers = ["Scientist name", "Birthdate", "Contribution"]
rows = [("Newton", "1643-01-04", "Classical mechanics"),
("Einstein", "1879-03-14", "Relativity"),
("Darwin", "1809-02-12", "Evolution")]
class TableModel(QAbstractTableModel):
def rowCount(self, parent):
# How many rows are there?
return len(rows)
def columnCount(self, parent):
# How many columns?
return len(headers)
def data(self, index, role):
if role != Qt.DisplayRole:
return QVariant()
# What's the value of the cell at the given index?
return rows[index.row()][index.column()]
def headerData(self, section, orientation, role):
if role != Qt.DisplayRole or orientation != Qt.Horizontal:
return QVariant()
# What's the header for the given column?
return headers[section]
app = QApplication([])
model = TableModel()
view = QTableView()
view.setModel(model)
view.show()
app.exec_()
It's taken from this GitHub repository and displays the following table:

Using QStandardItemModel like QStringListModel

I am trying to use QStandardItemModel to do the same thing as QStringListModel (just for practice):
http://programmingexamples.net/wiki/Qt/ModelView/StandardItemModel
However, one cell shows up, but it is empty, as opposed to containing "text" as I would expect. Can anyone explain this? Is this the right way to go about using the QStandardItemModel (i.e. constructing QStandardItems and feeding them to the model?)
Actually, I'm surprised you aren't getting a crash. You are creating item0 on the stack, then passing a pointer to it to the QList. When that method leaves scope, item0 is deleted, and your list contains a pointer to the rotting area of memory that used to hold a QStandardItem.
{
QStandardItem item0("test");
QList<QStandardItem*> items;
items.insert(0, &item0); // Doesn't transfer ownership
model->appendRow (items);
} // Out of scope! Oh no!
Typically you would just create the new item, then add it using something like QStandardItemModel::setItem, like this:
QStandardItem *item0 = new QStandardItem("test");
model->setItem(0, 0, item); // transfers ownership of item0 to the model

Qt: update pixmap grid layout with 2d array values

I am undertaking a game using a combination of c++ in visual studios 2010 and Qt 4.7 (both windows). The game is a clone of battleship and is console input based. I have created my gui how I want it to look, and on the Qt side in Qt designer, my gui consists of a grid layout 10x10, using labels to hold pixmaps of game cells:
I have painstakingly named each label to represent its position in the 2d array (ie. fleet map => F_00 => F[0,0] => F[i],[j]). I can manually choose what pixmap I would like to display using the properties editor, but I would like something dynamic.
I use an update mapboard class to redraw the game board after a player fires, which keeps storing over a char array. I would like to update my pixmaps for each, using a generic getupdatearray type function. As we traverse the array it will update the pixmap currently associated with individual labels to match their cousins from the array. (say F[5][6] = 'X' for hit, then when the loops got to that position in the array it would update the grid of pixmaps at F_56 to equal hit.png, replacing the empty.png.
I have an idea how to make the loop that would accomplish this, but unsure how i would go about getting the pixmap for each label to be more along the lines of a runtime feature versus the now compile time (static) feature. I have read about QPainter and another Qt class that deals with images, but still having a hard go at it.
Question to any of you, how do I update these pixmaps based on a 2d array?
loop structure - i can figure out
condition statements - i can figure out
qt specific syntax dealing with labels- newbie so no idea atm.
Here's some pseudocode of the kind of thing I am trying to do with map.h:
#include <QtCore>
#include <QtGui>
// WARNING: PSEUDOCODE, DOES NOT COMPILE
// AT A LOSS ON HOW TO SELECT THE CORRECT LABEL
// MAYBE A CHILD CLASS FOR THAT?
class map {
public:
char updateboard(char mapname, char b[][10]){
for(int i=0;i<10;i++){
for(int j=0;j<10;j++){
char C = b[i][j];
if (C == 'M'){//miss
Qlabel mapname_[i][j](<img src='images/missspace.png'/>);
mapname_[i][j].show();
}
else if(C == 'X'){//hit
Qlabel mapname_[i][j](<img src='images/hitspace.png'/>);
mapname_[i][j].show();
}
else if(C == ' '){//undiscovered space
Qlabel mapname_[i][j](<img src='image/emptyspace.png'/>);
mapname_[i][j].show();
}
}
}
}
};
Then in my mainwindow.cpp, I include map.h and say:
// calls class function update board
// takes updated array values and replaces old pixmap with new
map.updateboard(T,b[][10]); // target map update
map.updateboard(F,v[][10]); // fleet map update
Thanks in Advance
UPDATE:
I've gotten to the point where I can swap pixmaps with buttons presses, but I would like to create something more dynamic. I wanted to use a Qstring in which I place the name of the label I want to change using by appending x y values using:
TR_position.append(QString::number(xvalue));
When I try to call it using:
ui->TR_position->setPixmap(QPixmap(":/images/missspace.png"));
...it obviously doesnt work. Is there a way to type case it, or use the contents of the string as the Qlabel name?
You manually entered and named 200 label widgets? Let no one call you lazy. :)
Per your update, you now know how to use QLabel::setPixmap(). What you think you need is getting a QLabel pointer from a name, which would be a combination of two things:
QWidget::findChild to get a QWidget* from a QString
qobject_cast to get a QLabel* from a QWidget
If you go down this path, what you'd wind up with is something like:
QWidget* cellWidget = ui->findChild(TR_position);
QLabel* cellLabel = qobject_cast<QLabel*>(cellWidget);
cellLabel->setPixmap(QPixmap(":/images/missspace.png"));
But BEWARE! There are many things wrong with this approach.
It's brittle: What if there doesn't happen to be any widget with that name (mysterious crash)? Or even worse, what if there are multiple widgets with that name and this code marches along blissfully ignorant of that odd condition that is likely a bug?
It's poor OOP: While there are some decent cases to use dynamic casting (or "downcasting"), it usually indicates a flaw in a design. You know that all QLabels are QWidgets, but not all QWidgets are QLabels...so that qobject_cast call might return NULL. It's just one more point of failure. Sometimes you can't avoid this, but really there is no reason your program needs to be structured in such a way.
It's terribly slow: Searching for a widget by its name is essentially a naive recursive search. If you've set aside a separate widget frame for each grid and only search that, Qt will have to do 100 string compares to find the last element (so 50 in the average case). Imagine clearing the grid with a loop...now you're talking about 100*50 string compares!
All these things are avoidable. Just as it's possible to use loops to set the images on the controls by name, it's possible to use loops to create the widgets in the first place. You basically would leave the area for the game board blank in the design tool, and then dynamically create the controls with code...attach them to the layout with code...and save pointers to them in 2D array. (You wouldn't access them by label name at that point, you'd index them just as you are indexing your board.)
You could create your own class derived from QLabel (such as a GameCell class) which contained the information for your board cell and methods related to it. Then you wouldn't need an array of label widgets in parallel to an array representing your board. You'd simply have one array of objects that took care of both aspects of the implementation.
UPDATE: Since you asked in the comments for specifics, here's a GameCell class:
class GameCell : public QLabel
{
Q_OBJECT
public:
enum State { Undiscovered, Hit, Miss };
GameCell (QWidget *parent = 0) : QLabel (parent),
currentState (Undiscovered)
{
syncBitmap();
}
State getState() const { return currentState; }
void setState(State newState) {
if (currentState != newState) {
currentState = newState;
syncBitmap();
}
}
private:
void syncBitmap() { // you'd use setPixmap instead of setText
switch (currentState) {
case Undiscovered: setText("U"); break;
case Hit: setText("H"); break;
case Miss: setText("M"); break;
}
}
State currentState;
};
This does double duty by behaving like a QWidget as well as maintaining a piece of internal state. Then a GameMap widget can use a QGridLayout of these GameCells:
class GameMap : public QWidget {
Q_OBJECT
public:
static const int Rows = 10;
static const int Columns = 10;
GameMap (QWidget* parent = 0) :
QWidget (parent)
{
layout = new QGridLayout (this);
for (int column = 0; column < Columns; column++) {
for (int row = 0; row < Rows; row++) {
GameCell* cell = new GameCell (this);
cells[column][row] = cell;
layout->addWidget(cell, row, column);
}
}
}
private:
GameCell* cells[Columns][Rows];
QGridLayout* layout;
};
If you wanted to, you could just leave spaces in your layout in the designer you wanted to fill in with the GameMap widget. Or you can push on and do the whole thing programmatically. For the sake of simplicity I'll just put two boards next to each other with a vertical separator on the surface of a dialog:
class Game : public QDialog
{
Q_OBJECT
public:
Game (QWidget *parent = 0)
: QDialog(parent)
{
targetMap = new GameMap (this);
fleetMap = new GameMap (this);
verticalSeparator = new QFrame (this);
verticalSeparator->setFrameShape(QFrame::VLine);
verticalSeparator->setFrameShadow(QFrame::Sunken);
layout = new QHBoxLayout (this);
layout->addWidget(targetMap);
layout->addWidget(verticalSeparator);
layout->addWidget(fleetMap);
setLayout(layout);
setWindowTitle(tr("Battleship"));
}
private:
GameMap* targetMap;
QFrame* verticalSeparator;
GameMap* fleetMap;
QHBoxLayout* layout;
};
I'm not going to write a whole game here or make it look fancy. That's just the gist, showing how to get 200 labels up in a programmatic fashion:
With my code, getting a GameCell from an (x,y) coordinate doesn't require an average of 50 string compares. Due to the formalized and predictable nature of 2D arrays, indexing into cells[x][y] only requires a single multiply operation and a single addition operation. There's no downcasting, and you can simply write:
cells[x][y].setState(GameCell::Miss);
ADDENDUM: Creating a QWidget for each grid cell isn't necessarily the way to go in the first place. Some might consider that "heavyweight". If your game were being played out on a large virtual space of tiles then it could be much too slow. You might find it useful to look into QGraphicsGridLayout, which could be a more appropriate approach in the long run:
http://doc.qt.io/qt-5/qtwidgets-graphicsview-basicgraphicslayouts-example.html
Using QWidgets won't be much of an issue with a 10x10 grid, however, so if you want to just stick with that then you can. If you're going to do it that way, then at least you shouldn't be placing them all by hand!

How to set filter option in QTableWidget

In my application I have QtableWidget displaying multiple rows , line edit to enter string and push button, Requirement says upon clicking on push button Same QTableWidget should show only those rows which are having string entered into line edit.
I thought of using QSortFilterProxy Model but QTableWidget is having setModel(...)method private so I am unable to use QSortFilterProxy Model in this case. Please let me know how to implement Filter option in QTable Widget
Using a sort/filter proxy is probably overkill for this anyway.
It's a matter of iterating through all of your QTableWidgetItem objects, determining if their text matches the filter and calling QTableView::setRowHidden() as needed.
For example:
QString filter = textEdit->text();
for( int i = 0; i < table->rowCount(); ++i )
{
bool match = false;
for( int j = 0; j < table->columnCount(); ++j )
{
QTableWidgetItem *item = table->item( i, j );
if( item->text().contains(filter) )
{
match = true;
break;
}
}
table->setRowHidden( i, !match );
}
I HIGHLY recommend going about this in the following way! This is how it is meant to be done in Qt.
Look at the tutorial on Qt Model/View Programming. The problem is that QTableWidget is a convenience class that hides the Model/View stuff for you. In your case, you can't (or shouldn't) ignore the Model/View structure Qt provides.
What you will need to do:
Use a QTableView instead of a QTableWidget.
Subclass QAbstractItemModel and implement data() (for reading), and all the other functions you need from the documentation. This is the trickiest part, but refer to the above link for a walkthrough of how to do this.
Create a QSortFilterProxyModel and setModel() of the QTableView to it.
setSourceModel() of your QSortFilterProxyModel to your subclassed model.
Set the string you want to filter on using setFilterFixedString() or setFilterRegExp() in your QSortFilterProxyModel
Let me know if this helps. This is far more professional, and in the long run, elegant, than iterating through all the elements in your table.

Using a delegate with a QDataWidgetMapper and QLabel

I'm trying to use a delegate to customize the way data from a model is displayed when using a QDataWidgetMapper.
I have two different versions of a widget, one is view-only (the data is displayed in QLabels) and the other is used to edit the data (the data is displayed in appropriate editors).
The latter one works perfectly with the delegate, everything is fine.
As you may have guessed the problem arises with the first one... When mapping the sections of my model to QLabels using the QDataWidgetMapper, the delegate is never called and the data is displayed correctly for the sections with regular data (strings, ints,...) but no data is displayed for the sections of my model with a custom data type (a kind of list) which I would like to format as a string using the delegate.
I've already performed this operation successfully when the same data is displayed in a QTableView (the method paint() of the delegate is called when the data is displayed).
After having looked at it a little bit closer, I've been able to see that, when using QLabels to display the data, the delegate is never called though I've explicitly associated a delegate to the QDataWidgetMapper using its method setItemDelegate().
So in synthesis, assume a class CustomItemDelegate which inherits QStyledItemDelegate with virtual methods:
void CustomItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
qDebug() << "DELEGATE: PAINT" << index.column();
QStyledItemDelegate::paint(painter, option, index);
}
void CustomItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const {
qDebug() << "DELEGATE: SET EDITOR DATA" << index.column();
QStyledItemDelegate::setEditorData(editor, index);
}
and a widget with following in it:
QDataWidgetMapper* mapper = new QDataWidgetMapper();
CustomItemDelegate* delegate = new CustomItemDelegate();
mapper->setModel(model);
mapper->setItemDelegate(delegate);
mapper->addMapping(editorWidget, 1);
mapper->addMapping(label, 2, "text");
mapper->toFirst();
QTableView* view = new QTableView();
CustomItemDelegate* delegate2 = new CustomItemDelegate();
view->setModel(model);
view->setItemDelegate(delegate2);
the code outputs:
DELEGATE: SET EDITOR DATA 1
// NOTHING ?!
DELEGATE: PAINT 1
DELEGATE: PAINT 2
and as a result I got
my editorWidget with the correct data in it (whatever data type the section contains: regular or custom, as long as the editor handles the type of course),
my label only displays the data if the section contains a regular type of the data as the delegate is not called
my view would display everything fine as the delegate is called for each section
So my questions are:
why isn't the delegate called when the mapped widget is a QLabel?
in this case, how come the data is even displayed when the data type is regular? Magic?
Thanks very much and I apologize in advance if the answer is obvious (but even then, thank you for pointing it out :P),
ixM
This is the code from QT that populates widgets
void QDataWidgetMapperPrivate::populate(WidgetMapper &m)
{
if (m.widget.isNull())
return;
m.currentIndex = indexAt(m.section);
if (m.property.isEmpty())
delegate->setEditorData(m.widget, m.currentIndex);
else
m.widget->setProperty(m.property, m.currentIndex.data(Qt::EditRole));
}
In the first case when you do not specify a property delegate is used whereas in the second case the data is set to widget directly by passing your delegate.
I don't know why it was designed this way but this is how it works currently !

Resources