QML: How to move items within a grid

14,746

Solution 1

You'd better use a GridView Item instead of your Grid approach.

This way you can use it's currentIndex property to choose which item to move like this:

import QtQuick 1.1

Rectangle {
    id: main;
    width: 500; height: 500;
    color: "darkgreen";

    property int emptyBlock: 16;

    GridView {
        id: grid16;
        x: 5; y: 5;
        width: 490; height: 490;

        model: gridModel

        delegate: Component{
          Rectangle {
            width: 118; height: 118; color: "darkblue";
            Text {
              anchors.centerIn: parent
              font.pixelSize: 20
              text: value
            }
          }
        }
    }

    ListModel {
      id: gridModel
      ListElement {value: 1}
      ListElement {value: 2}
      ListElement {value: 3}
      ListElement {value: 4}
    }

    Keys.onRightPressed: {
      gridModel.move(grid16.currentIndex, grid16.currentIndex+1, 1)
    }

    Keys.onLeftPressed: {
      gridModel.move(grid16.currentIndex, grid16.currentIndex-1, 1)
    }

    focus: true;
}

Solution 2

Grids give you no way to manipulate the position of the contained items directly. Instead their position is directly derived from the physically order of the child items of the grid. There is no easy way to to manipulate child items in QML dynamically, so I think you should abandon the Grid item and specify the position of the child items explicitly with the x and y properties. Applied to your code this could look like:

Rectangle {
    id: main;
    width: 500; height: 500;
    color: "darkgreen";

    Item {
        x: 5; y: 5;
        width: 490; height: 490;

        Repeater {
            id: pieces
            model: 1;
            Rectangle {
                property int column: 0
                property int row: 0
                x: column * 123
                y: row * 123
                width: 118; height: 118; color: "darkblue";
            }
        }
    }

    Keys.onRightPressed: pressRight();

    function pressRight() {
        console.log("Left key pressed");
        pieces.itemAt(0).column++
    }

    focus: true;
}

Update 1:

Grids (in combination with a Repeater) can be used to visualize models, e.g., a XmlListModel item or an QAbstractItemModel descendent.

With move property it's easy to react to layout changes in the model (if an entry is removed/added) in an animated way. Still, the items in the Grid are laid out strictly in the order of the entries of the model.

So if you want have manual control over the position of your items, even in cellular layout, use of a Grid is not advisable.

Solution 3

You can change the number of items before the item you want to move to change its position:

import QtQuick 1.1

Rectangle {
    id: main;
    width: 500; height: 500;
    color: "darkgreen";

    property int emptyBlock: 16;

    property int posX: 0;
    property int posY: 0;

    Grid {
        id: grid;
        x: 5; y: 5;
        width: 490; height: 490;
        rows: 4; columns: 4; spacing: 5;

        Repeater {
            id: beforeTheItem
            model: main.posX + parent.columns * main.posY
            Rectangle {
                width: 118; height: 118; color: "transparent";
            }
        }

        Rectangle {
            id:theItem
            width: 118; height: 118; color: "darkblue";
        }
    }

    Keys.onPressed: {
        // To avoid flickering, the item is hidden before the change
        // and made visible again after
        theItem.visible = false;
        switch(event.key) {
        case Qt.Key_Left: if(posX > 0) posX--;
            break;
        case Qt.Key_Right: if(posX < grid.columns - 1) posX++;
            break;
        case Qt.Key_Up: if(posY > 0) posY--;
            break;
        case Qt.Key_Down: if(posY < grid.rows - 1) posY++;
            break;
        }
        theItem.visible = true;
    }

    focus: true;
}

Solution 4

Now, by using Qt 5.1 or above and GridLayout you can move your items without hassle:

import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1

Window
{
    visible: true

    MainForm
    {
        GridLayout {
            id: gridLayout
            columns: 3

            property int oneRow: 0
            property int oneCol: 0

            Text { id: one; Layout.row :grid.oneRow; Layout.column: grid.oneCol; text: "My"; font.bold: true; }

            Text { text: "name"; color: "red" }
            Text { text: "is"; font.underline: true }
            Text { text: "not"; font.pixelSize: 20 }
            Text { text: "Ravan"; font.strikeout: true }
        }

        Component.onCompleted:
        {
            gridLayout.oneRow = 1
            gridLayout.oneCol = 2
        }
    }
}

Solution 5

The GridView is a very confusing monster. It just populates one row from a given model, which leads to confusion since it is called GRID. But it can still be used as a fixed size grid, as I show in the example below. A single square can be moved with arrow keys on a 4x4 sized grid.

    GridView {
        id: grid16;
        anchors.fill: parent
        cellWidth:  parent.width  / 2
        cellHeight: parent.height / 2

        model: gridModel

        delegate:
          Rectangle {
            Component.onCompleted: if( index >= 1 ) visible = false
            width: grid16.cellWidth ; height: grid16.cellHeight ; color: "yellow";
            Text {
              anchors.centerIn: parent
              font.pixelSize: 20
              text: value
            }
        }

        move: Transition {
            NumberAnimation { properties: "x,y"; duration: 1000 }
        }
    }

    ListModel {
      id: gridModel
      ListElement {value: 1}
      //Necessary, otherwise the grid will have the dimension 1x1
      ListElement {value: 2}
      ListElement {value: 3}
      ListElement {value: 4}
    }

    Keys.onRightPressed: { gridModel.move(grid16.currentIndex, grid16.currentIndex+1, 1) }
    Keys.onLeftPressed:  { gridModel.move(grid16.currentIndex, grid16.currentIndex-1, 1) }
    Keys.onUpPressed:    { gridModel.move(grid16.currentIndex, grid16.currentIndex-2, 1) }
    Keys.onDownPressed:  { gridModel.move(grid16.currentIndex, grid16.currentIndex+2, 1) }

    focus: true;
}
Share:
14,746
Xolve
Author by

Xolve

Updated on June 04, 2022

Comments

  • Xolve
    Xolve about 2 years

    I have a 4x4 grid and I want to associate arrow key presses with the movement of items within the grid. How does one do that?

    Here is a sample QML:

    import QtQuick 1.1
    
    Rectangle {
        id: main;
        width: 500; height: 500;
        color: "darkgreen";
    
        property int emptyBlock: 16;
    
        Grid {
            id: grid16;
            x: 5; y: 5;
            width: 490; height: 490;
            rows: 4; columns: 4; spacing: 5;
    
            Repeater {
                model: 1;
                Rectangle {
                    width: 118; height: 118; color: "darkblue";
                }
            }
        }
    
        Keys.onRightPressed: pressRight();
    
        function pressRight() {
            console.log("Left key pressed");
        }
    
        focus: true;
    }
    

    Update 1: Thanks to sebasgo and alexisdm for the answers. If moving within a grid is not that easy why we have the move transition property [http://qt-project.org/doc/qt-4.8/qml-grid.html#move-prop]