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

How to catch c++ model signals in qml&



  • I'm a newbie in qml and I'm trying to make barley-break game in qml. In this game you have a 4x4 field with 15 squares which have nums and 1 empty square. If you click on a square with num and it is positioned behind empty square it will move to an empty place. Game is build according to model-view model. I made model in c++ code and interface in qml. Now I'm trying to make animations and faced a problem with catching custom c++ model signals in delegate. I wanted my "move" function to emit custon signal with indexes of squares which are making changes in theirs positions (empry and not empty). But my delegate item doesn't see the signal. How I could fix this problem? Sorry if it is a really stupid question.
    qml interface code:

    import QtQuick 2.12
    import Game 1.0
    GridView {
        id:root
        model:GameBoardModel{
        }
    
        cellHeight: height/root.model.dimension
        cellWidth: width/root.model.dimension
    
    
        delegate: Item{
            id:_backgroundDelegate
            visible:root.model.hiddenElementValue !==display
            width:root.cellWidth
            height:root.cellHeight
            //root.model.onPlacementChanged - this doesn't work
            Tile{
    
                displayText: display
                anchors.fill: _backgroundDelegate
                anchors.margins: 5
    
    
                MouseArea{
                   anchors.fill:parent
    
                   onClicked:{
                       if (root.model.move(index)){}
                   }
                }
            }
        }
    }
    
    

    c++ model header code:

    #ifndef GAMEBOARD_H
    #define GAMEBOARD_H
    #include <vector>
    #include <QAbstractListModel>
    class GameBoard:public QAbstractListModel
    {
        Q_OBJECT
        Q_PROPERTY(int dimension READ dimension CONSTANT)
        Q_PROPERTY(int hiddenElementValue READ boardSize CONSTANT)
    
    public:
        using Position = std::pair<size_t,size_t>;
    
        static constexpr size_t defaultDimension {4};
    
        GameBoard(const size_t boardDimension = defaultDimension, QObject *parent = nullptr);
    
        struct Tile{
            size_t value{};
            Tile& operator=(const size_t newValue){
                value = newValue;
                return *this;
            }
            bool operator==(const size_t other){
                return value == other;
            }
        };
    
        Q_INVOKABLE bool move(int index);
        Q_INVOKABLE int row(int value);
        Q_INVOKABLE int col(int value);
        Q_INVOKABLE void emitDataChanged();
        int rowCount(const QModelIndex& parent = QModelIndex{})const override;
        size_t dimension() const;
    
        QVariant data(const QModelIndex& index,int role = Qt::DisplayRole)const override;
        size_t boardSize() const;
    signals:
        void placementChanged(int index1, int index2);
    
    
    
    private:
        Position getRowCol(size_t index)const;
        bool isPositionValid(const size_t position)const;
        bool isBoardValid();
        void shuffle();
        std::vector<Tile> m_rawBoard;
        size_t m_dimension;
        size_t m_boardSize;
    };
    
    #endif // GAMEBOARD_H
    
    

    c++ model source code:

    #include "gameboard.h"
    #include <algorithm>
    #include <random>
    #include <QDebug>
    #include <windows.h>
    namespace{
    bool isAdjacent(const GameBoard::Position f,const GameBoard::Position s){
        if (f == s){
            return false;
        }
        const auto calcDistance = [](const size_t pos1, size_t pos2){
            int distance = pos1;
            distance-=pos2;
            return std::abs(distance);
        };
        if ((f.first == s.first && calcDistance(f.second,s.second) == 1)
                || (f.second == s.second && calcDistance(f.first,s.first) == 1)){
                return true;
            }
        return false;
    }
    }
    
    
    GameBoard::GameBoard(const size_t boardDimension , QObject *parent):
        QAbstractListModel{parent},
        m_dimension{boardDimension},
        m_boardSize{boardDimension*boardDimension}
    {
        m_rawBoard.resize(m_boardSize);
        std::iota(std::begin(m_rawBoard),std::end(m_rawBoard),1);
        shuffle();
    }
    
    bool GameBoard::move(int index)
    {
        qDebug()<<index;
        if (!isPositionValid(index)){
            return false;
        }
        Position ElementPosition = {getRowCol(index)};
    
        auto hiddenElementIterator = std::find(m_rawBoard.begin(),m_rawBoard.end(),m_boardSize);
        Q_ASSERT(hiddenElementIterator != m_rawBoard.end());
        Position hiddenElementPosition { getRowCol(std::distance(m_rawBoard.begin(),hiddenElementIterator))};
        if (!isAdjacent(ElementPosition,hiddenElementPosition)){
            return false;
        }
        std::iter_swap(hiddenElementIterator,m_rawBoard.begin()+index);
        emit placementChanged(hiddenElementIterator-m_rawBoard.begin(),index);
        //emit dataChanged(createIndex(0,0),createIndex(m_boardSize,0));s
        return true;
    
    }
    
    int GameBoard::row(int value)
    {
        int index = 0;
        for (size_t i = 0; i < m_boardSize;i++){
            if(m_rawBoard[i] == value){
                index =i;
            }
        }
        return getRowCol(index).first;
    }
    
    int GameBoard::col(int value)
    {
        int index = 0;
        for (size_t i = 0; i < m_boardSize;i++){
            if(m_rawBoard[i] == value){
                index =i;
            }
        }
        return getRowCol(index).second;
    }
    
    void GameBoard::emitDataChanged()
    {
        emit dataChanged(createIndex(0,0),createIndex(m_boardSize,0));
    }
    
    int GameBoard::rowCount(const QModelIndex &parent) const
    {
        Q_UNUSED(parent);
        return m_rawBoard.size();
    }
    
    QVariant GameBoard::data(const QModelIndex &index, int role) const
    {
        if (!index.isValid() || role != Qt::DisplayRole){
            return {};
        }
        const int rowIndex{index.row()};
        if (!isPositionValid(rowIndex)){
            return {};
        }
        return QVariant::fromValue(m_rawBoard[rowIndex].value);
    }
    
    bool GameBoard::isPositionValid(const size_t position) const
    {
    
        return position < m_boardSize;
    }
    
    bool GameBoard::isBoardValid()
    {
        size_t  n = 0, sum = 0, rowEmpty = 0;
        for(size_t i = 0;i<m_rawBoard.size();i++){
            if (m_rawBoard[i].value == m_rawBoard.size()){
                rowEmpty = i/m_dimension+1;
            }
            for(size_t j = i+1;j<m_rawBoard.size();j++){
                if (m_rawBoard[i].value<m_rawBoard[j].value){
                    n++;
                }
            }
            sum+=n;
            n = 0;
        }
        return (rowEmpty + sum)%2 == 0;
    }
    
    
    void GameBoard::shuffle(){
        static auto seed = std::chrono::system_clock::now().time_since_epoch().count();
        static std::mt19937 generator(seed);
        do{
            std::shuffle(m_rawBoard.begin(),m_rawBoard.end(),generator);
        }while(!isBoardValid());
    }
    
    size_t GameBoard::boardSize() const
    {
        return m_boardSize;
    }
    
    GameBoard::Position GameBoard::getRowCol(size_t index) const
    {
        size_t row = index / m_dimension;
        size_t column = index % m_dimension;
        return std::make_pair(row,column);
    }
    
    size_t GameBoard::dimension() const
    {
        return m_dimension;
    }
    
    

    Some functions (like row() and col()) were added just for experiments.



  • hi,
    @T_e_d_d_y said in How to catch c++ model signals in qml&:

    model:GameBoardModel{
    }

    you can try this :

     model:GameBoardModel{
     id:myGameModel
         }
    
    Connections{
      target : myGameModel
     function onPlacementChanged() { console.log(index1  +" " + index2)}
    }
    


  • hi,
    @T_e_d_d_y said in How to catch c++ model signals in qml&:

    model:GameBoardModel{
    }

    you can try this :

     model:GameBoardModel{
     id:myGameModel
         }
    
    Connections{
      target : myGameModel
     function onPlacementChanged() { console.log(index1  +" " + index2)}
    }
    


  • @LeLev Thanks! It really works! I can't believe that it was so easy



  • @T_e_d_d_y nice,

    Actually you don't even need the Connections object

     model:GameBoardModel{
     onPlacementChanged : {}
         }