In the example, I would like to filter the data contained in rsModel by making a selection in sectionComboBox. In sectionComboBox, I display the NAME of a section, and would like to filter rsModel to entries with a matching SECTION_ID
ComboBox {
id: sectionComboBox
model: sectionModel
textRole: "NAME"
onCurrentTextChanged: rsModel.setQLFilter("SECTION_ID=" + ??)
}
QLTableModel subclasses QSqlRelationalTableModel and implements setQLFilter
void QLSqlTableModel::setQLFilter(const QString filter){
setFilter(filter);
}
Here's code that seems to be working. Please suggest any improvements you might see.
QLSqlTableModel.cpp (derived from QSqlRelationTableModel)
void QLSqlTableModel::setQLFilter(const QString & field, int value){
QString tempString = field + QString::number(value);
setFilter(tempString);
}
QVariant QLSqlTableModel::data(int row, int role) const {
QModelIndex qmi;
qmi = index(row, role, qmi);
return data(qmi, role);
}
main.qml
ComboBox {
id: sectionComboBox
model: sectionModel
textRole: "NAME"
onCurrentIndexChanged: {
rsModel.setQLFilter("SECTION_ID=", sectionModel.data(currentIndex, 0))
}
}
Related
I have created a mixin-like proxy model (Qt5) which just adds an extra first column to another proxy model, for adding a QToolBar of actions to each row of the table view (for example, a "delete" button). The model just provides a way of populating a QList<QVariant> for the first column. The delegate must know what is the meaning of each QVariant (usually ints/enums identifying actions), and populate the QToolBar accordingly. As last feature, if there's no actions, no extra column is added (it behaves like a QIdentityProxyModel in that case). Once added, actions cannot be removed. That's a feature for another day.
The problem of today is that, when I insert actions (which I do before setting the model to the view), the cells are all blanks. So, I'm doing something wrong with the signals or who knows with what (I think the mistake is in the add_action function, at the end of the snippet):
template<class proxy_model>
class action_model : public proxy_model
{
QList<QVariant> l_actions;
public:
using base_t = proxy_model;
using base_t::base_t; // Inheriting constructors.
QModelIndex mapFromSource(const QModelIndex& source_idx) const override
{
if (!l_actions.empty() and source_idx.isValid())
return this->createIndex(source_idx.row(),
source_idx.column() + 1);
else // identity proxy case
return base_t::mapFromSource(source_idx);
} // same for mapToSource but with - 1 instead of + 1.
int columnCount(const QModelIndex& parent = QModelIndex()) const override
{ return this->base_t::columnCount() + !l_actions.empty(); }
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
{
if (!l_actions.empty()) {
if (orientation == Qt::Horizontal and section == 0
and role == Qt::DisplayRole)
return "Actions"; // Testing.
else
return base_t::headerData(section - 1, orientation, role);
} else // identity proxy case
return base_t::headerData(section, orientation, role);
}
QVariant data(const QModelIndex& idx, int role) const override
{
if (!l_actions.empty()) {
if (idx.column() == 0 and role = Qt::DisplayRole)
return l_actions; // All the actions for drawing.
else
return QVariant();
} else // identity proxy case
return base_t::data(idx, role);
}
Qt::ItemFlags flags(QModelIndex const& idx) const
{
if (!l_actions.empty() and idx.column() == 0)
return Qt::NoItemFlags; // No editable or selectable
else
return base_t::flags(idx);
}
// And here, I think, is where the fun starts:
// The action could be added before or after the sourceModel
// is set or this model is connected to a view, but I don't
// how that cases are supposed to be managed.
void add_action(QVariant const& action)
{
bool was_empty = l_actions.empty();
l_actions << action;
if (was_empty and !this->insertColumns(0, 1))
throw std::logic_error("Something went wrong");
Q_EMIT this->dataChanged
(this->createIndex(0, 0),
this->createIndex(this->rowCount(), 0),
{ Qt::DisplayRole });
}
};
Without setting actions, the model works fine, both with QAbstractIdentityProxyModel and QSortFilterProxyModel as proxy_model. But, when setting actions, the view shows every cell blank, both with QSortFilterProxyModel and QAbstractIdentityProxyModel.
Here is a user-land code:
enum sql_action { DELETE };
auto* table_model = /* My QSqlTableModel */;
auto* view_model = new action_model<QIdentityProxyModel>(my_parent);
auto* table_view = new QTableView;
view_model->add_action(static_cast<int>(sql_action::DELETE));
view_model->setSourceModel(table_model);
table_view->setModel(view_model);
table_view->setSortingEnabled(true);
table_view->setAlternatingRowColors(true);
// The last column is printed in white, not with alternate colors.
table_view->show();
table_model->select();
The delegates are not a problem because I have set no one. I expect a first column with white cells, but I get an entirely white table. The column names are shown fine, except the last one, which prints just 0 as column name.
What am I doing wrong?
The problem is in your data() method.
You do not compare role to Qt::DisplayRole you assign to role
If you have actions, you either return the action entry or QVariant(), never any data
I want to select multiple items from a ListView. In C++ I would have done something like this
if (clicked_card->is_selected) {
clicked_card->is_selected = false;
int i = 0;
while(selected_cards[i] != clicked_card) i++;
selected_cards.erase(selected_cards.begin() + i);
} else {
clicked_card->is_selected = true;
selected_cards.push_back(clicked_card);
}
The above code uses pointer for comparison. So how to so such selection in QML. The solution I've come up with is something like this
Card.qml
Image {
id: delegate
property bool isSelected: false
...
MouseArea {
onClicked: {
if(isSelected === true) {
isSelected = false;
gameScene.deselectCard(selectSeq);
}
else {
isSelected = true;
gameScene.selectCard({'opParam': opParam, 'selectSeq': selectSeq});
}
}
}
}
GameScene.qml
Item {
id: gameScene
property var selectedCards: []
signal selectCard(variant userData)
onSelectCard: {
gameScene.selectedCards.push(userData)
}
signal deselectCard(variant userData)
onDeselectCard: {
for (var i = 0; i < gameScene.selectedCards.length; i += 1) {
if (gameScene.selectedCards[i].selectSeq == userData) {
gameScene.selectedCards.splice(i, 1);
break;
}
}
}
}
The problem with the above code is that I'm storing property isSelected in a delegate which is created and destroyed by the system. So this is giving me false solution. Is there any better way of multiple selection or any improvements in the solution ? I'm using model from C++ by subclassing QAbstractListModel.
I found the answer in Qt documentation. I simply have to use [DelegateModel][1]. It has a group property, for every group defined in a DelegateModel two attached properties are added to each delegate item. The first of the form DelegateModel.in*GroupName* holds whether the item belongs to the group and the second DelegateModel.*groupName*Index holds the index of the item in that group.
import QtQuick 2.0
import QtQml.Models 2.2
Rectangle {
width: 200; height: 100
DelegateModel {
id: visualModel
model: ListModel {
ListElement { name: "Apple" }
ListElement { name: "Orange" }
}
groups: [
DelegateModelGroup { name: "selected" }
]
delegate: Rectangle {
id: item
height: 25
width: 200
Text {
text: {
var text = "Name: " + name
if (item.DelegateModel.inSelected)
text += " (" + item.DelegateModel.selectedIndex + ")"
return text;
}
}
MouseArea {
anchors.fill: parent
onClicked: item.DelegateModel.inSelected = !item.DelegateModel.inSelected
}
}
}
ListView {
anchors.fill: parent
model: visualModel
}
}
Other solution would have been to move the property isSelected to C++ data model and use a getter and setter function to update the changes.
An easy solution. Use QPair or QPair to store the state of all of your item.
typedef QPair<int, bool> ItemState;
Enable multiple selection in your list o table:
ui->tableView->setSelectionMode(QAbstractItemView::MultiSelection);
And when you want to select a collection, just try something like this:
QList<ItemState> collection;
foreach (ItemState& el , collection) {
const int row = el.first;
const bool state = el.second;
const QModelIndex& index = ui->tableView->model()->index(row, 0);
ui->tableView->selectionModel()->select(index, state ? QItemSelectionModel::Select : QItemSelectionModel::Deselect );
}
You should update your collection data, everytime you modify the data in the model (add, remove o move elements). When the user clicks in a card, just handle the clicked event and modify your collection item state, and recall the loop.
I am working on a program to view and edit records in a file. It features a QTableView that displays all records, a QLineEdit to search for records, and some labels that display the details of the selected record:
There is a QAbstractTableModel class that holds the data and a QSortFilterProxyModel class that helps filtering the rows in the QTableView.
Searching and filtering works fine. Typing text in the search box immediately filters the list of records. But there are two things I cannot get to work:
if the list is not empty, I want one item to be selected/current always
the selected/current item must be brought into view when typing in the search box
For example, when I type "tesla", the list will be empty since no item matches. But as soon as I backspace to "te", the "Forester" matches and I want it to be selected. Second example: when (after starting the program) I type "f" the list is narrowed down to 8 items and "Pacifica" is selected. When I erase the "f", the Pacifica is still selected but no longer in the visible part of the list.
I have posted the full source code of this on Pastie, and here are some (hopefully) relevant snippets.
void MainWindow::on_lineEditSearch_textChanged(const QString & text)
{
itemProxy->setFilterFixedString(text);
updateStatusBar();
}
void MainWindow::currentRowChangedSlot(QModelIndex const & current, QModelIndex const & /*previous*/)
{
Car * car = 0;
if (current.isValid())
{
QModelIndex sibling = current.sibling(current.row(), COLUMN_THIS);
QVariant variant = itemProxy->data(sibling);
car = static_cast<Car *> (variant.value<void *> ());
}
updateCarMake(car);
updateCarModel(car);
}
MainWindow::MainWindow(QWidget * parent, CarItemModel * itemModel, CarSortFilterProxyModel * itemProxy) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
this->itemModel = itemModel;
this->itemProxy = itemProxy;
ui->setupUi(this);
setupStatusBar();
ui->tableView->setModel(itemProxy);
ui->tableView->setColumnHidden(COLUMN_THIS, true);
QItemSelectionModel * selectionModel = ui->tableView->selectionModel();
connect(selectionModel, SIGNAL(currentRowChanged(QModelIndex const &, QModelIndex const &)),
this, SLOT(currentRowChangedSlot(QModelIndex const &, QModelIndex const &)));
connect(selectionModel, SIGNAL(selectionChanged(QItemSelection const &, QItemSelection const &)),
this, SLOT(selectionChangedSlot(QItemSelection const &, QItemSelection const &)));
ui->tableView->selectRow(0);
ui->lineEditSearch->setFocus();
updateStatusBar();
}
<widget class="QTableView" name="tableView">
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOn</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
So, my question is: how can I make sure an item is always selected and in the visible part of the list (unless the user is scrolling, of course)?
Here's what I did to
always have an item selected, and
have the selected item visible when searching
The solution is even a bit fancier since when going from no visible items to some visible items the most recently selected item will be selected instead of just the first item.
First, I added a QModelIndex lastModelIndex; private member to the MainWindow class, and set it in the SelectionChanged slot. Note that the model index is stored and not the proxy index.
void MainWindow::selectionChangedSlot(QItemSelection const & selected, QItemSelection const & /*deselected*/)
{
if (selected.count() > 0)
{
QModelIndex index = selected.indexes().first();
QModelIndex modelIndex = itemProxy->mapToSource(index);
lastModelIndex = modelIndex;
}
}
Next, I added two methods: ensureSelected() ...
void MainWindow::ensureSelected(QItemSelectionModel * selectionModel, int const proxyCount)
{
if (selectionModel->hasSelection())
{
// an item is currently selected - don't have to do anything
}
else if (proxyCount == 1)
{
// no item is currently selected, but there is exactly one item in the list - select it
QModelIndex proxyIndex = itemProxy->index(0, 0);
selectionModel->setCurrentIndex(proxyIndex, QItemSelectionModel::Select | QItemSelectionModel::Rows);
}
else if (proxyCount > 1)
{
// no item is currently selected, but there are several items in the list
QModelIndex proxyIndex; // !isValid
if (lastModelIndex.isValid())
{
// there's a most recently selected item - compute its index in the list
proxyIndex = itemProxy->mapFromSource(lastModelIndex);
}
if (proxyIndex.isValid())
{
// the most recently selected item is in the list - select it
proxyIndex = proxyIndex.sibling(proxyIndex.row(), COLUMN_THIS);
}
else
{
// there's no most recently selected item or it is no longer in the list - select the first item
proxyIndex = itemProxy->index(0, 0);
}
selectionModel->setCurrentIndex(proxyIndex, QItemSelectionModel::Select | QItemSelectionModel::Rows);
}
else
{
// There are no items in the list - cannot select anything.
}
}
... and ensureVisible():
void MainWindow::ensureVisible(QItemSelectionModel * selectionModel)
{
if (selectionModel->hasSelection())
{
const QModelIndex index = ui->tableView->currentIndex();
ui->tableView->scrollTo(index);
ui->tableView->selectRow(index.row());
ui->tableView->scrollTo(index);
}
}
Weird as it may seem, I have to call scrollTo() twice, or the tableView won't scroll.
These new methods are called from on_lineEditSearch_textChanged() like so:
void MainWindow::on_lineEditSearch_textChanged(const QString & text)
{
itemProxy->setFilterFixedString(text);
QItemSelectionModel * selectionModel = ui->tableView->selectionModel();
int modelCount = itemModel->rowCount(); // number of items in the model
int proxyCount = itemProxy->rowCount(); // number of items in tableview
ensureSelected(selectionModel, proxyCount);
ensureVisible(selectionModel);
updateStatusBar();
}
Please find the updated and complete source code on Pastie.
Handle selection change signal and connect it to your custom slot:
QSortFilterProxyModel *model = new QSortFilterProxyModel(this);
ui->tableView->setModel(model);
connect(ui->tableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(selectionChanged(QItemSelection,QItemSelection)));
Every time, you table change the current selection the app should select the first item in the model:
void MainWindow::selectionChanged(QItemSelection selected, QItemSelection deselected) {
// Use this
const int rows = ui->tableView->selectionModel()->selectedRows().size();
// or
const int rowCount = selected.size();
// Lets select the first item if there are some items availables
if (rowCount < 1) {
const int availableItems = ui->tableView->model()->rowCount();
if (availableItems > 0) {
const QModelIndex index = ui->tableView->model()->index(0,0);
ui->tableView->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect);
}
}
}
By this way, you should have at least one item selected.
So I have FINALLY gotten to the point where I can select multiple items on a ListView:
ListView {
id: lv_stuffs
horizontalAlignment: HorizontalAlignment.Fill
dataModel: _app.personDataModel //REFERENCE 1
multiSelectAction: MultiSelectActionItem {
}
multiSelectHandler {
actions: [
// Add the actions that should appear on the context menu
// when multiple selection mode is enabled
ActionItem {
title: "Search for stuffs"
onTriggered: {
_app.search(lv_stuffs.selectionList());
}
...
And I am sending this selection list through to my search method:
void ApplicationUI::search(const QVariantList &list)
{
alert(QString("%1 items selected").arg(list.length()));
alert(((Person)list.at(0)).firstName);//<---- THIS IS THE PROBLEM
}
I am trying to get the "Person" object out of the GroupedDataModel that originally bound to the item... and I have to say I am more than a little stumped. The person is being added to the personDataModel via a simple insert method in a database class:
personDataModel->insert(person);
and the items are then bound to the ListView in the QML (REFERENCE 1 above). The binding is all fine and the items are visible in the list. What I can't figure out is how to now extract these "Person" objects out of the QVariantList I am sent via the MultiSelectionMethod.
My person class:
Person::Person(QObject *parent) : QObject(parent){}
Person::Person(const QString &id, const QString &firstname, const QString &lastname, QObject *parent)
: QObject(parent)
, m_id(id)
, m_firstName(firstname)
, m_lastName(lastname)
{
}
QString Person::customerID() const
{
return m_id;
}
QString Person::firstName() const
{
return m_firstName;
}
QString Person::lastName() const
{
return m_lastName;
}
void Person::setCustomerID(const QString &newId)
{
if (newId != m_id) {
m_id = newId;
emit customerIDChanged(newId);
}
}
void Person::setFirstName(const QString &newName)
{
if (newName != m_firstName) {
m_firstName = newName;
emit firstNameChanged(newName);
}
}
void Person::setLastName(const QString &newName)
{
if (newName != m_lastName) {
m_lastName = newName;
emit lastNameChanged(newName);
}
}
I have been PAINFULLY following this tutorial here, https://developer.blackberry.com/cascades/documentation/ui/lists/list_view_selection.html, which conveniently stops right where my question begins.
Are you perhaps looking for the value function?
void ApplicationUI::search(const QVariantList &list)
{
alert(QString("%1 items selected").arg(list.length()));
alert(((Person)list.value(0)).firstName);
}
(syntax of the extraction of the firstName value may not be right there, depends on your implementation)
Your Person class will be stored in a QVariant. To accomplish this, Qt has to generate some code specific to your class. It can be done by adding this right after your class definition, in the header file for example: Q_DECLARE_METATYPE(Person). You can read more about this here: http://qt-project.org/doc/qt-4.8/qmetatype.html#Q_DECLARE_METATYPE
Now, to extract the value as a Person object, you can use QVariant::value<T>() (http://qt-project.org/doc/qt-4.8/qvariant.html#value):
alert(list.at(0).value<Person>().firstName());
I have a written model (myModel) using internalpointer() for his data() implementation.
I want to filter a tree (based on myModel) using QSortFilterProxyModel,
I got it to work, only when i try to get any data from the tree my app crashes.
I think its because while calling the tree data, expecting to get the myModel indexModel, i get the myQSortFilterProxyModel indexModel.
myItem *myModel::getItem(const QModelIndex &index) const
{
if (index.isValid()) {
myItem *item = static_cast<myItem*>(index.internalPointer());
if (item) return item;
}
return rootItem;
}
myModel data() using internalPointer()
QVariant myModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role != Qt::DisplayRole && role != Qt::EditRole)
return QVariant();
myItem *item = getItem(index);
return item->data(index.column());
}
setting the filter model between myModel ant the tree
void myTree::myInit()
{
...
myModel *model = new myModel();
proxyModel = new mySortFilterProxyModel(this);
proxyModel->setSourceModel(model);
this->setModel(proxyModel);
...
myTree is QTreeView subclass.
I want to use tree->model() to get myModel model
how do I get the source model data?
This approach won't work with proxy models, for the reason you pointed out.
Internalpointer/id should only be accessed in methods that are guaranteed to get indexes passed that belong to the model itself, like data() etc.
A better approach to get an item for an index is pass it via a custom role:
enum Roles {
MyItemRole=Qt::UserRole
};
QVariant data( const QModelIndex& index, int role ) const {
...
if ( role == MyItemRole )
return QVariant::fromValue<MyItem>( item );
//MyItem or MyItem*, depending on whether it can be copied/has
// value semantics or not
...
}
And in using code:
const MyItem item = index.data( MyModel::MyItemRole ).value<MyItem>();
You'll need to Q_DECLARE_METATYPE(MyItem) (or MyItem*) in a header and call qRegisterMetaType() at runtime, so that MyItem/MyItem* can be passed as QVariant.
This approach has the advantage that it will work regardless of any proxy models in between, and the code calling data doesn't even have to know about the proxies.
All you have to do is call mapToSource() before your call to getItem().
Here's the data() method I use to change the font for a certain type of item. Otherwise it just calls the QSortFilterProxyModel's data().
virtual QVariant data(const QModelIndex & proxy_index, int role) const
{
QModelIndex source_index = mapToSource(proxy_index);
IT::TreeItem * item = IT::toItem(source_index);
if (role == Qt::FontRole && item->isMessage()) return _bold_font;
return QSortFilterProxyModel::data(proxy_index, role);
}