removeRows() and QPersistentModelIndex - c++

I have implemented my own QAbstractListModel which is based on an std::vector. I now want to display the contents of this model in a QGraphicsScene. For this I have implemented my own QGraphicsItem which stores a QPersistentModelIndex as a pointer to the data.
I have implemented the removeRows method as follows:
bool VectorModel::removeRows(int row, int count, const QModelIndex& parent) {
if (row + count < _vector.size()) {
beginRemoveRows(QModelIndex(), row, row + count);
_vector.erase(_vector.begin() + row, _vector.begin() + row + count);
endRemoveRows();
return true;
}
return false;
}
Now since I erase some elements, the index of the following elements will change. Because of this the QPersistentModelIndex needs to be adjusted.
I have found the changePersistentIndex() method in QAbstractItemModel and I know that I can get all persistent indices with persistentIndexList(). However I don't know how to adjust the indices accordingly using this method. How can this be done?
Will changing these indices be enough to prevent Invalid index errors?
Update
I have changed the removeRows() with the enhancements of #Sebastian Lange, however it is still not working as expected and I receive Invalid index errors:
bool LabelModel::removeRows(int row, int count, const QModelIndex& parent) {
Q_UNUSED(parent)
if (row + count < _vector.size()) {
beginRemoveRows(QModelIndex(), row, row + count);
_vector.erase(_vector.begin() + row, _vector.begin() + row + count);
endRemoveRows();
auto pil = persistentIndexList();
for(int i = 0; i < pil.size(); ++i)
{
if (i >= row + count) {
changePersistentIndex(pil[i], pil[i-count]);
}
}
return true;
}
return false;
}
The emitted errors look like this (when removing the 7th element):
QAbstractItemModel::endRemoveRows: Invalid index ( 7 , 1 ) in model QAbstractListModel(0x101559320)
QAbstractItemModel::endRemoveRows: Invalid index ( 8 , 1 ) in model QAbstractListModel(0x101559320)
QAbstractItemModel::endRemoveRows: Invalid index ( 9 , 1 ) in model QAbstractListModel(0x101559320)
QAbstractItemModel::endRemoveRows: Invalid index ( 10 , 1 ) in model QAbstractListModel(0x101559320)
QAbstractItemModel::endRemoveRows: Invalid index ( 6 , 1 ) in model QAbstractListModel(0x101559320)

Well, you do not need to fiddle with changePersistentIndex, calling beginRemoveRows and endRemoveRows will automatically update all the persistent indexes currently existing on the model. The only invalid QPersistentModelIndex you should have after removing rows are the index on rows which have been actually deleted

Related

Qcombobox remove and add item in C++ Qt

