Simplest way to write M-to-N (eg. aggregating) proxy models?



  • I have several projects where I need to display a QTreeView sidebar that acts as a filter for a main view. However, unlike in a file manager, the source data is flat and entirely in memory.

    I've decided to implement populating the QTreeView as a proxy model and I did find this Qt Center thread for group/aggregate models, but it doesn't cover hooking up the signals and slots to react to changing data and that's an area where I don't feel confident in my understanding.

    Can someone point me to a simple example of how to write a properly updating proxy model which isn't limited to 1-to-1 mappings? (Or, failing that, the clearest possible guide on how to properly hook up the various signals and slots related to mutating data in a model)

    One of my projects does require many-to-many mappings (M tags, grouped by type, to N tagged objects) but, if there's a simpler option for one-to-many (eg. click a folder to see files within) mappings, I'd like to know for the projects to which it applies.

    I'm prototyping in Python but may rewrite in C++ once the design stabilizes, so any code will need to either be C++ with PyQt5-compatible Python bindings or be simple enough that it's not a hassle to port it myself.



  • I'm not sure I understand what you are trying to do...

    Is it something like converting a db-like table structure in a table?

    ID Region Client Amount
    1 EU Merkel 100
    2 EU Hollande 50
    3 EU Merkel 20
    4 USA Trump 1
    5 USA Clinton 2

    should become (bold numbers are in a different column)

    • EU 170
      • Merkel 120
        • 1 100
        • 3 20
      • Hollande 50
        • 2 100
    • USA 3
      • Trump 1
        • 4 1
      • Clinton 2
        • 5 2

    Is this correct?



  • @VRonin

    No. While I may have a use for that at some point, right now, my most pressing use case has a source model capable of producing a tabular view like this:

    Title Provider
    [icon] Game Name 1 <processed string>
    [icon] Game Name 2 <processed string>
    [icon] Game Name 3 <processed string>

    ...and, importantly, Qt::UserRole on any column returns that row's raw object from the underlying data model, which has members like this:

    • Game Name 1:
      • sources: {'heuristics', 'xdg'}
      • launchers: {<launcher: role: play>}
      • ...
    • Game Name 2:
      • sources: {'heuristics', 'gog.com'}
      • launchers: {<launcher: role: play>, <launcher: role: install>, etc.}
      • ...
    • Game Name 3:
      • sources: {'scummvm'}
      • launchers: {<launcher: role: play>, etc.}
      • ...

    And the result I'm trying to get from the filter model is a QTreeView that looks like this:

    • Sources
      • Filesystem Heuristics
      • GOG.com
      • ScummVM
      • System Launchers
    • Install Status
      • Downloaded
      • Playable

    (The human-readable names come from a separate set of objects)

    Then, for example, if you click "Downloadable", "selection changed" signal-slot connections should cause another filter model to limit another view to a list of records you see things with a role=install launcher but no role=play launcher.

    Since a picture really is worth 1000 words in this case, here's what I have so far, with everything but the sidebar functional and the sidebar using a very minimal temporary hack to populate partial data for visual effect.



  • This looks tatally duable using 2 QSortFilterProxyModel subclasses...
    you just need a way to link "installed" to "<launcher: role: install>"



  • @VRonin

    Linking data and metadata together is no problem. (<launcher: role: install> is just my sloppy approximation of the debugging string the Launcher instances return if you print() a reference to them in the Python prototype)

    I was actually already planning to subclass and override QSortFilterProxyModel::filterAcceptsRow for filtering the central QListView/QTableView widget.

    (Given that I have complex multi-select behaviour planned, such as "(GOG OR ScummVM) AND (Downloaded OR Playable)", combined with the aforementioned "Downloaded excludes install+play, but GOG includes heuristics+gog", I'm obviously going to need to code my own "Do we want this?" routine.)

    What has always been the problem is performing the complex M-to-N conversion to feed the QTreeView with as little wheel-reinvention as possible, and, even with you saying it should be possible, I have no idea how I'd do that by extending QSortFilterProxyModel (or even QAbstractProxyModel) with less effort than by subclassing QAbstractItemModel and reinventing everything.

    From my reading of the docs, QSortFilterProxyModel and QAbstractProxyModel seem pretty wedded to the idea of a one-to-one mapping between input and output rows.



  • @ssokolow I don't know how you link metadata to human readable strings but if its something like a "dictionary" you can use that directly as a tree



  • @VRonin

    This looks tatally duable using 2 QSortFilterProxyModel subclasses...

    I don't know how you link metadata to human readable strings but if its something like a "dictionary" you can use that directly as a tree

    While the project I need this for most immediately may allow me to solve this by forcing a QStandardItemModel to be the authoritative copy of the sidebar's data, ensuring the data is in fifth normal form, and then just overriding filterAcceptsRow to let the search field control the sidebar visibility, I asked my question the way I did because, even in this project, that's a bit of a contortion and not all of the projects I have coming can be solved that way.

    Sometimes, I can't just normalize the data before it enters Qt's purview and, when entirely new "sidebar" entries must come into existence as a side-effect of new denormalized data arriving in the main panel, that brings this back to what I wrote initially:

    Can someone point me to a simple example of how to write a properly updating proxy model which isn't limited to 1-to-1 mappings? (Or, failing that, the clearest possible guide on how to properly hook up the various signals and slots related to mutating data in a model)

    Emphasis mine. The former point (updating) is beyond the scope of this Qt Center thread and the latter (not limited to 1-to-1 mappings) makes QAbstractProxyModel and its decendants ineligible because mapFromSource and mapToSource take and return QModelIndex.

    Again...

    1. Let's assume I know what I'm talking about and I really do need to dynamically normalize aspects of data from one model to feed a supplementary view and react as the source model changes.
    2. I am perfectly capable of reinventing the wheel... it's just that...
      • Maybe I have too much faith in Qt, but doing this from scratch just because I need an M-to-N mapping feels like reinventing QAbstractProxyModel to implement multi-column filtering because I wasn't aware of QSortFilterProxyModel::filterAcceptsRow.
      • I don't feel confident in my understanding of which signals/slots I need to use when in other to ensure that the two views don't get out of sync... hence my request for example code.


  • Sorry, I didn't want to come off as dismissive.
    From your answers in other posts I am firmly convinced you know your stuff very well, apologies again if my tone was disrespectful on that side.

    Without knowing how your data is stored it's difficult however to give you any concrete answer.

    Could you give us a 2 or 3 rows example of your data (including what data is stored in what role)?



  • @VRonin

    I'm a bit skeptical that 2 or 3 rows of example data would be relevant to developing a solution I can apply to the other projects I have waiting in the wings (hence my asking about a general solution), but I'll give as much as I can.

    The Qt side of the data pipeline is still somewhat in flux (partly because I have yet to implement persistent column hide/show for the tabular view and certain custom item delegates, which means that much of the data is retrieved by retrieving the raw data objects via UserRole, bypassing the intended way of doing things.)

    However, here's how it currently works:

    1. The authoritative data is in the form of a list of objects which my backend test code dumps like this (with null entries removed):

      [
        {
          "name": "A Boy And His Blob",
          "provider": ["GOG.com"], 
          "icon": "/mnt/buffalo_ext/games/a_boy_and_his_blob_2.1.0.2/support/icon.png", 
          "base_path": "/mnt/buffalo_ext/games/a_boy_and_his_blob_2.1.0.2", 
          "commands": [
            {
              "name": "Play", 
              "role": Roles.play, 
              "provider": "GOG.com", 
              "sort_key": [ Roles.play,  ["/mnt/buffalo_ext/games/a_boy_and_his_blob_2.1.0.2/start.sh",  "--start"]], 
              "tryexec": "/mnt/buffalo_ext/games/a_boy_and_his_blob_2.1.0.2/start.sh", 
              "argv": ["/mnt/buffalo_ext/games/a_boy_and_his_blob_2.1.0.2/start.sh",  "--start"], 
              "categories": [
                "Game", 
                "ActionGame"
              ], 
      
            }, 
        }, 
      ]
      

      (Note: I synthesized the categories list since, at this point in time, the GOG.com backend doesn't set it.)

    2. The main model wraps that data (without proper dynamic updating due to my aforementioned lack of confidence in my understanding of how to hook up the signals and slots) and maps each row as follows:

      Title Providers
      DisplayRole entry.name ', '.join(x.backend_name for x in entry.provider)
      DecorationRole icon_provider.get_icon(entry.icon, ICON_SIZE)
      ToolTipRole entry.summarize()
      UserRole entry entry
    3. While it doesn't yet, the sidebar should produce a tree based on these transforms, with entries hidden if they contain no results (eg. "Not Downloadable" would only be visible if at least one manually installed game was removed after the system started keeping records for it):

      Top-level Child-node Criteria for inclusion
      Install Status all
      Not Downloadable Not in any other category
      Downloadable Has no Roles.install but provider supports downloading
      Downloaded Has Roles.install but not Roles.play
      Installed Has Roles.play
      Play Status <created-by-default entry in user-defined categories system>
      Unplayed <created-by-default entry in user-defined categories system>
      Started <created-by-default entry in user-defined categories system>
      Beaten <created-by-default entry in user-defined categories system>
      Complete <created-by-default entry in user-defined categories system>
      Endless <created-by-default entry in user-defined categories system>
      Source all
      <dynamic> One for each provider plugin
      Genres all
      <dynamic> aggregated and deduplicated from entry.categories
      <dynamic> User-defined categories system allows users to create their own tag classes
      <dynamic> User-defined categories system allows users to create and apply their own tags

    Clicking on a sidebar entry should filter the main view, so the pipeline to reach the QListView or QTableView should look like this:

    http://imgur.com/a/K9UhI



  • Ok, now I get what is going on. Sorry for being this dull.

    What you are trying to do should not be done via a proxy model. proxy models should only modify the structure of the data, it shouldn't add any data to the original model.

    CategoriesModel should be a different model populated and changed when GameListModel changes (use the dataChanged signal to detect them). they should not however be proxies of the same model as they contain different data (even if only partially different)



  • @VRonin

    To be fair to my past self, I'd forgotten about the plan for custom categories when I made my initial post and the play status was still envisioned as a hard-coded enum like Install Status. (And, when seen like that, it is purely an M-to-N aggregation of data already present in a source model that could be seen as a denormalized SQL-like table where some columns contain delimited lists.)

    That said, unless the docs are wrong, dataChanged isn't sufficient.

    This signal is emitted whenever the data in an existing item changes.

    Emphasis mine.

    This is why, even when I was asking about a high-level API for building M-to-N proxies, I was saying "or failing that, example code or reference information (more focused than the API docs) on how all of the 'something was added/removed/changed' signals and slots are intended to fit together"

    Even the source GameListModel is effectively a proxy (since it follows rather than holds the authoritative copy of the data) similar in concept to QFileSystemModel or QSqlQueryModel and both GameListModel and CategoriesModel need to be enhanced with proper change notification. (Currently, I'm just throwing out GamesListModel and generating a whole new one whenever the user hits F5... which is clearly not optimal, if for no other reason than doing so resets view state like the selection.)

    I've produced an updated data flow diagram to acknowledge that aspect and to leave myself a reminder that CategoriesModel isn't a pure proxy filter:

    https://imgur.com/a/1E9x8

    (I'm going to assume that, for the other projects which do require pure "normalize/aggregate tabular data at runtime" behaviour, there's still no high-level helper for doing M-to-N proxying, so the answers I need to build this should also be what I need to write some kind of AbstractAggregatingProxyModel for those.)



  • The closest thing I found to what you are trying to do is KCategorizedSortFilterProxyModel from KDE but it's still 1 to 1 mapping, the view also only supports lists (1 column and no children). Nevertheless it might be a good place to start given apparently this will not be something you'll find out of the box.

    An example using KCategorizedSortFilterProxyModel can be:

    #include <QApplication>
    #include <KCategorizedView>
    #include <KCategoryDrawer>
    #include <QStandardItemModel>
    #include <KCategorizedSortFilterProxyModel>
    #include <QPixmap>
    #include <QIcon>
    int main(int argc, char **argv)
    {
        QApplication app(argc,argv);
        QPixmap bluePix(48,48);
        bluePix.fill(Qt::blue);
        QStandardItemModel baseModel;
        baseModel.insertRows(0, 3);
        baseModel.insertColumns(0, 2);
        baseModel.setHeaderData(0, Qt::Horizontal, "Col1");
        baseModel.setHeaderData(1, Qt::Horizontal, "Col2");
        baseModel.setData(baseModel.index(0, 0), "0.0");
        baseModel.setData(baseModel.index(0, 0), QIcon(bluePix), Qt::DecorationRole);
        baseModel.setData(baseModel.index(0, 1), "0.1");
        baseModel.setData(baseModel.index(1, 0), "1.0");
        baseModel.setData(baseModel.index(1, 1), "1.1");
        baseModel.setData(baseModel.index(2, 0), "2.0");
        baseModel.setData(baseModel.index(2, 1), "2.1");
        baseModel.setData(baseModel.index(2, 0),5,KCategorizedSortFilterProxyModel::CategorySortRole);
        baseModel.setData(baseModel.index(2, 1),5,KCategorizedSortFilterProxyModel::CategorySortRole);
        baseModel.setData(baseModel.index(2, 0),"Test",KCategorizedSortFilterProxyModel::CategoryDisplayRole);
        baseModel.setData(baseModel.index(2, 1),"Test",KCategorizedSortFilterProxyModel::CategoryDisplayRole);
        KCategorizedSortFilterProxyModel proxyModel;
        proxyModel.setSourceModel(&baseModel);
        proxyModel.setCategorizedModel(true);
        KCategorizedView mainView;
        mainView.setCategoryDrawer(new KCategoryDrawer(&mainView));
        mainView.setModel(&proxyModel);
        mainView.show();
        return app.exec();
    }
    


  • @VRonin

    I appreciate the suggestion, but KCategorizedSortFilterProxyModel is 1-to-1 because it subclasses QSortFilterProxyModel, which means it provides no benefit over just poking through the source of QAbstractProxyModel to figure out how all of the "thing X changed in manner Y" signals and slots are supposed to be hooked up.

    (Unfortunately, I'm not familiar with Qt internals and I've been having trouble finding the code that does said hook-ups.)

    Hell, if I can just get confident in hooking up those signals and slots, I could probably split the "Games" input on CategoriesModel out into a QSortFilterProxyModel subclass with a custom filterAcceptsRow and plumb CategoriesModel directly into things like the game provider backends in order to add the non-user-defined categories.



  • KCategorizedSortFilterProxyModel is 1-to-1 because it subclasses QSortFilterProxyModel, which means it provides no benefit over just poking through the source of QAbstractProxyModel

    It does as it adds the category that is an extra piece of data not present on the original model but agree it's not great.

    to figure out how all of the "thing X changed in manner Y" signals and slots are supposed to be hooked up.

    dataChanged is emitted anytime setData is called so while not being sufficient in general (adding an empty line will not trigger it) it is in practice (as soon as you populate that empty line dataChanged will be emitted) the other signals to keep an eye on are rowsRemoved, modelReset and possibly columnsRemoved



  • @VRonin

    dataChanged is emitted anytime setData is called so while not being sufficient in general (adding an empty line will not trigger it) it is in practice (as soon as you populate that empty line dataChanged will be emitted) the other signals to keep an eye on are rowsRemoved, modelReset and possibly columnsRemoved

    That makes no sense. I'm using QAbstractItemModel rather than QStandardItemModel, which means I don't just magically get insertRows for free, which means that the signals you listed can't be all there is to it.

    I get the impression you're still be operating under the assumption that Qt's model holds the authoritative copy of the data in some sense of the word, rather than simply being a proxy that takes a non-Qt "model" as its source.

    I have to get to bed but, once I get back tomorrow, why don't I just write the kind of summary I was hoping to find and then you can tell me if I got anything obviously wrong.



  • That makes no sense. I'm using QAbstractItemModel rather than QStandardItemModel, which means I don't just magically get insertRows for free

    If you don't, you are subclassing QAbstractItemModel the wrong way. http://doc.qt.io/qt-5/qabstractitemmodel.html#subclassing clearly specifies:

    The dataChanged() and headerDataChanged() signals must be emitted explicitly when reimplementing the setData() and setHeaderData() functions, respectively.

    An insertRows() implementation must call beginInsertRows() before inserting new rows into the data structure, and endInsertRows() immediately afterwards.

    and endInsertRows takes care of emitting rowsInserted. below you find the implementation

    void QAbstractItemModel::endInsertRows()
    {
        Q_D(QAbstractItemModel);
        QAbstractItemModelPrivate::Change change = d->changes.pop();
        d->rowsInserted(change.parent, change.first, change.last);
        emit rowsInserted(change.parent, change.first, change.last, QPrivateSignal());
    }
    

    These mechanisms are relied upon by the views to work that's why they are so strict



  • @VRonin

    Yes, but the docs for insertRows also say:

    Note: The base class implementation of this function does nothing and returns false.

    If you implement your own model, you can reimplement this function if you want to support insertions. Alternatively, you can provide your own API for altering the data. In either case, you will need to call beginInsertRows() and endInsertRows() to notify other components that the model has changed.

    In other words, I have two choices for supporting unsolicited insertion events from the backend:

    • I can maintain the backend's internal model separate from the frontend's model (in which case, I might as well use QStandardItemModel) and write unappealing sync code to manually mirror changes back and forth.

    • I can write my QAbstractItemModel subclass such that the only state it keeps is cached results for expensive-to-derive fields and manually call beginInsertRows and endInsertRows when a new row arrives from the backend without warning.

    To rephrase what I said before, we're obviously not on the same page because the only way your advice, as stated, makes sense, is if I'm taking the former approach, which I don't consider acceptable.

    When I'm free tomorrow, I'll try to summarize my understanding of when to call which begin* and end* functions and you can tell me if I've missed or misunderstood any.



  • Things went bad and I only got about 3 or 4 hours of sleep. I'll summarize as soon as I've had a good night's sleep.



  • Sorry for the lack of response. Shortly after my last message, I got bombarded with obligations and it's taken me the last two weeks to get back to where I was before.

    I may or may not have a proper response in the next couple of days. The rest of the family is just getting over a debilitating flu and I've started to show faint symptoms.



  • That's often the result of meddling with model/view programming :)

    Hope you get better



  • "That's often the result of meddling with model/view programming :)" Indeed!

    I don't know if I can offer any real help, but I tell how I would begin. I would make a class inheriting QAbstractItemModel. It has the base model as a (pointer) member. The base model, if it behaves correctly, sends signals when changes are made to it. First I would create only one slot to which I would bind all the signals of the base model except "AboutTo" signals. This slot would simply emit modelAboutToBeReset and modelReset signals (in this order). Everything else would be implemented in the required public functions which are pure virtual in QAbstractItemModel (e.g. rowCount, index, data). Each of those functions would query the base data and return the required result dynamically. In this way every time something changes in the base model my new model would just inform the view that the model is reset. The view constructs itself automatically from scratch by calling the implemented required functions.

    In this way I would see if the logic is correct. After that it's easy to cache some data and create new slots which catch e.g. dataChanged and rowsInserted signals which come from the base model. They must be translated to meaningful events in my own model, for example dataChanged of the base model could mean inserting a row in the new model. The new slot which catches the dataChanged signal of the base model queries the data and decides for example that a game was added, therefore new rows are inserted under several different top level nodes. It would emit rowsAboutToBeInserted and rowsInserted with proper arguments for each added row. The view catches those signals and adds the rows as it wants by calling the implemented required functions. As you can see, the QModelIndex is in a big role in emitting the signals and giving the data to the view.

    There's nothing nice M-to-N in this, just a raw general way to create any kind of proxy. Also, because it reimplements QAbstractItemModel it's not as simple as one might wish. Especially I hate QModelIndex.



  • @Eeli-K

    First I would create only one slot to which I would bind all the signals of the base model except "AboutTo" signals. This slot would simply emit modelAboutToBeReset and modelReset signals (in this order). Everything else would be implemented in the required public functions which are pure virtual in QAbstractItemModel (e.g. rowCount, index, data).

    Thanks, but I already do that in the semi-placeholder implementation I used to take the screenshot. The part I'm uncertain about is how it all ties together. (eg. In which exact circumstances will and won't each signal get fired off? How does this all interact with lazy-loading models like QSqlQueryModel? etc. etc. etc.)

    I have a vague theoretical understanding of how it all works, but I don't think I'm uniquely perceptive and Qt's Model/View system is a bit infamous for developers having implemented things incorrectly when writing custom components.

    Anyway, once I'm no longer half-braindead from this cold, I'll try to compile my interpretation of how it all fits together into a document. Even if I can't get any "that's wrong" feedback for it, it should give me an idea where I could root out some ambiguities by writing automated tests.



  • @ssokolow said in Simplest way to write M-to-N (eg. aggregating) proxy models?:

    I have a vague theoretical understanding of how it all works, but I don't think I'm uniquely perceptive and Qt's Model/View system is a bit infamous for developers having implemented things incorrectly when writing custom components.

    No wonder. This is a learning experience for me, that's why I answered, I probably don't know more than you. The thread draw my attention exactly because the whole model/view system has felt frustrating and I would like to know more.

    The part I'm uncertain about is how it all ties together. (eg. In which exact circumstances will and won't each signal get fired off? How does this all interact with lazy-loading models like QSqlQueryModel? etc. etc. etc.)

    Good question. My understanding this far is something like this:

    First, the model has to know when the underlying data is changed. For example, a database connection triggers an automatic message when something is changed. Or the data is queried periodically, or whatever suits best. The model (=programmer) has to decide which change-related signal it should trigger to inform views. Is it just one row or a set of continous rows inserted? If you don't cache anything, just call beginInsertRows and endInsertRows. They call the corresponding signals, which are handled by the views. Is it several rows, one here, another there? call begin... and end... for each row. If it looks like there are so many changes at the same time that it's cheaper for a view to reconstruct the whole thing, beginResetModel and endResetModel instead. If the amount of rows doesn't change but same data inside of a row is changed, naturally you emit dataChanged (there's no begin... and end... for data change).

    If you cache data or have the data inside the model (e.g. in lists or maps), first call begin... , then change your data, then call end... As the doc says about beginInsertColumns:

    Note: This function emits the columnsAboutToBeInserted() signal which connected views (or proxies) must handle before the data is inserted. Otherwise, the views may end up in an invalid state.

    And about endInsertColumns:

    When reimplementing insertColumns() in a subclass, you must call this function after inserting data into the model's underlying data store.

    If you don't use or reimplement the standard data-changing API, you still call these functions when your underlying data is changed. But this triggers one question: should the view or other catcher of the begin... signal have access to the old state of the data? It would be impossible if you don't cache it and just receive a message about a change which happened earlier from the underlying data source.

    In the case of the QStandardItemModel we have append*, insert*, set* etc. functions for manipulating data. It's logical to think that whenever you use one of these standard functions, the corresponding signals are triggered automatically. E.g. the default implementation of insertRow would first emit rowsAboutToBeInserted, then store the given QStandardItem, then emit rowsInserted.



  • Just a status update. I haven't given up and I've started to work my way through the relevant bits of the Qt docs to build a list of questions which I feel the docs don't answer properly.

    Once I've finished that, I'll start putting together a plan for getting some answers.



  • In case anyone already knows the answers, here are the TODOs I've accumulated so far as I attempt to distill my way down the relevant bits of the Qt docs and fold in the appropriate cross-references.

    • How does API-based selection interact with "select by row/column, not by cell" restrictions on user selections?

    • Verify that list and table views essentially behave like tree views which hide the existence of child nodes (and verify my understanding of the effects of asking a list/table view to move, mutate, or delete nodes with children)

    • Test the actual user-visible result of not following the "common" convention that only items in the first column have children.

      • ...what if multiple cells in the same row have different children?
    • How do ranges (eg. selection APIs) interact with tree-structured data? What about data where children have variable column counts? (Selecting all items in a model suggests children are skipped)

    • How do Qt's various views and proxy models behave when the number of columns is not "independent of the parent"? (eg. What if the proxy model is set
      to expect a column number that not all subtrees have?)

    • What actual, implemented (not theoretical) purpose is served by having both "about to be" and "done" signals and slots for changes to the model?

      For something so heavy on examples, Qt's manual just seems to say "you must do this" without explaining why.

    • Under what circumstances might I want to subclass QItemSelectionModel?

    • How does the lazy-loading (canFetchMore and fetchMore) interact with the signals and proxy models?

    • Make a list/table mapping common operations to signals which should be emitted. (eg. When you sort the model, emit layout[AboutToBe]Changed)

      • The docs seem to be saying that, if I emit layout[AboutToBe]Changed rather than calling beginMoveRows/endMoveRows, I have to also call changePersistentIndex manually, which suggests that it should only be done that way if it really is a case like sorting where you're moving many rows in a non-contiguous and outwardly haphazard manner and don't want the overhead of a bunch of tiny operations.

      • Audit which situations do and don't require changePersistentIndex or changePersistentIndexList to be manually called.

    • How does overriding match for non-column data map to QSortFilterProxyModel? Does it at all?

    • How exactly do submit and revert fit into things?

    • Investigate cache-related flags in EndEditHint

    • Does QAbstractItemModel::span have a default implementation?


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.