How to get selection changed notification in QTreeView - c++

I am trying to figure this out and it seems like I have to use QItemSelectionModel but I can't find an example how to wire things up.
I have defined in .h file.
QItemSelectionModel* selectionModel;
Now in constructor of the view, I set:
selectionModel = ui->treeView->selectionModel();
// the following line is not compiling!
connect(ui->treeView->selectionModel(), SIGNAL( ui->treeView->selectionModel(const QModelIndex&, const QModelIndex &) ),
this, this->selectionChanged ( QItemSelection & sel, QItemSelection & desel) );
I thought there would be predefined slot but I can't find one so I added this one (the syntax of which I found here)
void MyDialog::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
qDebug() << "Item selection changed";
}
I also tried replacing QItemSelection with QModelIndex but still doesn't work.
What do I need to do in order to simply get notified when selection has changed and than obviously grab the newly selected item?

the QObject::connect method should be used as follow :
QObject::connect(sender, SIGNAL(signal_method), receiver, SLOT(slot_method));
so in your case it should be something like
connect(selectionModel, SIGNAL(selectionChanged(const QItemSelection&,const QItemSelection&)), this, SLOT(mySelectionChanged(const QItemSelection&,const QItemSelection&)));

Related

How to programmatically change the Index of QComboBox using QStandardItemModel?

How to use the QStandardItemModel::setData function to set the current value for a QComboBox that is found in a certain cell for example at index (0,0) that works as a delegate such that the QStandardItemModel::setData function calls the ComboBoxItemDelegate::setModelData function.
I know that ComboBoxItemDelegate::setModelData function is called when an item is selected from the comboBox but my problem is that ComboBoxItemDelegate::setModelData function is not called by calling the comboBox::setData.
So I want to call comboBox::setData or (any other function that) programatically that will call ComboBoxItemDelegate::setModelData
What I have tried:
Here is the setModelData function that is called when an item from the comboBox is selected from the UI:
void ComboBoxItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
if (QComboBox* cb = qobject_cast<QComboBox*>(editor)){
qDebug()<<"hello, i have been called from the setModelData";
}
else
QStyledItemDelegate::setModelData(editor, model, index);
}
But when I tried to set the data of the comboBox (that is found in index (0,0)) to "Item A" by the following:
ui->tableView->model()->setData ( ui->tableView->model()->index(0,0), "Item A, Qt::EditRole);
the comboBox's value was set successfully, but the ComboBoxItemDelegate::setModelData function was not called, and this is my problem

How to catch key presses in editable QTableWidgetItem?

Now I can process all key presses in my QTableWidget in a function eventFilter() (after calling of myTable->viewport()->installEventFilter(this); in the constructor).
The only place where this doesn't work is editable cell while editing (because it grabs all key presses). To fix it I can't call installEventFilter() for each item in the table, because these items are not QObjects (and also I can't use connect for putting of my processing of key presses).
The only solution I have is to put QLineEdits in these cells and to use event filter to catch key presses while editing. But is it possible to solve it using only standard items? (i.e. only QTableWidgetItem with a flag Qt::ItemIsEditable)
Also I can call grabKeyboard() for my QTableWidget. In this case I'll have all key presses (even while editing of cells by user), but it blocks edit box (i.e. user can't input anything). May be it is possible to fix broken edit boxes after calling of grabKeyboard() for the table?
This so quite ease to achieve. Just subclass QStyledItemDelegate override createEditor method like this:
QWidget *AlterEditorDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
QWidget *result = QStyledItemDelegate::createEditor(parent, option, index);
result->installEventFilter(new YourEventFilter(result));
return result;
}
Than replace delegate for your QTableWidget.
Or even better instead subclassing create proxy class which accepts original QAbstractItemDelegate (more writing but much more universal and can be composed with other modifications).
AlterEditorProxyDelegate::AlterEditorProxyDelegate(QAbstractItemDelegate *original, QObject *parent)
: QAbstractItemDelegate(parent)
, original(original)
{}
QWidget *AlterEditorProxyDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
QWidget *result = original->createEditor(parent, option, index);
result->installEventFilter(new YourEventFilter(result));
return result;
}
// other methods which invokes respective methods for `original` style.
Since QTableWidgetItem has no function keyEvent() that you can overload this is not possible.
What you have to do is set a delegate with custom editor factory that produces widgets where keyEvent is overloaded.
But is it possible to solve it using only standard items? (i.e. only QTableWidgetItem with a flag Qt::ItemIsEditable)
Not really. In Qt4 QTableWidget leaks KeyRelease events from the cell editor, but exploiting that would be an ugly hack.
May be it is possible to fix broken edit boxes after calling of grabKeyboard() for the table?
I once tried doing that and then posting the events to QTableWidget but ran into trouble as well.
The proper thing to do is to create your own delegate and install event filter in createEditor function. You can do something like this:
class FilterDelegate : public QStyledItemDelegate
{
public:
FilterDelegate(QObject *filter, QObject *parent = 0) :
QStyledItemDelegate(parent), filter(filter)
{ }
virtual QWidget *createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QWidget *editor = QStyledItemDelegate::createEditor(parent, option, index);
editor->installEventFilter(filter);
return editor;
}
private:
QObject *filter;
};
Then your MainWindow constructor would look something like this:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
setupUi(this);
tableWidget->setItemDelegate(new FilterDelegate(this));
tableWidget->installEventFilter(this);
}
And your event filter:
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if(event->type() == QEvent::KeyPress)
{
// do something
}
return QMainWindow::eventFilter(obj, event);
}
ANOTHER ALTERNATIVE:
You can install event filter on the QApplication object and capture all events. This is a bit of an overkill if you ask me, but it would work for a small application and requires minimal code.
All you have to do is:
qApp->installEventFilter(this);
And:
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if(event->type() == QEvent::KeyPress)
{
// do something
}
return QMainWindow::eventFilter(obj, event);
}