Creating a combobox in Qtableview2 column 1 and passing values from Qtableview1 column1
so i am storing column1 table1 values in Qstringlist and passing to combobox
void cymodel::rowvalues() {
QAbstractItemModel* table1 = ui.tableView->model();
QAbstractItemModel* table2 = ui.tableView_2->model();
QStringList colvallist1;
for (int r = 0, maxI = table1->rowCount(); r < maxI; ++r)
colvallist1.append(table1->data(table1->index(r, 0)).toString());//store value in stringlist
for (int i = 0, maxI = table2->rowCount(); i < maxI; ++i)//for all rows
{
const QModelIndex idx = table2->index(i, 1);
QComboBox* combo = qobject_cast<QComboBox*>(ui.tableView_2->indexWidget(idx));
if (!combo)
{
combo = new QComboBox(); // make combo
ui.tableView_2->setIndexWidget(idx, combo);// add combo
}
// combo->model()->removeRows(0, combo->count(), combo->rootModelIndex());
colvallist1.removeDuplicates(); // clear duplicates in colvallist1
colvallist1.removeAll(QString("")); //remove empty row data
combo->setPlaceholderText(QString(" "));
combo->addItems(colvallist1);
}
}
connect(ui.tableView->model(), &QAbstractItemModel::dataChanged, this,
&cymodel::rowvalues);
Using removerows() all items are removed everytime,,if I added some values in column0 table1 then select in combobox and again adding values in table1 Column0 that time combobox selection go away
but if i don't use removerows() then when I'm adding new item to combobox then its adding multiple time like 2 values added then i add 2 more in table1 col then in combobox it become 4
So, how to add those that appear in colvallist1 but are not already in the combo & remove those that don't appear in colvallist1 ??
Thanks Plz help!!
thnks,,,i solved this..
to remove the items in combobox which are not in colvallist1..without change in selection
here is the code--
void cymodel::rowvalues() {
QAbstractItemModel* table1 = ui.tableView->model();
QAbstractItemModel* table2 = ui.tableView_2->model();
QStringList colvallist1;
for (int r = 0, maxI = table1->rowCount(); r < maxI; ++r)
colvallist1.append(table1->data(table1->index(r, 0)).toString());//store value in stringlist
for (int i = 0, maxI = table2->rowCount(); i < maxI; ++i)//for all rows
{
const QModelIndex idx = table2->index(i, 1);
QComboBox* combo1 = qobject_cast<QComboBox*>(ui.tableView_2-
>indexWidget(idx));
if (!combo1)
{
combo1 = new QComboBox(); // make combo
ui.tableView_2->setIndexWidget(idx, combo);// add combo
}
colvallist1.removeDuplicates(); // clear duplicates in colvallist1
colvallist1.removeAll(QString("")); //remove empty row data
combo1->setPlaceholderText(QString(" "))
QString selected = combo1->currentText();
int indx = combo1->currentIndex();
combo1->clear();
combo1->addItems(colvallist1);
combo1->findData(selected);
combo1->setCurrentIndex(indx);
combo1->setCurrentText(selected);
}

Sort QTableView column containing index widgets

I have a QTableView whose model is a QStandardItemModel. In one of the columns, I create new indexes and use setIndexWidget to assign them to a custom color-selection widget. I'd like to be able to sort the view using this column so users could group items with like colors. I've implemented the operator< for the custom widget, but whenever I try to use it I get a run-time debug assertion saying:
Expression: Invalid operator<
In order to sort by this column do I need to set some role data for the QStandardItem the widgets are attached to?
I think the operator< code is fine but I'm including it as well just in case.
bool operator<(const QPenWidget &rhs) const
{
// use the RGB value to index the color
int r_lhs, g_lhs, b_lhs, rgb_lhs;
int r_rhs, g_rhs, b_rhs, rgb_rhs;
this->m_pen.color().getRgb(&r_lhs, &g_lhs, &b_lhs);
rgb_lhs = (r_lhs * 65536) + (g_lhs * 256) + b_lhs;
rhs.m_pen.color().getRgb(&r_rhs, &g_rhs, &b_rhs);
rgb_rhs = (r_rhs * 65536) + (g_rhs * 256) + b_rhs;
if (rgb_lhs != rgb_rhs)
return rgb_lhs < rgb_rhs;
if (m_pen.style() != rhs.m_pen.style())
return m_pen.style() < rhs.m_pen.style();
return m_pen.width() < rhs.m_pen.width();
}
By default, the QStandardItemModel uses whatever the data for the Qt::DisplayRole is to do the sorting. While it's possible to change the role using setSortRole, for index widgets the easiest thing to do is put some sorting hash value into the display role (note: this doesn't affect how the widget is shown in the view).
For this application here is a hash function:
int sortKey()
{
int r, g, b, rgb;
m_pen.color().getRgb(&r, &g, &b);
rgb = (r * 65536) + (g * 256) + b;
rgb *= styleModel->rowCount();
rgb += m_pen.style();
rgb *= widthModel->rowCount();
rgb += m_pen.width();
return rgb;
}
Then when creating the index:
QStandardItem* penItem = new QStandardItem;
QPenWidget* pen = new QPenWidget;
tableView->setIndexWidget(penIndex, pen);
penItem->setData(pen->sortKey(), Qt::DisplayRole);

How to use a 2d vector of pointers

