Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QStringListModel Subclass Drag and Drop



  • Good Morning!

    I'm working on a portion of my application that uses multiple QListViews to manage some data stored in .json files. Basically the data is stored hierarchically, with three layers. One QListView is used to show available components for each layer at a time (say layer 1, 2, or 3), and the other three are used to show the contents under an individual hierarchy, for example when a single level 1 hierarchy is selected. This part isn't as important, just some background.

    So I subclassed QStringListModel to add some json parsing functionality to it. I have rowCount() and data() both implemented, and they're working great, everything I need from the jsons is filling the views perfectly, and when I select a single item from a view I'm able to show some other information I need from the json. The next step is enabling drag+drop between the views, which I'm struggling with.

    I've made a test program that uses drag+drop just fine, using just QStringListModel. Since I'm subclassing that class, I assumed setting it up the same way would enable dragging+dropping just fine. What I'm seeing is that I can drag and drop, but when I drop, all that is appended to the QStringList is a null string.

    I feel like I'm maybe missing a virtual function that needs to be overridden in my subclass? I don't understand what function gets called when you drop something into a QListView to handle the transfer of the data over - I need access to that so I can populate some other stuff based on it from the json files.

    I didn't post any code since I feel like this is a broader question about subclassing properly - feel free to ask for parts of it if you think it'll help!

    Thanks!


  • Lifetime Qt Champion

    Hi,

    Your code is indeed needed. Otherwise it is just guesswork. If an empty string is inserted you might have an issue the way your create the data to send or in the way you manage your drop.



  • Yea fair enough. I guess I was looking for some broader guidance on how to subclass a model, but you're right. I'm new to the Model/View system, so I'm definitely still wrapping my brain around it.

    So I'm subclassing QStringListModel like so:
    jsonListModel.h:

    #include <QStringListModel>
    #include "json/single_include/nlohmann/json.hpp"
    
    using json = nlohmann::json;
    
    class JsonListModel : public QStringListModel
    {
        Q_OBJECT
    
    public:
        explicit JsonListModel(std::string newJsonIdentifier, QObject *parent = nullptr);
    
        std::string jsonIdentifier;
        std::string selectedCycle;
        std::string selectedStage;
        std::string selectedSegment;
        int modeFlag = 1; //hard code this to only read from the segments.json
        int rows = 0; //hard code to zero in case we look too early
        void updateJsonStringList();
        json readFromJson(std::string identifier);
        //std::vector<std::string> jsonStringList;
        QStringList jsonStringList;
        json newJson;
    
        int rowCount(const QModelIndex &parent = QModelIndex()) const override;
        QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
        bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) const;
        QStringList getJsonSubdata(std::string jsonIndex);
    };
    

    I pared this down a bunch to remove unrelated stuff, but here's the source, jsonListModel.cpp:

    #include <QDebug>
    #include <iostream>
    #include <fstream>
    #include <QStringList>
    #include <experimental/filesystem>
    
    JsonListModel::JsonListModel(std::string newJsonIdentifier, QObject *parent) : QStringListModel(parent)
    {
        jsonIdentifier = newJsonIdentifier;
        updateJsonStringList();
        //this populates the stringlist as soon as the constructor is called
    }
    
    int JsonListModel::rowCount(const QModelIndex & /*parent*/) const
    {
        return rows;
    }
    
    QVariant JsonListModel::data(const QModelIndex &index, int role) const 
    {
        int row = index.row();
        if(role == Qt::DisplayRole)
        {
            return jsonStringList.at(row);
        }
        return QVariant();
    }
    
    void JsonListModel::updateJsonStringList()
    {   
       //A bunch of stuff happens here to fill up jsonStringList with data from the json files
        setStringList(jsonStringList);
    }
    

    Then in the header of the widget that holds the jsonStringListModels:

    //QListViews
        QListView* jsonList;
        QListView* cycleList;
        QListView* stageList;
        QListView* segmentList;
    
        //SegmentTableModels
        JsonListModel* jsonCycleListModel;
        JsonListModel* jsonStageListModel;
        JsonListModel* jsonSegmentListModel;
    
        JsonListModel* cycleListModel;
        JsonListModel* stageListModel;
        JsonListModel* segmentListModel;
    

    And the source of that widget:

    //Models
        jsonCycleListModel = new JsonListModel("jsonCycles");
        jsonStageListModel = new JsonListModel("jsonStages");
        jsonSegmentListModel = new JsonListModel("jsonSegments");
    
        cycleListModel = new JsonListModel("cycles");
        stageListModel = new JsonListModel("stages");
        segmentListModel = new JsonListModel("segments");
    
    //QListViews
        jsonList = new QListView(this);
        jsonList->setDragEnabled(true);
        jsonList->setAcceptDrops(true);
        jsonList->setDropIndicatorShown(true);
        jsonList->setModel(jsonCycleListModel);
        jsonList->setStyleSheet(
            "QListView { font-size: 10pt; font-weight: bold; }"
            "QListView::item { background-color: #E74C3C; padding: 10%;"
            "border: 1px solid #C0392B; }"
            "QListView::item::hover { background-color: #C0392B }");
    

    I have four QListViews in here, but they're all basically the same, so I don't see the need in posting all of them.

    So now that I've been looking at this a little closer, I suspect the issue is with my call of setStringList(jsonStringList); - it feels like I should be appending to the model's stringList? I guess I'm not clear if that call attaches my stringlist to the model, or if it just copies the contents over. The other possible issue is what I mentioned in the first post, that I'm missing an override of a virtual function somewhere. I've been doing a bunch of document reading, and there's a bunch of stuff about MIME data, but my understanding was that that would be taken care of by the QStringListModel already.



  • @Kelenyche said in QStringListModel Subclass Drag and Drop:

    setStringList(jsonStringList)

    You didn't show us that method.

    My suggestion would be: don't remplement anything. Just add some code to populate the model but let the base class do the dirty work for you. Delete JsonListModel::rowCount and JsonListModel::data altogether

    P.S.

    • Qt has json reading and writing classes so a bit odd that you used an external library for it


  • void QStringListModel::setStringList(const QStringList &strings) is already a member of QStringListModel, I didn't do anything to it.
    https://doc.qt.io/qt-5/qstringlistmodel.html#setStringList

    You might be right about my reimplementation of data and rowCount - I thought I read somewhere that if you subclass, those two are definitely required to be reimplemented, but I might have confused myself reading too many different things. It makes sense, since I'm using setStringList. I'll give that a go.

    PS
    Thanks for the info on jsons! I didn't know Qt had json stuff - I've used this library before so I grabbed it before I thought to check qt for it. :)



  • Yep, removing my reimplementation of rowCount and data fixed the problem. Like I said, I think I saw somewhere that any subclass needed those functions, but it must have been talking about QAbstractListModel, and at that point I had been reading a LOT of documentation and got them mixed up. :) Thank you for the suggestion!


Log in to reply