Changing QAbstractTableModel headerData using the role - c++

I have a subclass
class TableModel : public QAbstractTableModel
I override the headerData method as follow:
QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {...}
if (role == TableModel::CurrencyRole && orientation == Qt::Horizontal) {...}
return QVariant();
}
I have a method that set a QTableView as follow using TableModel* table
void A::SetDisplay(QTableView* table_view, QString filter, int role, int sort_role)
{
proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(table);
proxyModel->setDynamicSortFilter(true);
proxyModel->setSortRole(sort_role);
table_view->setModel(proxyModel);
table_view->setSortingEnabled(true);
table_view->setSelectionBehavior(QAbstractItemView::SelectRows);
table_view->horizontalHeader()->setStretchLastSection(true);
table_view->verticalHeader()->hide();
table_view->setEditTriggers(QAbstractItemView::NoEditTriggers);
table_view->setSelectionMode(QAbstractItemView::SingleSelection);
proxyModel->setFilterRegExp(QRegExp(filter, Qt::CaseInsensitive));
proxyModel->setFilterKeyColumn(1);
proxyModel->sort(0, Qt::AscendingOrder);
connect( table_view->selectionModel(),
SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
this, SIGNAL(selectionChanged(QItemSelection)));
}
I have two QTableView objects ViewA and viewB. I need ViewA to have a header with role == Qt::DisplayRole and viewB to have a header with role == TableModel::CurrencyRole. How can I get the headerData to change for each view using the role.
Thanks, and please let me know if I left any detail out or or something is unclear in my question.

First of all, it looks like to do exactly what you're trying to to is going to be a bit tricky.
After a quick perusal of the Qt source code it looks like there's no way to change what role is passed to your model's headerData() function just using the API.
You do, however, have the ability to subclass QHeaderView and override the virtual paintSection() function and then do whatever you want. You'll probably need to look over Qt's implementation of this function for reference of how to implement it properly.
At this point you can set the header view on your views to your new custom one, and then set some internal flag from your view that tells it how to properly call headerData() with the role you want.

Related

Qt Model for non-table custom widget

I created a custom widget composed of QPushButtons placed on a broken layout since I need to place them on specific places with no geometric pattern. I've previously used QAbstractTableModel for QTableView with success and I would like to have the same ability of letting a model be in charge of the front-end updating. The buttons will mostly provide visual feedback changing text and color based on backend data changes. I included a link (end of post) that has something of use for me but I'm unable to see how I would achieve what you get by, for example, reemplementing Model::data() when role is Qt::DisplayRole. How would QModelIndex apply in something like this?
The first thing that comes to mind with a rough pseudocode:
class MyModel : QAbstractItemModel
{
QMap<int, BtnData> m_btnDataMap;
QVariant data(QModelIndex mIdx, int role) {
if (role == DisplayRole) {
return m_btnDataMap[mIdx.row()]; // returns a BtnData item
}
}
void updateButtonData(int btnKey, BtnData d) {
m_btnDataMap[btnKey] = d;
Q_EMIT dataChanged(btnKey);
}
};
class RandomlyPlacedButtonsWidget : public QWidget
{
QMap<int, QPushButton*> m_btnMap;
MyModel m_myModel;
RandomlyPlacedButtonsWidget() {
connect(m_myModel, dataChanged(int btnKey), this, updateButtonVisual(int btnKey));
}
void updateButtonVisual(int btnKey) {
BtnData data = m_myModel->data(index, DisplayRole);
m_btnMap[index]->text = data.text;
m_btnMap[index]->color = data.color;
}
};
Is this a proper way of using the dataChanged() signal on the widget side? Would something like this make sense to do? It would be ignoring columns, would that conflict with it? Would QAbstractListModel be better because of that?
I don't need specific code, I'm more in need of a way to properly approach this while keeping things clean and easy to maintain.
Using Qt Model/View with non-table like data and non-table/list UI?

Update sourceModel() without applying filtering on QSortFilterProxyModel

