I am attempting to create a model/view application in Qt 4.7.1. I am a very new Qt developer.
Summary of what I am attempting to do:
I have a treeview that is organized as a rectangular table of rows and columns. One column of items contains a button. By default this button is to be transparent and disabled. A given button is to become visible and enabled when the mouse is hovering over its row.
The approach I am pursuing is to
find the model index for the cell that the mouse is hovering over, and
obtain a pointer to the widget associated with the widget, and
using this pointer manipulate the visibility of the button within said widget.
I cannot get a valid pointer to the widget.
my current code looks like this:
void HistoryTreeView::mouseMoveEvent(QMouseEvent *event)
{
QAbstractItemModel *m(model());
// Only do something when a model is set.
if (m)
{
QModelIndex index = indexAt(event->pos());
if (index.isValid())
{
// if the mouse has moved to another row
if (index.row() != m_currentRow)
{
m_currentRow = index.row();
QMessageBox::information( this, "HistoryTreeView", QString("index(%1)").arg(index.row()));
QWidget * item = indexWidget(index);
Q_ASSERT(item != NULL );
}
}
else // model is invalid
{
m_currentRow = -1;
}
}
QTreeView::mouseMoveEvent(event);
}
The symptoms:
I expected the call to indexWidget() to return a valid pointer to the widget the mouse is over. Instead it unexpectedly returns a NULL pointer.
Commentary:
The variable named 'index' is acting as I expected because the QMessageBox shows the correct row value. Consequently I do not think there is anything wrong with the value I am providing to indexWidget().
This is just debug code. It is missing things like code that selects the column that holds the buttons.
OK, Here is the nature of my error as I understand it.
I had incorrectly understood that every item in a view is its own widget. I now understand that the view itself is a widget, but that individual items within the view are not widgets, per se.
Because I had misunderstood that view items were widgets I believed that could:
obtain an index from a given element in a model,
use indexWidget() to obtain a Widget * to the view item associated with the model element
and then use this pointer to manipulate the view item as though it was a widget.
indexWidget() simply returned a NULL because view items are not widgets.
Related
How to be given setFocus() properties into QGraphicsItemGroup item?
I have 3 GraphicsRectItem in QGraphicsItemGroup with bondingrect()
and I want to give setfocus() preporty this QGraphicsItemGroup to can be controled with keyPressEvent
Although your question does not provide enough details, but it seems that you have some graphics items inside a group, and you want to control the items through the keyboard, right?
If so, let's assume that you have some QGraphicsItem items in a QGraphicItemGroup and you want to change the active item by using the Tab key and then do some actions on the active item by using other keys (e.g., rotate it or so on).
My solution is as follows:
define a QGraphicsItem *activeItem pointer which will point to the active item in the group.
overriding the group's keyPressedEvent function as follows:
void MainWindow::keyPressEvent(QKeyEvent *event)
{
//Which key is pressed? ---> <QKeyEvent> must be included
if (event->key() == Qt::Key_Tab) {
/* set activeItem as you desire.
* For example, find the index of the
* item that currently activeItem is pointing
* to and then go to the next item.
*/
//then return
}
if (event->key() == Qt::Key::YourDesiredKey) {
//do your actions and then return
}
}
I would like to know the best way to achieve the following thing in QML:
I have a ListView with droppable elements and a Grid initialy filled with DropArea. The ListView uses a model derived from QAbstractItemModel. I would like to drop an element on the grid and interact with it (rename it for instance). For now, any modifications in the ListView update the model, but how modifications of the element in the grid could update the model ?
There can be multiple items dropped in the grid corresponding to a subset of ListView's model. I do not know how can I achieve this. The Package can not be used because the Grid is not a GridView and Items must be moved/set at specific positions. So I tried to:
create a ListView displayed on the dropped item, using the same model as the source ListView used to drag items,
set the same rootIndex, then the same index
I am close to a solution but I think it is not the best way to do this.
Any clue ?
Thanks
I would like to have different visual representation of the same model
item in a ListView and in a component in a Grid. So, a modification of
the item in the ListView should update the item in the Grid and
vice-versa.
If your model's data is not QObject derived with NOTIFY and properties, notifications of changes can only be made through the model.
And since you won't be using the model for the grid, that means your model has to use underlying QObject instances. A generic model object might come in handy.
When you have that you only need to reference the underlying QObject model item and use bindings to set up your list view delegate. Then when you drag and drop, you only need to pass a reference to the QObject to that other visual representation which you will be creating inside the grid.
Lastly, take care not to be left with dangling references when you remove items from the model, as that will most likely hard-crash your application. Use the onDestruction attached signal to clear elements from the grid as the model item objects are destroyed.
I finally found a solution by create a C++ type which inherits from QObject an which can be embedded in a QML object. This type has read/write properties and is initialized with the same model as the ListView. The interesting methods are:
/* writing to a property **from QML** goes here */
void ModelItem::setName(const QString& name)
{
setModelData(GroupMemberModel::Nom, name);
}
/* then here */
bool ModelItem::setModelData(GroupMemberModel::Role role, const QVariant& value)
{
return m_model->setData(m_modelIndex, value, role);
}
/* any changes in the model fall here (signals/slots mecanism)*/
void ModelItem::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles)
{
if(m_modelIndex.row() < topLeft.row() || m_modelIndex.row() > bottomRight.row())
return;
if(m_modelIndex.column() < topLeft.column() || m_modelIndex.column() > bottomRight.column())
return;
//Index is modified, emit signal
foreach(int role, roles) {
emitDataChanged(role);
}
}
/* **notify QML** by emit signal on property */
void ModelItem::emitDataChanged(int role) const
{
if(role < (Qt::UserRole+1))
role+=Qt::UserRole+1;
switch(role)
{
case GroupMemberModel::Nom:
emit nameChanged();
break;
default:
qDebug() << "ModelItem::dataChanged, unknown role";
break;
}
}
This works as needed and is simplier than what I thought.
I do not understand why a selection is returned by my QListView, even when the user does not select an item, no item appears as selected, and clearSelection() is called on the pane before it is displayed.
Here is the relevant code that creates the list items:
class WidgetInstanceIdentifier {...} // This class is properly registered with Qt
listpane = new QListView(...);
QStandardItemModel * model = new QStandardItemModel(listpane);
int index = 0;
std::for_each(vg_list.cbegin(), vg_list.cend(), [&](WidgetInstanceIdentifier const & vg)
{
QStandardItem * item = new QStandardItem();
std::string text = "...";
item->setText(text.c_str());
item->setEditable(false);
item->setCheckable(false);
QVariant v;
v.setValue(vg);
item->setData(v);
model->setItem( index, item );
++index;
});
model->sort(0);
listpane->setModel(model);
listpane->clearSelection();
Here is a screenshot showing the dialog at the moment I click "OK". Notice that neither item in the list pane is selected:
... And here is the relevant code that tests for the selection:
QItemSelectionModel * listpane_selectionModel = listpane->selectionModel();
QModelIndex selectedIndex = listpane_selectionModel->currentIndex();
if (!selectedIndex.isValid())
{
// This block of code is not hit!!!!!!
// I expect it would be hit
}
QVariant vg_variant = listpaneModel->item(selectedIndex.row())->data();
// The following variable is properly set to correct data representing
// the first item in the list
vg_to_use = vg_variant.value<WidgetInstanceIdentifier>();
As noted in the code, the block of code that I expect to be hit in the case of "no selection" - the if (!selectedIndex.isValid()) {...} block - is not hit. Instead, the first item in the list is returned, as though it is selected. This is not desired! The user has no way to know which item is really being selected.
What am I misunderstanding? Why does Qt seem to report that there is a valid selection, even with no item selected in the list? What is the proper way to check if there is really no item selected?
I think selected item and current item are not the same things. Although, in some cases current item can be selected too, but this is not necessarily. Usually the current item indicated by the dashed outline in the view. Thus, if you want to check selection items count do it with QItemSelectionModel::selectedIndexes() function, i.e.:
QModelIndexList selected = listpane_selectionModel->selectedIndexes();
if (!selected.isEmpty()) {
// do something
}
I have a QListView with the ViewMode set to IconMode. I would like to achieve the following DnD behavior:
If a list view item is dragged inside the view, only the items position in the view is changed. This is the same as setting DragDropMode equal to InternalMove.
If a list item is moved out of the view, it can be copied to another external view. In this case, DragDropMode is equal to DragOnly.
How do I mix the two modes in such a way that both behaviors are supported by the view?
You might be able to do this by overriding the dropEvent of your view like this:
void MyListView::dropEvent( QDropEvent* e )
{
if( e->source() != this )
{
// something comes from the outside
// what to do? return?
return;
}
else
{
// event comes from the view itself, let's do some stuff
// for example call the base class default event
QAbstractItemView::dropEvent(e);
}
}
I guess the correct flag would be QAbstractItemView::DragDrop to do this.
I'm trying to do something that seems like it should be very simple, but the more I look into it I wonder if it's a Qt bug.
So, I have a QTableView that has columns that can be shown/hidden as the user likes. After I initialize the table, I call a custom restoreColumns() method that hides the columns (using QTableView::hideColumn()) that the user had hidden the last time the GUI was open.
The problem then comes when the user tries to show the columns that were hidden by the user the last time the GUI was ran. The appropriate signal/slot gets called and run through but for some reason the QTableView isn't updating to display the column.
What's weird is that any column that is already displayed (was not hidden by the user the last time the GUI was ran) has no problems with getting hidden/shown.
Any thoughts? Thanks!
Here's how I initialize the table...
m_tableModel = new mytablemodel();
m_tableView = new mytableview();
m_tableView->setItemDelegate(m_tableDelegate);
m_tableView->setModel(m_tableModel);
Meat of restoreColumns() method:
for (int i=0; i<horizontalHeader()->count(); i++) {
// load size to restore previous width
...
horizontalHeader()->resizeSection(i, width); // restore width
// load previous column position
...
// restore column order
int currentVisualIndex = horizontalHeader()->visualIndex(i);
if (currentVisualIndex != visualIndex)
horizontalHeader()->moveSection(currentVisualIndex, visualIndex);
// load previous hidden/shown state
...
if (columnHidden) {
hideColumn(i);
} else {
showColumn(i);
}
}
Below is some sample code to show/hide one of the columns.
void mytableview::showAColumn(bool checked) {
// mytableview is a subclass of qtableview
if (checked)
showColumn(COLUMN_A); // COLUMN_A is an enum for the column
else
hideColumn(COLUMN_A);
}
Which is connected to a QAction that can be accessed from the Menu and Context Menu of the QHeaderView of the QTableView.
connect(action, SIGNAL(toggled(bool)), this, SLOT(showAColumn(bool)));
When you are loading the previous width of the hidden columns, the width that was saved was 0.
So, when resizing the column make sure that the width is greater than 0.
Do this and then the columns will show/hide as expected.