This is a Qt-specific question.
It's convenient to be able to add new data to a table by typing content into a blank row at the bottom of a table. When the data is committed, a new blank row is added to the table.
Has anyone found a way of implementing this in a generic way, that fits into Qt's model-view programming architecture? My closest attempt involves creating a proxy model, such that the rowCount() returned from the model is always one greater than the source model.
QAbstractTableModel* sourceModel ; // Data is stored here
QBlankRowModel* model ; // Proxy model that adds one to rowCount()
QTableView* view ; // View
view->setModel( model ) ;
model->setSourceModel( sourceModel ) ;
Any suggestions are welcome. Thanks.
From a design-perspective, this should be part of the view, not the model. Therefore, I suggest implementing a view with the functionality and leave the model unchanged. KOffice Kexi does just this with kexitableview (screenshot, documentation). Maybe you want to use some of their code.
BTW, you might still be able to use your hack and combine it with my suggestion by putting it inside a new table view implementation YourTableView:
QBlankRowModel re-implements the
QAbstractTableModel
interface. It returns sourceModel.rowCount()+1 as the QBlankRowModel::rowCount().
It returns a QVariant() if the n+1th row is requested in QBlankRowModel::data().
All the rest within QBlankRowModel is forwarded to the sourceModel (with editing
the n+1th row in QBlankRowModel buffered and replaced with inserting into the
sourceModel when finished).
The new YourTableView inherits from
QTableView and wraps the sourceModel within
YourTableView::setModel(), calling
QTableView::setModel(QBlankRowModel(sourceModel)).
Thereby, your hack is localized at one point.
Your solutions seems a little hackish. Your problem is not only additions, it's also editions. What happens when your user edits a row, the typed data goes directly to your "data layer" even before the user commits his edition?
A better solution would be to restrict the role of your sourceModel. Rather than being a "direct" representation of your data, it should be a "buffered" representation of it. When the sourceModel is created, you make a copy of your data in some kind of Row() instances. The sourceModel, having its own copy of the data can then freely play around, perform editions and additions, and only commit the data to your model layer when the user commits his edits.
If you want a PyQt example of such a table, you can look at the source of a project of mine:
http://hg.hardcoded.net/moneyguru/
You might have to dig around to actually find the "buffering" logic because it's not in the PyQt code itself, but rather the "cross-platform" part of the code:
http://hg.hardcoded.net/moneyguru/src/tip/core/gui/table.py
This logic is then used in my QAbstractItemModel subclass:
http://hg.hardcoded.net/moneyguru/src/tip/qt/controller/table.py
Sounds like a reasonable solution, as it should work for any model that you might want as the actual table model, ie. SqlTableModel or just a plain one. As long as you add the row when the user is done editing and take care not to add the row when the user did not add any data.
Related
I'm implementing drag/drop behavior in my model, which is derived from QAbstractItemModel. My code (C++) for the drop event looks something like this:
beginInsertRows(destination_index, row, row);
destination->AcquireDroppedComponent(component);
endInsertRows();
The call to AcquireDroppedComponent can fail for a number of reasons and reject the drop, in which case no new rows will be inserted in the index stored in destination_index. My question is will calling begin/endInsertRows cause problems if this happens? My limited testing on Windows 7 so far shows no undesirable behavior, but I want to be thorough and not rely on the specific behavior of one platform. I can check beforehand if the drop will succeed or not, but I'd like to avoid the extra code if I can. My question also applies for the other begin/end functions like beginRemoveRows, beginInsertColumns, etc.
Calling these methods without doing the actions you indicate breaks their contract. How the clients of your model will cope with that is essentially undefined.
I can check beforehand if the drop will succeed or not, but I'd like to avoid the extra code if I can.
That "extra" code is absolutely necessary.
I'd refactor your code to perform acquisition and model change separately:
if (destination->acquireDroppedComponent(component)) {
beginInsertRows(destination_index, row, row);
destination->insertDroppedComponent(component);
endInsertRows();
}
The acquireDroppedComponent would store the data of the dropped object without modifying the model, and return true if it was successful and the data is usable. You then would call insertDroppedComponent to perform the model change.
We created a small painting application in JavaFX. A new requirement arose, where we have to warn the user, that he made changes, which are not yet persisted and asking him, if the user might like to save first before closing.
Sample Snapshot:
Unfortunately there are a lot of different Nodes, and Nodes can be changed in many ways, like for example a Polygon point can move. The Node itself can be dragged. They can be rotated and many more. So before firing a zillion events for every possible change of a Node object to the canvas I`d like to ask, if anyone might have an idea on how to simplify this approach. I am curious, if there are any listeners, that I can listen to any changes of the canvas object within the scene graph of JavaFX.
Especially since I just want to know if anything has changed and not really need to know the specific change.
Moreover, I also do not want to get every single event, like a simple select, which causes a border to be shown around the selected node (like shown on the image), which does not necessary mean, that the user has to save his application before leaving.
Anyone have an idea? Or do I really need to fire Events for every single change within a Node?
I think you are approaching this problem in the wrong way. The nodes displayed on screen should just be a visual representation of an underlying model. All you really need to know is that the underlying model has changed.
If, for example, you were writing a text editor, the text displayed on the screen would be backed by some sort of model. Let's assume the model is a String. You wouldn't need to check if any of the text nodes displayed on screen had changed you would just need to compare the original string data with the current string data to determine if you need to prompt the user to save.
Benjamin's answer is probably the best one here: you should use an underlying model, and that model can easily check if relevant state has changed. At some point in the development of your application, you will come to the point where you realize this is the correct way to do things. It seems like you have reached that point.
However, if you want to delay the inevitable redesign of your application a little further (and make it a bit more painful when you do get to that point ;) ), here's another approach you might consider.
Obviously, you have some kind of Pane that is holding the objects that are being painted. The user must be creating those objects and you're adding them to the pane at some point. Just create a method that handles that addition, and registers an invalidation listener with the properties of interest when you do. The structure will look something like this:
private final ReadOnlyBooleanWrapper unsavedChanges =
new ReadOnlyBooleanWrapper(this, "unsavedChanged", false);
private final ChangeListener<Object> unsavedChangeListener =
(obs, oldValue, newValue) -> unsavedChanges.set(true);
private Pane drawingPane ;
// ...
Button saveButton = new Button("Save");
saveButton.disableProperty().bind(unsavedChanges.not());
// ...
#SafeVarArgs
private final <T extends Node> void addNodeToDrawingPane(
T node, Function<T, ObservableValue<?>>... properties) {
Stream.of(properties).forEach(
property -> property.apply(node).addListener(unsavedChangeListener));
drawingPane.getChildren().add(node);
}
Now you can do things like
Rectangle rect = new Rectangle();
addNodeToDrawingPane(rect,
Rectangle::xProperty, Rectangle::yProperty,
Rectangle::widthProperty, Rectangle::heightProperty);
and
Text text = new Text();
addNodeToDrawingPane(text,
Text::xProperty, Text::yProperty, Text::textProperty);
I.e. you just specify the properties to observe when you add the new node. You can create a remove method which removes the listener too. The amount of extra code on top of what you already have is pretty minimal, as (probably, I haven't seen your code) is the refactoring.
Again, you should really have a separate view model, etc. I wanted to post this to show that #kleopatra's first comment on the question ("Listen for invalidation of relevant state") doesn't necessarily involve a lot of work if you approach it in the right way. At first, I thought this approach was incompatible with #Tomas Mikula's mention of undo/redo functionality, but you may even be able to use this approach as a basis for that too.
I want to know how to ensure the sort indicator in the horizontal header of a QTableView is updated when a programmatic sort is performed on the model.
Here's the problem:
QStandardItemModel model(3,1);
QTableView view;
view.setModel( &model );
// Populate the model ensuring it is not in a sorted order
for( int row = 0; row < model.rowCount(); ++row )
{
model.setItem( row , 0 ,
new QStandardItem(QString::number((row+1)%model.rowCount())));
}
view.setSortingEnabled( true );
// At this point everything is consistent since enabling the sorting
// triggers a sort that matches the indicator in the horizontalHeader (see A)
model.sort( 0 , Qt::AscendingOrder );
// However at this point the sort order has been reversed but the
// header's sort indicator remains unchanged (see B)
A: B:
As you can see the sort indicator remains the same and therefore is inconsistent with the actual sort order.
In my application I have two views that interact with the same model and sorting can be triggered from either of them. I don't see anything in QAbstractItemModel that signals when a sort has been performed. It seems like QHeaderView/TableView assume that they are the only thing that can trigger a sort.
Does Qt provide facilities for coping with this that I'm missing? If not, what's the best way of keeping the sort indicator up-to-date without breaking the encapsulation of the multiple views on the model too much?
One of the ItemDataRole enumerators available since Qt 4.8 is InitialSortOrderRole.
http://qt-project.org/doc/qt-4.8/qt.html#ItemDataRole-enum
It should therefore be possible to transmit sort order information through the QAbstractItemModel::headerData method.
I've tried this however and found that QTableView and QHeaderView do not seem to update in response to changes in this headerData role. A customised header view would appear to be necessary...
It might be worth it because passing this information via the model allows any number of views to synchronise without any external agent having to track all the views in existence so that it can distribute notifications. It would also work seamlessly through model proxy stacks such as those built with QSortFilterModelProxy.
The solution I've come up with to avoid breaking encapsulation too much is
to have a signal on each view (on QTableView the sortIndicatorChanged signal suffices and on my custom view I have added a similar signal).
the manager of views connects to these signals
when any view emits such a signal the manager of views calls a slot on all the other views so that they can synchronise their sort indicators
I still feel like I might be missing something - surely this is a common problem? It seems to me that QAbstractItemModel should have a way of transmitting sort-order information to views...
I am new to Qt.
I have the following situation.
Can someone suggest me how to tackle it.Thanks
I have a custom made model which i should populate in the backend.ie when the program starts there is no guarantee there will be elements in the model.But these elements may be added later in the backend.So if i add a new element in the data structure in the model how do i let the view know the change.And this is an insertion and not a change in the data.
Also if i want the view i display to be sorted how should i notify the view
To update any connected views of an insertion, call beginInsertRows(const QModelIndex& parent, int first, int last) before you modify the model, and endInsertRows() after. Read the docs.
To sort views you need to use a QAbstractProxyModel, it is highly configurable so read the docs before asking more specific questions about it.
For the Qt App I'm writing, I'd like to have a list of clickable functions, which, when clicked will allow the user to supply required input arguments.
What I'm specifically looking for is a selection of widgets which provide a clean and intuitive interface for the following tasks:
User scrolls through a list of functions for performing computations (in my case, from glm).
Once a function is found, the user clicks on the item; a popup window opens, which specifies the required input arguments (e.g., vec3, vec4, etc.).
The idea here is that the functions themselves already exist: they just need an interface, which in a nutshell, provides a pseudo interpreter to process and output their results to a GLWidget, which will update the data passed accordingly by sending it to its corresponding shader.
I've looked at QListView, and its Widget variant, but it appears to be more suited towards filesystem data, such as images or text files, though I'm not quite sure. So far, it seems to be the only thing which could be considered as realistically usable in this scenario.
Is there a recommended way to do this? I'm fairly new to Qt in general, thus my knowledge is pretty limited.
The view isn't really important in your case. You need to create/reuse a adapted model.
This model have to contain the relation between what your view displays and the action that you want to launch.
For example, if your commands are text like bash commands, you can create a view that displays "list files", "Copy files" and a model that contains the data ("list files" = 'ls -l'), ("copy files" = 'ls -l'), etc.
You can store different data (using QVariant) in a same item with different roles: Qt::DisplayRole corresponds to the data that the view displays and Qt::UserRole what you want.
So, if you only have to store a command line associated to a name, you can store the name in the item with the Qt::DisplayRole and the command line as a QString (or other) using Qt::UserRole.
See QAbstractItemModel::data() and QAbstractItemModel::setData(), for more information.