I have a custom model (QAbstractListModel) and a custom proxy model (QSortFilterProxyModel) working as a filter.
In the view, when I update the model, I need to execute "emit dataChanged(...)" in order to see this changes displayed.
But then, proxy model is automatically updated (filterAcceptsRow is called) and the view applies the corresponding filtering options.
I need to disable this behavior, to be able to update the view and only apply filtering when clicking a button, and not automatically.
For example:
My Model has two fields, id (int) and selected (bool)
In the view, I filter for only those elements that are selected (selected == true)
With this filter applied, I unselect one element.
With current behavior, this element disappears, since filter is applied when model changes. I want to display item with this change and apply filtering only when a button is clicked.
You could implement a public method in your custom proxy model which you use for enabling/disabling the filter. In my example code I'm using Q_INVOKABLE macro to make enableFilter() callable from QML too. So you can enable the filter when a button is clicked by calling the method.
class ExampleFilterProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit ExampleFilterProxyModel(QObject *parent = nullptr) : QSortFilterProxyModel(parent) {}
Q_INVOKABLE void setFilter(const QString & pattern) {
QRegularExpression re(pattern, QRegularExpression::CaseInsensitiveOption);
if (re.isValid()) {
QSortFilterProxyModel::setFilterRegularExpression(pattern);
}
invalidateFilter();
}
Q_INVOKABLE void enableFilter(bool enabled) {
m_enabled = enabled;
invalidateFilter();
}
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override {
if (m_enabled) {
QModelIndex index = sourceModel()->index(sourceRow, filterKeyColumn(), sourceParent);
QString str = sourceModel()->data(index, filterRole()).toString();
return str.contains(filterRegularExpression());
}
return true;
}
private:
bool m_enabled = false;
};
When you instantiate the model if you don't explicitly call enableFilter with true the filterAcceptsRow() method will return true for all rows.
myFilterProxyModel->setFilterRole(MyListModel::ExampleRole);
myFilterProxyModel->setFilter("^fullstringmatch$");
myFilterProxyModel->setSourceModel(myModel);
//myFilterProxyModel->enableFilter(true);
If you are using QML then you make the custom proxy model visible to the QML context e.g. by setting it as a context property (e.g. filterProxyModel) and enable the filter by calling
filterProxyModel.enableFilter(true)
Setting dynamic sort filter has worked for me. It was false by default in older versions of QT but I realized that it's true for current version!

QT - QTreeView with different colors for subgroups of the QTreeView items

Does anybody know how to implement/realize a QTreeView with different colors for subgroups of the QTreeView items?
Something like:
Does anybody has done something like that and could give me a link to an tutorial or how to, or sample code would also be good. Currently I have absolutely no idea how to build this.
I'm working with Qt 5.1.1 and using the QTreeView with the QFileSystemModel and the QItemSelectionModel.
I also thought of :
m_TreeView->setStyleSheet(...)
but this only sets the style for the whole treeView or only for the selected ones.
Any suggestions? Thanks a lot for your help!
There's Qt::BackgroundRole which can be used to return a QColor to be used by the view to paint an index' background.
As you use an existing item model class (QFileSystemModel), it would be easiest to put a proxy model on top of the file system model just doing the colorization.
Using QIdentityProxyModel:
class ColorizeProxyModel : public QIdentityProxyModel {
Q_OBJECT
public:
explicit ColorizeProxyModel(QObject *parent = 0) : QIdentityProxyModel(parent) {}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const {
if (role != Qt::BackgroundRole)
return QIdentityProxyModel::data(index, role);
... find out color for index
return color;
}
};
To use it:
QFileSystemModel *fsModel = new QFileSystemModel(this);
ColorizeProxyModel *colorProxy = new ColorizeProxyModel(this);
colorProxy->setSourceModel(fsModel);
treeView->setModel(colorProxy);
If you need anything more fancy, (like special shapes etc.), you'd need your own item delegate with custom painting (see QStyledItemDelegate).

Automatically refreshing a QTableView when data changed