Getting access to filepath with QFileSystemModel

I have a QFileSystemModel within a QListView that allows me to select items within the file system.
When I select an item, I want to return the filepath in a QMessageBox.
So far I have the following code:
filemodel = new QFileSystemModel(this);
filemodel->setFilter(QDir::NoDotAndDotDot | QDir::Files);
filemodel->setNameFilters(filters);
filemodel->setNameFilterDisables(false);
filemodel->setRootPath(sPath);
//get file path
QString filepath = filemodel->fileName(index);
QMessageBox::information(this, "title", filepath);
ui->listView->setModel(filemodel);
This creates the filemodel.
I'm getting this error:
mainwindow.cpp:46: error: no matching function for call to 'QFileSystemModel::fileName(char* (&)(const char*, int))'
Is this the correct way to go about this? Returning the filepath when an item is selected?
EDIT #dunc123
In constructor:
connect(ui->listView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), this, SLOT(selectedFile(QItemSelection one, QItemSelection two)));
selectedFile:
void MainWindow::selectedFile(QItemSelection one, QItemSelection two){
QString file_name = filemodel->fileName(ui->listView->selectionModel()->currentIndex());
QMessageBox::information(this, "title", file_name);
}
It builds and runs but when I click on a file, I get the following error:
Object::connect: No such slot MainWindow::selectedFile(QItemSelection one, QItemSelection two) in ../Images/mainwindow.cpp:26
Object::connect: (receiver name: 'MainWindow')
I'm assuming the way I pass the variables is wrong?
Could you help me out?
You need to pass a QModelIndex object to the fileName method of QFileSystemModel. It appears the symbol "index" is being resolved as a function. At a guess you have a member function named index in your class.
Edit:
The larger issue here is that you want something to happen when you select an item in your QListView, but you are putting the code to handle this in the constructor. You need to create a slot in your class and connect this to the signal emitted when an item is selected.
connect(ui->listView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &), this, SLOT(...));
In this slot you should call the fileName method and display that information. You'll need to make filemodel a member variable of your class as well so that you can access it from the slot.
Edit 2:
The way you are specifying your slot when calling connect is incorrect, it should be:
connect(ui->listView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), this, SLOT(selectedFile(QItemSelection , QItemSelection)));
However, since you aren't using either of those parameters in your slot, you can infact remove them all together from your slot, e.g. define it in your header as:
void selectedFile();
And connect it using:
connect(ui->listView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), this, SLOT(selectedFile()));
QT will work out that you don't want either of the parameters from the signal.
You can get a list of selected indexes with QItemSelectionModel::selectedIndexes() function.
Heres an example of how you can use it:
QModelIndexList list = ui->listView->selectionModel()->selectedIndexes();
foreach (QModelIndex index, list)
{
QString file_name = fileModel->fileName(index);
}
Or if you can only select a single item, you can use the QItemSelectionModel::currentIndex function like this:
QString file_name = fileMode->fileName(ui->listView->selectionModel()->currentIndex());
You can also connect the QItemSelectionModel::selectionChanged signal to a slot and use that in similar fashion. QListView has a selectionModel() function that you can use to retrieve a QItemSelectionModel object. QItemSelection has a indexes() function that returns a QModelIndexList.
QString filepath = filemodel->fileName(index);
mainwindow.cpp:46: error: no matching function for call to 'QFileSystemModel::fileName(char* (&)(const char*, int))'
Looks like "index" is a function, not a QModelIndex...
You can use a slot that is connected to the model's signal "currentChanged(QModelIndex,QModelIndex)" to get the new selected index.

In a QStyledItemDelegate for a QListWidget, how do I paint the selected item to match the standard list?