What is the correct way to implement an efficient 2d vector? I need to store a set of Item objects in a 2d collection, that is fast to iterate (most important) and also fast to find elements.
I have a 2d vector of pointers declared as follows:
std::vector<std::vector<Item*>> * items;
In the constructor, I instantiate it as follows:
items = new std::vector<std::vector<Item*>>();
items->resize(10, std::vector<Item*>(10, new Item()));
I how do I (correctly) implement methods for accessing items? Eg:
items[3][4] = new Item();
AddItem(Item *& item, int x, int y)
{
items[x][y] = item;
}
My reasoning for using pointers is for better performance, so that I can pass things around by reference.
If there is a better way to go about this, please explain, however I would still be interested in how to correctly use the vector.
Edit: For clarification, this is part of a class that is for inventory management in a simple game. The set 10x10 vector represents the inventory grid which is a set size. The Item class contains the item type, a pointer to an image in the resource manager, stack size etc.
My pointer usage was in an attempt to improve performance, since this class is iterated and used to render the whole inventory every frame, using the image pointer.
It seems that you know the size of the matrix beforehand, and that this matrix is squared. Though vector<> is fine, you can also use native vectors in that case.
Item **m = new Item*[ n * n ];
If you want to access position r,c, then you only have to multiply r by n, and then add c:
pos = ( r * n ) + c;
So, if you want to access position 1, 2, and n = 5, then:
pos = ( 1 * 5 ) + 2;
Item * it = m[ pos ];
Also, instead of using plain pointers, you can use smart pointers, such as auto_ptr (obsolete) and unique_ptr, which are more or less similar: once they are destroyed, they destroy the object they are pointing to.
auto_ptr<Item> m = new auto_ptr<Item>[ n * n ];
The only drawback is that now you need to call get() in order to obtain the pointer.
pos = ( 1 * 5 ) + 2;
Item * it = m[ pos ].get();
Here you have a class that summarizes all of this:
class ItemsSquaredMatrix {
public:
ItemsSquaredMatrix(unsigned int i): size( i )
{ m = new std::auto_ptr<Item>[ size * size ]; }
~ItemsSquaredMatrix()
{ delete[] m; }
Item * get(unsigned int row, unsigned int col)
{ return m[ translate( row, col ) ].get(); }
const Item * get(unsigned int row, unsigned int col) const
{ return m[ translate( row, col ) ].get(); }
void set(unsigned int row, unsigned int col, Item * it)
{ m[ translate( row, col ) ].reset( it ); }
unsigned int translate(unsigned int row, unsigned int col) const
{ return ( ( row * size ) + col ); }
private:
unsigned int size;
std::auto_ptr<Item> * m;
};
Now you only have to create the class Item. But if you created a specific class, then you'd have to duplicate ItemsSquaredMatrix for each new piece of data. In C++ there is a specific solution for this, involving the transformation of the class above in a template (hint: vector<> is a template). Since you are a beginner, it will be simpler to have Item as an abstract class:
class Item {
public:
// more things...
virtual std::string toString() const = 0;
};
And derive all the data classes you will create from them. Remember to do a cast, though...
As you can see, there are a lot of open questions, and more questions will raise as you keep unveliling things. Enjoy!
Hope this helps.
For numerical work, you want to store your data as locally as possible in memory. For example, if you were making an n by m matrix, you might be tempted to define it as
vector<vector<double>> mat(n, vector<double>(m));
There are severe disadvantages to this approach. Firstly, it will not work with any proper matrix libraries, such as BLAS and LAPACK, which expect the data to be contiguous in memory. Secondly, even if it did, it would lead to lots of random access and pointer indirection in memory, which would kill the performance of your matrix operations. Instead, you need a contiguous block of memory n*m items in size.
vector<double> mat(n*m);
But you wouldn't really want to use a vector for this, as you would then need to translate from 1d to 2d indices manually. There are some libraries that do this for you in C++. One of them is Blitz++, but that seems to not be much developed now. Other alternatives are Armadillo and Eigen. See this previous answer for more details.
Using Eigen, for example, the matrix declaration would look like this:
MatrixXd mat(n,m);
and you would be able to access elements as mat[i][j], and multiply matrices as mat1*mat2, and so on.
The first question is why the pointers. There's almost never any reason
to have a pointer to an std::vector, and it's not that often that
you'd have a vector of pointers. You're definition should probably be:
std::vector<std::vector<Item> > items;
, or at the very least (supposing that e.g. Item is the base of a
polymorphic hierarchy):
std::vector<std::vector<Item*> > items;
As for your problem, the best solution is to wrap your data in some sort
of a Vector2D class, which contains an std::vector<Item> as member,
and does the index calculations to access the desired element:
class Vector2D
{
int my_rows;
int my_columns;
std::vector<Item> my_data;
public:
Vector2D( int rows, int columns )
: my_rows( rows )
, my_columns( columns )
{
}
Item& get( int row, int column )
{
assert( row >= 0 && row < my_rows
&& column >= 0 && column < my_columns );
return my_data[row * my_columns + column];
}
class RowProxy
{
Vector2D* my_owner;
int my_row;
public;
RowProxy(Vector2D& owner, int row)
: my_owner( &owner )
, my_row( row )
{
}
Item& operator[]( int column ) const
{
return my_owner->get( my_row, column );
}
};
RowProxy operator[]( int row )
{
return RowProxy( this, row );
}
// OR...
Item& operator()( int row, int column )
{
return get( row, column );
}
};
If you forgo bounds checking (but I wouldn't recommend it), the
RowProxy can be a simple Item*.
And of course, you should duplicate the above for const.

How to select next row in QTableView programmatically

I have QTableView subclass that I am marking and saving its state with this :
connect(this,
SIGNAL(clicked(const QModelIndex &)),
this,
SLOT(clickedRowHandler(const QModelIndex &))
);
void PlayListPlayerView::clickedRowHandler(const QModelIndex & index)
{
int iSelectedRow = index.row();
QString link = index.model()->index(index.row(),0, index.parent()).data(Qt::UserRole).toString();
emit UpdateApp(1,link );
}
now i like programmatically to move the selection to the next row (not by pressing the row with the mouse)
and invoking clickedRowHandler(...) how shall i do that ?
Thanks
You already have the current row index, so use something like the following to get the modelindex for the next row
QModelIndex next_index = table->model()->index(row + 1, 0);
Then you can set that modelindex as the current one using
table->setCurrentIndex(next_index);
Obviously you'll need to make sure you're not running past the end of the table, and there's probably some extra steps to make sure the entire row is selected, but that should get you closer.
/*
* selectNextRow() requires a row based selection model.
* selectionMode = SingleSelection
* selectionBehavior = SelectRows
*/
void MainWindow::selectNextRow( QTableView *view )
{
QItemSelectionModel *selectionModel = view->selectionModel();
int row = -1;
if ( selectionModel->hasSelection() )
row = selectionModel->selection().first().indexes().first().row();
int rowcount = view->model()->rowCount();
row = (row + 1 ) % rowcount;
QModelIndex newIndex = view->model()->index(row, 0);
selectionModel->select( newIndex, QItemSelectionModel::ClearAndSelect );
}

How do I iterate a collection of Excel columns in C++ using Automation?

I want to do the moral equivalent of the following VBA code:
For Each col In Worksheets("Sheet1").Columns
# do stuff
Next col
I have generated MFC wrappers for the Excel type library that get me this far (the generated types all derive from COleDispatchDriver:
CApplication app;
app.CreateDispatch( clsid, e );
CWorkbooks wbks( app.get_Workbooks() );
CWorkbook book( wbks.Open( filename, /* optional args */ ) );
CRange cols( app.get_Columns() );
long numCols = cols.get_Count();
and from there I'm stuck. It looks like I can sort of iterate over cells using Range::get_Item( rowid, colid ), and then get the column from the cell, but I was looking for a more direct translation of the above loop.
(EDIT) Clarification: I don't actually care about the individual cells. My goal is to determine which columns have width 0 (are hidden) and delete them from the worksheet.
I assume that you're trying to traverse all the cells in the spreadsheet. You can get the active worksheet's "all cells" range, and loop through its rows and columns :
CSheets sheets = book.get_WorkSheets();
CWorkSheet sheet = sheets.get_ActiveSheet();
Range cells = sheet.get_Cells();
int nRows = cells.get_Rows().get_Count();
int nCols = cells.get_Columns().get_Count();
for (int i = 0; i <= nRows; i++)
{
for (int j = 0; j <= nCols; j++)
{
Range cell = cells.get_Item(i+1,j+1);
//Do stuff with individual cell
}
}
EDIT: In response to OP clarification:
This will probably do what you're looking for:
CSheets sheets = book.get_WorkSheets();
CWorkSheet sheet = sheets.get_ActiveSheet();
Range cols= sheet.get_Columns();
int nCols = cols.get_Count();
for (int i = nCols; i > 0; i--)
{
Range topCellOfCol = cells.get_Item(1, i);
Range entireCol = topCellOfCol.get_EntireColumn();
if (entireCol.get_Hidden())
entireCol.Delete( xlShiftToLeft );
}
Thanks to Steve, I got most of the way there. It turns out that despite the Excel VBA documentation, Range::Item has different behavior depending on how the range is created; thus:
COleVariant OPTIONAL( (long)DISP_E_PARAMNOTFOUND, VT_ERROR );
Range cols = sheet.get_Columns();
for ( long i = cols.get_Count(); i > 0; --i )
{
Range col = cols.get_Item( COleVariant( i ), OPTIONAL ) ;
// ...
}
will iterate through ranges representing the columns, and
Range rows = sheet.get_Rows();
for ( long i = rows.get_Count(); i > 0; --i )
{
Range row = cols.get_Item( COleVariant( i ), OPTIONAL );
// ...
}
will iterate through ranges representing the rows. This is implicit in the VBA examples, but not actually stated in the documentation.