QTableView with icons in rows - c++

I have a QTableView showing rows of a database table. In this table I have a column called data type and I have icon images for each type. How can I add these icons in front of each data type?
Here's a part of my code as requested by justanothercoder.
QString msgQueryString = "select MESSAGE_ID, DATA_TYPE from SER_MESSAGES where MESSAGE_ID > 500 ";
serendibMsgTableModel->setQuery(msgQueryString, *database);
serendibMsgTableModel->setHeaderData(0, Qt::Horizontal, tr("Message ID"));
serendibMsgTableModel->setHeaderData(1, Qt::Horizontal, tr("Data Type"));
serendibMsgProxyModel->setSourceModel(serendibMsgTableModel);
serendibMsgView->setModel(serendibMsgProxyModel);
"serendibMsgTableModel" is a QSqlQueryModel and "serendibMsgProxyModel" is a customized QSortFilterProxyModel. "serendibMsgView" is the QTableView I need the icons to be displayed, in the Data Type column.
Hope this helps for your answer.

I saw that you've already picked an answer but since you are learning Qt I'll add a few things.
Taking a look at the excellent Qt documentation I suggest you overwrite this in your model:
QVariant QSqlTableModel::data (
const QModelIndex & index,
int role = Qt::DisplayRole ) const   [virtual]
There are various roles (int role = Qt::DisplayRole):
enum Qt::ItemDataRole :
Each item in the model has a set of
data elements associated with it, each
with its own role. The roles are used
by the view to indicate to the model
which type of data it needs. Custom
models should return data in these
types.
Qt::DecorationRole : The data to be
rendered as a decoration in the form
of an icon. (QColor, QIcon or Qpixmap)
Thus, what you need to do is return a QIcon or QPixmap in the data() function for the DisplayRole.
Another approach which might be more appropriate is to make use of delegates: For example ColorListEditor

Set the DecorationRole of your items to the QPixmap you want and it should work.
edit:
I guess that the icon depends on the value in the data type column.
int rowCount = serendibMsgTableModel->rowCount();
for(int row = 0; row < rowCount; row++)
{
QModelIndex index = serendibMsgTableModel->index(row, 1);
QVariant value = serendibMsgTableModel->data(index);
static QPixmap s_invalidIcon(PATH_TO_INVALID_ICON);
static QPixmap s_type1Icon(PATH_TO_TYPE1_ICON);
static QPixmap s_type2Icon(PATH_TO_TYPE2_ICON);
QPixmap icon(s_invalidIcon);
if(value.toString() == "type1")
{
icon = s_type1Icon;
}
else if(value.toString() == "type2")
{
icon = s_type2Icon;
}
serendibMsgTableModel->setData(index, icon, Qt::DecorationRole);
}
Something like this should work.
Set the values before setModel.
I haven't tested it, but I think you should get the idea from this.

Related

Why can't I update the QTableView model inside a thread automatically?

Introduction:
I'm using QT5 and I want to update a TableView where my model for the TableView is updated inside a thread.
*Method
I create a table like this and set it's columns and rows.
/* TableView */
QStandardItemModel* looping_terminal_model = new QStandardItemModel(4, 2);
looping_terminal_model->setHeaderData(0, Qt::Horizontal, QObject::tr("ID"));
looping_terminal_model->setHeaderData(1, Qt::Horizontal, QObject::tr("DATA"));
ui->looping_terminal_tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui->looping_terminal_tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
ui->looping_terminal_tableView->setModel(looping_terminal_model);
Then I starting the thread.
/* Threads */
this->loop_terminal = new Loop_terminal(j1939, &start_loop_terminal_thread, looping_terminal_model);
loop_terminal->start();
Inside the loop_terminal class, it looks like this
Loop_terminal::Loop_terminal(J1939* j1939, bool* start_loop_terminal_thread, QStandardItemModel* looping_terminal_model){
this->j1939 = j1939;
this->start_loop_terminal_thread = start_loop_terminal_thread;
this->looping_terminal_model = looping_terminal_model;
i = 0;
}
void Loop_terminal::run(){
while(1){
if(j1939->ID_and_data_is_updated){
j1939->ID_and_data_is_updated = false;
}
QModelIndex index = looping_terminal_model->index(1,1,QModelIndex());
// 0 for all data
looping_terminal_model->setData(index,this->i);
i++;
msleep(1000);
}
}
Result
The value can be shown in the blue cell. But it only updates if I click on the blue cell.
Discussion:
Here is the problem
looping_terminal_model->setData(index,this->i);
Every time I updated an index, I need to press on the blue TableView cell, so it can updates. Or else, nothing happens. If I minimize the window, then it updates, but I can't see that.
What should I do, to make sure that the ui can update, even if it's outside of the thread?
No, you cannot and should not. Models are intensively used by the view so they must live (and be modified) in the same thread as the view as they are not thread-safe. The solution is to send the information to the main thread using signals and there just update the model.