I have a QStyledItemDelegate for my QList widget:
class MappingDisplayWidgetDelegate: public QStyledItemDelegate
{
Q_OBJECT
public:
MappingDisplayWidgetDelegate(QObject *parent=NULL);
virtual void paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const;
virtual QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const;
};
and it's paint method looks like:
void MappingDisplayWidgetDelegate::paint ( QPainter * painter,
const QStyleOptionViewItem & option,
const QModelIndex & index ) const
{
if (option.state & QStyle::State_Selected)
painter->fillRect(option.rect, option.palette.highlight());
else if (option.state & QStyle::State_MouseOver)
painter->fillRect(option.rect, option.palette.midlight());
QFontMetrics fm(option.font);
QString filename = index.data(Qt::DisplayRole).toString();
QRect outline = option.rect;
int outmid = outline.center().y();
QRect fnBound = fm.boundingRect(filename);
int fnBoundMid = fnBound.center().y();
fnBound.moveLeft(outline.left());
fnBound.translate(0, outmid - fnBoundMid);
painter->drawText(fnBound, Qt::AlignVCenter | Qt::AlignLeft, filename);
}
Now this works, BUT the handling of the State_Selected and State_MouseOver doesn't produce the same results as the default lists. Here's a screen shot (I'm running on a Win7 system) of the code above on the left, and a standard QListWidget on the right. You can see that the QListWidget has nice gradients, while my items have only simple colors.
I want to paint my items to properly match the standard widgets, but I can't quite figure out how. I'm not seeing anything in the options that would give me the info I need.
Edited to add:
Note that this is a somewhat toy example. The real code has a bunch of other stuff besides the one string, and has an appropriate (and working) sizeHint function. This is just a toy to demonstrate the problem.
In the source code for drawing that element, the following call is made:
// draw the background
proxy()->drawPrimitive(PE_PanelItemViewItem, opt, p, widget);
I suspect you may want to do something similar to the following:
style().drawPrimitive(QStyle::PE_PanelItemViewItem, option, painter, NULL);
You may have to play around with the option parameter to get the right rectangle target. Unfortunately, the computer I am using right now isn't set up to test any of this, so YMMV.
I had the same question--I wanted to have a totally custom paint function, but take advantage of the built-in pretty selection painting. I've tried the below code, and so far I'm finding that it works great with no negative consequences:
void MassIndexItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex& index) const {
QStyledItemDelegate::paint(painter, option, QModelIndex());
//Above, note that rather than passing >index< to the inherited function, I pass a default, invalid index.
//Since my model's data() function returns an empty QVariant for invalid indexes,
//I get the default selection painting, but no other data is painted in any way.
//Below, I can do whatever painting I want and it will show up on top of the pretty selection paint
painter->drawEllipse(option.rect);
}
I'm detailing this as a usable solution, though it's not a great one.
If I don't put anything in the DisplayRole of the item, then I can call the underlying QStyledItemDelegate::paint routine and it will simply draw the proper background, thusly:
void MappingDisplayWidgetDelegate::paint ( QPainter * painter,
const QStyleOptionViewItem & option,
const QModelIndex & index ) const
{
QStyledItemDelegate::paint(painter, option, index);
QFontMetrics fm(option.font);
QString filename = index.data(Qt::UserRole).toString();
QRect outline = option.rect;
int outmid = outline.center().y();
QRect fnBound = fm.boundingRect(filename);
int fnBoundMid = fnBound.center().y();
fnBound.moveLeft(outline.left());
fnBound.translate(0, outmid - fnBoundMid);
painter->drawText(fnBound, Qt::AlignVCenter | Qt::AlignLeft,
filename);
}
I don't like this, and I'm going to be trying Dave's suggestion momentarily, but if one gets stuck, this does seem to work. On the down side it means you can't use the DisplayRole, so you can't use findItems in the QListWidget.

Qt attaching itemChanged signal to QStandardItem doesn't work

I'm using qtreeview trying to find out when ever the check box state changes,
but the SLOT method never fires.
Here is my code:
// in the init
connect(ui.treeView_mainwindow, SIGNAL(itemChanged( const QModelIndex &)), this,
SLOT(tree_itemChanged( const QModelIndex &)));
// this method never trigered
void GroupMainWindowContainer::tree_itemChanged(const QModelIndex & index)
{
QStandardItem* standardItem = m_model->itemFromIndex(index);
Qt::CheckState checkState = standardItem->checkState();
if(checkState == Qt::Checked)
{
WRITELOG("Qt::Checked")
}
else if(checkState == Qt::Unchecked)
{
WRITELOG("Qt::Unchecked")
}
}
// this is how i build the items :
QList<QStandardItem *> items;
items.insert(0,new QStandardItem());
items.at(0)->setCheckable(true);
items.at(0)->setCheckState(Qt::Unchecked);
m_model->insertRow(0,items);
QTreeView doesn't have an itemChanged signal, so your QObject::connect call will fail.
This is a good example of why you should always check the return value from QObject::connect. Also, the failed connection would have appeared in your debug output, which you should also be monitoring.
Possibly you're looking for QTreeWidget, which inherits from QTreeView and does have an itemChanged signal, albeit one that has an QTreeWidgetItem* as a parameter, not a const QModelIndex&.