I have written a custom data model to be displayed with several QTableViews.
Technically, everything works fine: my views show the changes made from my model. My data model is editable, and the setData() method does emit the dataChanged() signal and returns true on successful edition.
However, my problem is that I have to move my mouse over a QTableView for it to show the actual change, while I would like all views to show the changes when they were made, without needing to interact with the views in order for them to be updated.
Any idea? Thanks,
It may be relevant to mention that I do not use the default Qt::EditRole role to edit data, but rather a custom enum value (named ActiveRole).
Here is what I am seeking: my data model contains properties on how to display data, used to generate style sheets that are fed to the viewS.
Thus, when altering the model, for each view, all its items are impacted, which is why the dataChanged() signal is sent with indices covering all cells.
I also tried to emit layoutChanged(), but it does not seem to change the behavior in my case.
Here is an excerpt of the setData() method:
bool DataModel::setData(QModelIndex const& idx, QVariant const& value, int role)
{
if (ActiveRole == role)
{
// Update data...
QModelIndex topLeft = index(0, 0);
QModelIndex bottomRight = index(rowCount() - 1, columnCount() - 1);
emit dataChanged(topLeft, bottomRight);
emit layoutChanged();
return true;
}
return false;
}
Here is a sample of the data() method:
QVariant DataModel::data(QModelIndex const& idx, int role) const
{
if (ActiveRole == role)
{
boost::uuids::uuid id;
return qVariantFromValue(id);
}
return QVariant();
}
And flags() does indicate an editable model:
Qt::ItemFlags DataModel::flags(QModelIndex const& idx) const
{
if (false == idx.isValid())
{
return Qt::ItemIsEditable;
}
return QAbstractTableModel::flags(idx) | Qt::ItemIsEditable;
}
I have a custom delegate, which relies heavily on this SO thread for overriding the paint and the sizeHint methods in order to draw a QTextDocument. Also, it provides the content of the ActiveRole to the editor in setEditorData, and calls DataMode::setData in setModelData:
void DataModelDelegate::setEditorData(QWidget* editor, QModelIndex const& idx) const
{
auto active = qVariantValue<boost::uuids::uuid>(idx.data(ActiveRole));
static_cast<DataModelEditor*>(editor)->setActive(active);
}
void DataModelDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, QModelIndex const& idx) const
{
auto active = static_cast<DataModelEditor*>(editor)->getActive();
model->setData(idx, qVariantFromValue(active), ActiveRole);
}
In the createEditor(), I plug a signal from the editor to a slot of my delegate for committing data:
QWidget* DataModelDelegate::createEditor(QWidget* parent, QStyleOptionViewItem const& option, QModelIndex const& idx) const
{
auto editor = new DataModelEditor(parent);
connect(editor, SIGNAL(activeItem()), this, SLOT(commitEditorData()));
return editor;
}
When clicking on an item, the editor triggers the activeItem signal; the connected slot commitEditorData in turn raises the commitData signal with the editor in argument.
So all my views use these custom delegate, editor, and data model. The view that I am interacting with does show the change immediately, but the other views need to have the mouse hovering over them to show changes as well.
I actually found the problem, which was that my other view was not properly notified of the data changes: my views each showed different portions of my data, so the other views needed to be notified of the dataChanged(), but for their own, proper, indices.
On a side note, I also had the problem of updating my views while my Qt application was not the active window in my window manager. The solution was to call repaint() on the main window.
I have met the same problem, and let me add a detailed explanation to the piwi's answers. If you change the data,and what to update the single or several columns(or rows,depending on your requirement), you should emit a set of index for topleft to bottomright.For example,if you have a table like below:
and, now you have changed some data, and want to update the cell row 1, column 1-2, then you should emit signal dataChange
emit datachange(index(1,1),index(1,2));
Are you calling the setData()? Is the dataChanged() signal really emitted? Connect some debug logging slot to it. I dare to speculate that this is very similar problem to yours:
http://www.qtcentre.org/threads/18388-Refreshing-a-QTableView-when-QAbstractTableModel-changes?s=fd88b7c4e59f4487a5457db551f3df2c

QTableView has unwanted checkboxes in every cell

I'm just getting started with Qt programming, and I'm trying to make a simple tabular data layout using a QTableView control with a model class of my own creation inheriting from QAbstractTableModel. For some reason, my table view ends up looking like this:
(source: nerdland.net)
What in the heck are those things that look like checkboxes (but don't do anything when I click them) in every cell, and how do I make them go away? I haven't changed any of the QTableView properties except for the object's name.
If it matters, my model code is dead simple:
MyTableModel::MyTableModel(QObject* parent)
: QAbstractTableModel(parent)
{
}
MyTableModel::~MyTableModel()
{
}
int MyTableModel::rowCount(const QModelIndex& parent) const
{
return 1000;
}
int MyTableModel::columnCount(const QModelIndex& parent) const
{
return 5;
}
QVariant MyTableModel::data(const QModelIndex& index, int role) const
{
return "Foo";
}
The dialog UI is built in Qt Designer, and inside the class for the dialog I attach the model to the view like this:
MyTableModel testModel = new MyTableModel(this);
ui.testTable->setModel(testModel);
Other than that I perform no operations on ui.testTable.
Using Qt 4.6.
Try changing MyTableModel::data() to the following:
QVariant MyTableModel::data(const QModelIndex& index, int role) const
{
if (role == Qt::DisplayRole)
return "foo";
else
return QVariant();
}
Probably the returned QVariant for role Qt::CheckStateRole was misunderstood by the QTableView.
Do you by any chance happen to set the Qt::ItemIsUserCheckable flag in flags()?