Qt5 : Get value of item clicked in a listview

I'm making a Qt5.7 application where I am populating a QListView after reading stuff from a file. Here's the exact code of it.
QStringListModel *model;
model = new QStringListModel(this);
model->setStringList(stringList); //stringList has a list of strings
ui->listView->setModel(model);
ui->listView->setEditTriggers(QAbstractItemView::NoEditTriggers); //To disable editing
Now this displays the list just fine in a QListView that I have set up. What I need to do now is to get the string that has been double clicked and use that value elsewhere. How do I achieve that?
What I tried doing was to attach a listener to the QListView this way
... // the rest of the code
connect(ui->listView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(fetch()));
...
And then I have the function fetch
void Window::fetch () {
qDebug() << "Something was clicked!";
QObject *s = sender();
qDebug() << s->objectName();
}
However the objectName() function returns "listView" and not the listView item or the index.
The signal already provides you with a QModelIndex which was clicked.
So you should change your slot to this:
void Window::fetch (QModelIndex index)
{
....
QModelIndex has now a column and a row property. Because a list has no columns you are interessted in the row. This is the index of the item clicked.
//get model and cast to QStringListModel
QStringListModel* listModel= qobject_cast<QStringListModel*>(ui->listView->model());
//get value at row()
QString value = listModel->stringList().at(index.row());
You should add the index as parameter of your slot. You can use that index to access the list
Your code should be some thing like this.
void Window::fetch (QModelIndex index) {
/* Do some thing you want to do*/
}

How to get the status of hardcoded QCheckBoxes in QSqlQueryModel?

I have a QTableView that shows QSqlQueryModel. The model contains checkboxes that are created in each row in the first column (which contain the ref_no; the primary-key in my db) as follow:
void MainWindow::showM(model){
ui->tableView->setModel(model);
ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
for( int i = 0; p<model->rowCount(); i++)
{
QCheckBox *checkBox = new QCheckBox();
ui->tableView->setIndexWidget(model->index(i,0),checkBox);
}
ui->tableView->show();
}
... and it's working fine, displaying all the information I need plus the checkboxes.
Now, I need to get the ref_no where the adjacent checkbox is checked.
How to do that ?
Use QSignalMapper (or an ad hoc solution involving a mapper using sender(), or lambdas). For instance define a member for the mapping:
QHash<QCheckBox *, int> m_mapping;
Then in your code hook it up like this:
QCheckBox *checkBox = new QCheckBox();
ui->tableView->setIndexWidget(model->index(i,0),checkBox);
m_mapping[checkBox] = i;
connect(checkBox, &QCheckBox::toggled, this, &MainWindow::onCheckBoxToggled);
Then define a slot like this:
// for the love of kittens use proper names for methods
void MyWindow::onCheckBoxToggled(bool toggled) {
QCheckBox *box = static_cast<QCheckBox *>(sender());
const int id = m_mapping.value(box);
// do something
}
Or, if you fancy lambdas, you can do the above with a capture:
connect(checkBox, &QCheckBox::toggled,
[i](bool toggled){ /* use i, toggled */ });
All of that having being said, I would strongly recommend against the idea of creating QCheckBoxes and using setIndexWidget. Instead, employ a proxy model that enriches your column by returning the Qt::ItemIsUserCheckable flag and handles reads and writes for the Qt::CheckStateRole.

QSortFilterProxyModel and lazily populated treeviews

I have implemented a lazily populated treeview by subclassing QAbstractItemModel. The implementation looks something like:
https://gist.github.com/gnufied/db9c4d805e2bb24d8c23
(I am not pasting code inline, so as to mess with messaging)
It is basically a tree representation of hierarchical data stored in table. Now, I want users to be able to sort the rows based on columns. Where columns are, "count" or "reference count". These values are basically integers.
The implementation on its own works, until I throw in QSortFilterProxyModel and I start to get lots of empty rows in the view. The hard problem is, this tends to happen only when I have lots of rows (like thousands or so).
The code for implementing sorting proxy is:
rootItem = RBKit::SqlConnectionPool::getInstance()->rootOfSnapshot(snapShotVersion);
model = new RBKit::HeapDataModel(rootItem, this);
proxyModel = new SortObjectProxyModel(this);
proxyModel->setSourceModel(model);
ui->treeView->setModel(proxyModel);
ui->treeView->setSortingEnabled(true);
I have subclassed QSortFilterProxyModel class and subclass implementation is really simple:
https://gist.github.com/gnufied/115f1a4fae3538534511
The documentation does say -
"This simple proxying mechanism may need to be overridden for source models with more complex behavior; for example, if the source model provides a custom hasChildren() implementation, you should also provide one in the proxy model."
But beyond that, I am not sure - what I am missing.
So, I think I have found the solution and the fix appears to be reimplementing fetchMore in proxy model subclass, because inserted rows reported by source model, do not match place where rows actually exist in view (Rows in view are owned by proxy model), so this seems to have fixed it for me:
#include "sortobjectproxymodel.h"
SortObjectProxyModel::SortObjectProxyModel(QObject *parent) :
QSortFilterProxyModel(parent)
{
}
bool SortObjectProxyModel::hasChildren(const QModelIndex &parent) const
{
const QModelIndex sourceIndex = mapToSource(parent);
return sourceModel()->hasChildren(sourceIndex);
}
int SortObjectProxyModel::rowCount(const QModelIndex &parent) const
{
const QModelIndex sourceIndex = mapToSource(parent);
return sourceModel()->rowCount(sourceIndex);
}
bool SortObjectProxyModel::canFetchMore(const QModelIndex &parent) const
{
if(!parent.isValid())
return true;
else {
const QModelIndex sourceIndex = mapToSource(parent);
return sourceModel()->canFetchMore(sourceIndex);
}
}
void SortObjectProxyModel::fetchMore(const QModelIndex &parent)
{
if (parent.isValid() && parent.column() == 0) {
int row = parent.row();
int startRow = row + 1 ;
const QModelIndex sourceIndex = mapToSource(parent);
RBKit::HeapItem *item = static_cast<RBKit::HeapItem *>(sourceIndex.internalPointer());
if (!item->childrenFetched) {
qDebug() << "Insert New Rows at :" << startRow << " ending at : " << startRow + item->childrenCount();
beginInsertRows(parent, startRow, startRow + item->childrenCount());
item->fetchChildren();
endInsertRows();
}
}
}
Thank you for responding. At this point I really don't care about resorting rows that were lazy loaded (when a node was expanded), so I went ahead and disabled sortingEnabled and disabled dynamicSortFiltertoo.
The new code looks like:
rootItem = RBKit::SqlConnectionPool::getInstance()->rootOfSnapshot(snapShotVersion);
model = new RBKit::HeapDataModel(rootItem, this);
proxyModel = new SortObjectProxyModel(this);
proxyModel->setSourceModel(model);
proxyModel->sort(2, Qt::DescendingOrder);
proxyModel->setDynamicSortFilter(false);
ui->treeView->setModel(proxyModel);
That still leaves with empty rows though.
In my oppinion You don't need to subclass QSortFilterProxyModel for sorting on the top layer. If sortingEnabled == true for your view then the view will perform sorting on the proxy model, which is not desirable as the model should sort itself. What you need is to to call proxyModel->sort(desiredColumn) and that will display your model sorted in the view without altering your data. By default the QSortFilterProxyModel has its dynamicSortFilter property on, which will cause the proxy model to automatically re-sort when data changes or row is inserted or removed. I didn't see emitting dataChanged signal anywhere in your HeapDataModel, so maybe that could be a hint for you to get dynamically sorted rows. If you need to sort subitems then it goes little more complicated and then maybe you'll need to subclasss QSortFilterProxyModel. These model-view abstractions are hard to learn but once you get it you can do miracles rapidly.
rootItem = RBKit::SqlConnectionPool::getInstance()->rootOfSnapshot(snapShotVersion);
model = new RBKit::HeapDataModel(rootItem, this);
proxyModel = new SortObjectProxyModel(this);
proxyModel->setSourceModel(model);
proxyModel->sort(column);
ui->treeView->setModel(proxyModel);

How do I add a ComboBox to a TreeView column?

In Gtkmm, I want to have a Gtk TreeView with a ListStore, and have one of the columns in the list be a ComboBoxText. But I can't seem to figure out how to do it.
What I currently have looks like:
class PlayerListColumns : public Gtk::TreeModelColumnRecord
{
public:
PlayerListColumns()
{ add(name); add(team);}
TreeModelColumn<string> name;
TreeModelColumn<ComboBoxText*> team;
}
Then when setting the TreeView (the player_list_view object)
PlayerListColumns *columns = new PlayerListColumns();
Glib::RefPtr<ListStore> refListStore = ListStore::create(*columns);
player_list_view->set_model(refListStore);
ComboBoxText *box = manage(new ComboBoxText());
box->append("Blah");
box->append("Blah");
box->append("Blah");
TreeModel::Row row = *(refListStore->append());
row[columns->name] = "My Name";
row[columns->team] = box;
The column "name" shows up just fine, but no ComboBox. I'm almost positive that simply having a pointer to a combo box as the column type is wrong, but i don't know how it's supposed to go. I do get GTK warning:
GLib-GObject-WARNING **: unable to set property text' of typegchararray' from value of type `GtkComboBoxText'
Which seems to indicate (from a small bit of Googling) that there isn't a default renderer for non-basic types. But I haven't been able to find any examples of how to set one up, if that were the problem. All the tutorials only show TreeViews with primitive data types.
Anyone know how to put a ComboBox into a TreeView?
Okay, I haven't gotten it working 100%, but this example class should get you on the right track:
http://svn.gnome.org/svn/gtkmm-documentation/trunk/examples/book/treeview/combo_renderer/
Basically you need to add a Gtk::TreeModelColumn<Glib::RefPtr<Gtk::ListStore> > to your columns class and a Gtk::TreeModelColumn<string> to hold the selected data.
Then, to make a column a combobox, you have to add:
//manually created column for the tree view
Gtk::TreeViewColumn* pCol = Gtk::manage(new Gtk::TreeViewColumn("Choose"));
//the combobox cell renderer
Gtk::CellRendererCombo* comboCell = Gtk::manage(new Gtk::CellRendererCombo);
//pack the cell renderer into the column
pCol->pack_start(*comboCell);
//append the column to the tree view
treeView->append_column(*pCol);
//this sets the properties of the combobox and cell
//my gtkmm seems to be set for Glibmm properties
#ifdef GLIBMM_PROPERTIES_ENABLED
pCol->add_attribute(comboCell->property_text(), columns->team);
//this is needed because you can't use the ComboBoxText shortcut
// you have to create a liststore and fill it with your strings separately
// from your main model
pCol->add_attribute(comboCell->property_model(), columns->teams);
comboCell->property_text_column() = 0;
comboCell->property_editable() = true;
#else
pCol->add_attribute(*comboCell, "text", columns->team);
pCol->add_attribute(*comboCell, "model", columns->teams);
comboCell->set_property(text_column:, 0);
comboCell->set_property("editable", true);
#endif
//connect a signal so you can set the selected option back into the model
//you can just have a column that is not added to the view if you want
comboCell->signal_edited()
.connect(sigc::mem_fun(*this,&ComboWindow::on_combo_choice_changed));
EDIT ABOVE
I think something along the lines of using a Gtk::CellRendererCombo* is the way to go in your PlayerListColumns
http://developer.gnome.org/gtkmm/stable/classGtk_1_1CellRendererCombo.html
(I haven't made a working test yet, but I got the idea from:
http://developer.gnome.org/gtkmm-tutorial/unstable/sec-treeview.html.en#treeview-cellrenderer-details)