diff options
author | Richard Moe Gustavsen <richard.gustavsen@qt.io> | 2022-09-27 14:03:18 +0200 |
---|---|---|
committer | Richard Moe Gustavsen <richard.gustavsen@qt.io> | 2022-11-09 12:15:07 +0100 |
commit | 8733b01ce3e8eadbbe62b9e9a264d4ce699a6be8 (patch) | |
tree | 442158b24d6a683646239a197a0259028df10a0f /tests/manual | |
parent | cb59815631a216b077a27e44b6886f7474b6496b (diff) |
QQuickTableView: implement support for letting the user resize rows and columns
This patch will add support to TableView for resizing rows and
columns by dragging between the cells.
To achieve this, a custom pointer handler (QQuickTableViewResizeHandler)
is implemented. This handler can detect if the pointer is hovering
between the cells, and if the user starts a drag. This information is
used to call out to the new setColumnWidth()/setRowHeight() API for
adjusting the row and column sizes while the user is dragging.
The pointer handler is careful to make sure that you can only start to
resize by dragging _between_ the cells. If the drag starts elsewhere, the
solution will fall back to normal contentItem dragging/flicking instead.
Resizing is off by default. The user can enable it by setting the
resizableRows/resizableColumns properties. In addition, an API that
lets you query the state of the resizing has been added.
[ChangeLog][Quick][TableView] Added resizableColumns and
resizableRows properties to enable resizing by dragging between cells.
Change-Id: I05d4170f30b8c6461a5877c2b831a1ab044d2b5b
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
Diffstat (limited to 'tests/manual')
-rw-r--r-- | tests/manual/tableview/abstracttablemodel/main.cpp | 34 | ||||
-rw-r--r-- | tests/manual/tableview/abstracttablemodel/main.qml | 567 | ||||
-rw-r--r-- | tests/manual/tableview/storagemodel/main.qml | 9 |
3 files changed, 450 insertions, 160 deletions
diff --git a/tests/manual/tableview/abstracttablemodel/main.cpp b/tests/manual/tableview/abstracttablemodel/main.cpp index d3cf2bed78..dadd992ef7 100644 --- a/tests/manual/tableview/abstracttablemodel/main.cpp +++ b/tests/manual/tableview/abstracttablemodel/main.cpp @@ -47,7 +47,7 @@ public: for (int x = 0; x < m_cols; ++x) { m_modelData[x] = QVector<CellData>(m_rows); for (int y = 0; y < m_rows; ++y) - m_modelData[x][y] = qMakePair(QStringLiteral("white"), false); + m_modelData[x][y] = qMakePair(QString("%1, %2").arg(x).arg(y), false); } } @@ -71,15 +71,25 @@ public: bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override { - if (role != Qt::CheckStateRole) - return false; - - bool checked = value.toBool(); - auto &cellData = m_modelData[index.column()][index.row()]; - if (checked == cellData.second) - return false; - - cellData.second = checked; + switch (role) { + case Qt::DisplayRole: { + QString text = value.toString(); + auto &cellData = m_modelData[index.column()][index.row()]; + if (text == cellData.first) + return false; + cellData.first = text; + break; } + case Qt::CheckStateRole: { + bool checked = value.toBool(); + auto &cellData = m_modelData[index.column()][index.row()]; + if (checked == cellData.second) + return false; + + cellData.second = checked; + break; } + default: + return QAbstractTableModel::setData(index, value, role); + } emit dataChanged(index, index, {role}); return true; @@ -124,7 +134,7 @@ public: for (int y = 0; y < count; ++y) { for (int x = 0; x < m_cols; ++x) - m_modelData[x].insert(row, qMakePair(QStringLiteral("lightgreen"), false)); + m_modelData[x].insert(row, qMakePair(QStringLiteral("added"), false)); } endInsertRows(); @@ -166,7 +176,7 @@ public: const int c = column + x; m_modelData.insert(c, QVector<CellData>(m_rows)); for (int y = 0; y < m_rows; ++y) - m_modelData[c][y] = qMakePair(QStringLiteral("lightblue"), false); + m_modelData[c][y] = qMakePair(QStringLiteral("added"), false); } endInsertColumns(); diff --git a/tests/manual/tableview/abstracttablemodel/main.qml b/tests/manual/tableview/abstracttablemodel/main.qml index 14dd590025..36f27afda3 100644 --- a/tests/manual/tableview/abstracttablemodel/main.qml +++ b/tests/manual/tableview/abstracttablemodel/main.qml @@ -6,207 +6,486 @@ import QtQuick.Window import QtQml.Models import TestTableModel import QtQuick.Controls +import QtQuick.Layouts import Qt.labs.qmlmodels ApplicationWindow { id: window - width: 640 - height: 480 + width: 1000 + height: 800 visible: true - property int selectedX: -1 - property int selectedY: -1 + readonly property var currentIndex: tableView.selectionModel.currentIndex + readonly property point positionOffset: useOffset.checked ? Qt.point(10, 10) : Qt.point(0, 0) + readonly property rect positionSubRect: useSubRect.checked ? Qt.rect(10, 10, 10, 10) : Qt.rect(0, 0, 0, 0) + property int hiddenColumn: -1 - Item { - anchors.fill: parent - - Column { - id: menu - x: 2 - y: 2 - - Row { - spacing: 1 - Button { - text: "Add row" - onClicked: tableView.model.insertRows(selectedY, 1) - } - Button { - text: "Remove row" - onClicked: tableView.model.removeRows(selectedY, 1) - } - Button { - text: "Add column" - onClicked: tableView.model.insertColumns(selectedX, 1) - } - Button { - text: "Remove column" - onClicked: tableView.model.removeColumns(selectedX, 1) + ScrollView { + id: menu + height: window.height + width: 230 + + readonly property real menuMargin: 10 + + ColumnLayout { + GroupBox { + Layout.minimumWidth: menu.availableWidth - (menu.menuMargin * 2) + Layout.rightMargin: menu.menuMargin + Layout.leftMargin: menu.menuMargin + Layout.topMargin: menu.menuMargin + ColumnLayout { + CheckBox { + text: "Use Syncview" + checkable: true + checked: true + onCheckedChanged: { + if (checked) { + leftHeader.syncView = tableView + topHeader.syncView = tableView + } else { + leftHeader.syncView = null + topHeader.syncView = null + } + } + } + + CheckBox { + id: flickingMode + checkable: true + checked: true + text: "Enable flicking" + } + + CheckBox { + id: indexNavigation + checkable: true + checked: true + text: "Enable navigation" + } + + CheckBox { + id: resizableRowsEnabled + checkable: true + checked: false + text: "Resizable rows" + } + + CheckBox { + id: resizableColumnsEnabled + checkable: true + checked: false + text: "Resizable columns" + } + + CheckBox { + id: enableAnimation + checkable: true + checked: true + text: "Enable animation" + } + + CheckBox { + id: drawText + checkable: true + checked: true + text: "Draw text" + } + + CheckBox { + id: useRandomColor + checkable: true + checked: false + text: "Use colors" + } + + CheckBox { + id: useLargeCells + checkable: true + checked: false + text: "Use large cells" + onCheckedChanged: Qt.callLater(tableView.forceLayout) + } + + CheckBox { + id: useSubRect + checkable: true + checked: false + text: "Use subRect" + } + + CheckBox { + id: useOffset + checkable: true + checked: false + text: "Use offset" + } + + CheckBox { + id: highlightCurrentRow + checkable: true + checked: false + text: "Highlight row/col" + } } - SpinBox { - id: spaceSpinBox - from: -100 - to: 100 - value: 0 + } + + GroupBox { + Layout.minimumWidth: menu.availableWidth - (menu.menuMargin * 2) + Layout.rightMargin: menu.menuMargin + Layout.leftMargin: menu.menuMargin + GridLayout { + columns: 2 + Label { text: "Model size:" } + SpinBox { + id: modelSize + from: 0 + to: 1000 + value: 200 + editable: true + } + Label { text: "Spacing:" } + SpinBox { + id: spaceSpinBox + from: -100 + to: 100 + value: 1 + editable: true + } + Label { text: "Margins:" } + SpinBox { + id: marginsSpinBox + from: 0 + to: 100 + value: 1 + editable: true + } } } - Row { - spacing: 1 - Button { - text: "fast-flick<br>center table" - onClicked: { - tableView.contentX += tableView.width * 1.2 + GroupBox { + Layout.minimumWidth: menu.availableWidth - (menu.menuMargin * 2) + Layout.rightMargin: menu.menuMargin + Layout.leftMargin: menu.menuMargin + RowLayout { + id: positionRow + Button { + text: "<<" + onClicked: { + tableView.positionViewAtRow(0, Qt.AlignTop, -tableView.topMargin) + tableView.positionViewAtColumn(0, Qt.AlignLeft, -tableView.leftMargin) + } + } + + Button { + text: ">>" + onClicked: { + tableView.positionViewAtRow(tableView.rows - 1, Qt.AlignBottom, tableView.bottomMargin) + tableView.positionViewAtColumn(tableView.columns - 1, Qt.AlignRight, tableView.rightMargin) + } } } - Button { - text: "flick to end<br>center table" - onClicked: { - tableView.contentX = tableView.contentWidth - tableView.width + } + + GroupBox { + Layout.minimumWidth: menu.availableWidth - (menu.menuMargin * 2) + Layout.rightMargin: menu.menuMargin + Layout.leftMargin: menu.menuMargin + ColumnLayout { + Button { + text: "Add row" + enabled: currentIndex.valid + onClicked: tableView.model.insertRows(currentIndex.row, 1) + } + + Button { + text: "Remove row" + enabled: currentIndex.valid + onClicked: tableView.model.removeRows(currentIndex.row, 1) + } + + Button { + text: "Add column" + enabled: currentIndex.valid + onClicked: tableView.model.insertColumns(currentIndex.column, 1) + } + + Button { + text: "Remove column" + enabled: currentIndex.valid + onClicked: tableView.model.removeColumns(currentIndex.column, 1) + } + + Button { + text: "Hide column" + enabled: currentIndex.valid + onClicked: { + hiddenColumn = currentIndex.column + tableView.forceLayout() + } } } - Button { - text: "fast-flick<br>headers" - onClicked: { - leftHeader.contentY += 1000 - topHeader.contentX += 1000 + } + + GroupBox { + Layout.minimumWidth: menu.availableWidth - (menu.menuMargin * 2) + Layout.rightMargin: menu.menuMargin + Layout.leftMargin: menu.menuMargin + ColumnLayout { + RadioButton { + id: selectionDisabled + text: "SelectionDisabled" + } + RadioButton { + id: selectCells + text: "SelectCells" + checked: true + } + RadioButton { + id: selectRows + text: "SelectRows" + } + RadioButton { + id: selectColumns + text: "SelectColumns" + } + Label { + width: parent.width + font.pixelSize: 10 + text: "(SelectionMode: " + (tableView.interactive ? "PressAndHold)" : "Drag)") } } - Button { - text: "set/unset<br>master bindings" - onClicked: { - leftHeader.syncView = leftHeader.syncView ? null : tableView - topHeader.syncView = topHeader.syncView ? null : tableView + } + + GroupBox { + Layout.minimumWidth: menu.availableWidth - (menu.menuMargin * 2) + Layout.rightMargin: menu.menuMargin + Layout.leftMargin: menu.menuMargin + ColumnLayout { + Button { + text: "Current to top-left" + enabled: currentIndex.valid + onClicked: { + let cell = Qt.point(currentIndex.column, currentIndex.row) + tableView.positionViewAtCell(cell, Qt.AlignTop | Qt.AlignLeft, positionOffset, positionSubRect) + } + } + + Button { + text: "Current to center" + enabled: currentIndex.valid + onClicked: { + let cell = Qt.point(currentIndex.column, currentIndex.row) + tableView.positionViewAtCell(cell, Qt.AlignCenter, positionOffset, positionSubRect) + } + } + + Button { + text: "Current to bottom-right" + enabled: currentIndex.valid + onClicked: { + let cell = Qt.point(currentIndex.column, currentIndex.row) + tableView.positionViewAtCell(cell, Qt.AlignBottom | Qt.AlignRight, positionOffset, positionSubRect) + } + } + + Button { + text: "Current to Visible" + enabled: currentIndex.valid + onClicked: { + let cell = Qt.point(currentIndex.column, currentIndex.row) + tableView.positionViewAtCell(cell, TableView.Visible, positionOffset, positionSubRect) + } + } + + Button { + text: "Current to Contain" + enabled: currentIndex.valid + onClicked: { + let cell = Qt.point(currentIndex.column, currentIndex.row) + tableView.positionViewAtCell(cell, TableView.Contain, positionOffset, positionSubRect) + } } } - Button { - id: flickingMode - checkable: true - text: checked ? "Flickable" : "Scrollable" + } + + GroupBox { + Layout.minimumWidth: menu.availableWidth - (menu.menuMargin * 2) + Layout.rightMargin: menu.menuMargin + Layout.leftMargin: menu.menuMargin + Layout.bottomMargin: menu.menuMargin + ColumnLayout { + Button { + text: "Fast-flick table" + onClicked: { + tableView.contentX += tableView.width * 1.2 + } + } + + Button { + text: "Fast-flick headers" + onClicked: { + topHeader.contentX += tableView.width * 1.2 + leftHeader.contentY += tableView.height * 1.2 + } + } } } - Text { - text: "Selected: x:" + selectedX + ", y:" + selectedY + + Item { + Layout.fillHeight: true } } + } - TableView { - id: topHeader - objectName: "topHeader" - anchors.left: tableView.left - width: tableView.width - anchors.top: menu.bottom - height: 30 - clip: true - ScrollBar.horizontal: ScrollBar {} + TableView { + id: topHeader + objectName: "topHeader" + anchors.left: centerScrollView.left + anchors.right: centerScrollView.right + anchors.top: menu.top + height: 30 + clip: true - model: TestTableModel { - rowCount: 1 - columnCount: 200 - } + model: TestTableModel { + rowCount: 1 + columnCount: modelSize.value + } - delegate: Rectangle { - implicitHeight: topHeader.height - implicitWidth: 20 - color: "lightgray" - Text { text: column } + delegate: Rectangle { + implicitHeight: topHeader.height + implicitWidth: 20 + color: "lightgray" + Text { + anchors.centerIn: parent + visible: drawText.checked + text: column + font.pointSize: 8 } + } - columnSpacing: 1 - rowSpacing: 1 + columnSpacing: 1 + rowSpacing: 1 - syncView: tableView - syncDirection: Qt.Horizontal - } + syncView: tableView + syncDirection: Qt.Horizontal + resizableColumns: resizableColumnsEnabled.checked + } - TableView { - id: leftHeader - objectName: "leftHeader" - anchors.left: parent.left - anchors.top: tableView.top - height: tableView.height - width: 30 - clip: true - ScrollBar.vertical: ScrollBar {} + TableView { + id: leftHeader + objectName: "leftHeader" + anchors.left: menu.right + anchors.top: centerScrollView.top + anchors.bottom: centerScrollView.bottom + width: 30 + clip: true - model: TestTableModel { - rowCount: 200 - columnCount: 1 - } + model: TestTableModel { + rowCount: modelSize.value + columnCount: 1 + } - delegate: Rectangle { - implicitHeight: 50 - implicitWidth: leftHeader.width - color: "lightgray" - Text { text: row } + delegate: Rectangle { + implicitHeight: 50 + implicitWidth: leftHeader.width + color: "lightgray" + Text { + anchors.centerIn: parent + visible: drawText.checked + text: row + font.pointSize: 8 } + } - columnSpacing: 1 - rowSpacing: 1 + columnSpacing: 1 + rowSpacing: 1 - syncView: tableView - syncDirection: Qt.Vertical - } + syncView: tableView + syncDirection: Qt.Vertical + resizableRows: resizableRowsEnabled.checked + } + + Item { + id: centerScrollView + anchors.left: leftHeader.right + anchors.right: parent.right + anchors.top: topHeader.bottom + anchors.bottom: parent.bottom + anchors.rightMargin: 10 + anchors.bottomMargin: 10 TableView { id: tableView + anchors.fill: parent objectName: "tableview" - anchors.left: leftHeader.right - anchors.right: parent.right - anchors.top: topHeader.bottom - anchors.bottom: parent.bottom - width: 200 clip: true delegate: tableViewDelegate columnSpacing: spaceSpinBox.value rowSpacing: spaceSpinBox.value interactive: flickingMode.checked + keyNavigationEnabled: indexNavigation.checked + pointerNavigationEnabled: indexNavigation.checked + resizableRows: resizableRowsEnabled.checked + resizableColumns: resizableColumnsEnabled.checked + animate: enableAnimation.checked + selectionBehavior: selectCells.checked ? TableView.SelectCells + : selectColumns.checked ? TableView.SelectColumns + : selectRows.checked ? TableView.SelectRows + : TableView.SelectionDisabled + leftMargin: marginsSpinBox.value + topMargin: marginsSpinBox.value + rightMargin: marginsSpinBox.value + bottomMargin: marginsSpinBox.value - columnWidthProvider: function(c) { - if (c > 30) - return 100 - } - - ScrollBar.horizontal: ScrollBar {} - ScrollBar.vertical: ScrollBar {} + ScrollBar.horizontal: ScrollBar { visible: !flickingMode.checked } + ScrollBar.vertical: ScrollBar { visible: !flickingMode.checked } model: TestTableModel { - rowCount: 200 - columnCount: 60 + rowCount: modelSize.value + columnCount: modelSize.value } selectionModel: ItemSelectionModel {} } + } - SelectionRectangle { - target: tableView - } + SelectionRectangle { + target: tableView + } + + Component { + id: tableViewDelegate + Rectangle { + id: delegate + implicitWidth: useLargeCells.checked ? 1000 : 50 + implicitHeight: useLargeCells.checked ? 1000 : 30 + border.width: current ? 2 : 0 + border.color: "darkgreen" + property var randomColor: Qt.rgba(0.6 + (0.4 * Math.random()), 0.6 + (0.4 * Math.random()), 0.6 + (0.4 * Math.random()), 1) + color: selected ? "lightgreen" + : (highlightCurrentRow.checked && (row === tableView.currentRow || column === tableView.currentColumn)) ? "lightgray" + : useRandomColor.checked ? randomColor + : model.display === "added" ? "lightblue" + : "white" + + required property bool selected + required property bool current - Component { - id: tableViewDelegate Rectangle { - id: delegate - implicitWidth: 50 - implicitHeight: 30 - border.width: row === selectedY && column == selectedX ? 2 : 0 - border.color: "darkgreen" - color: selected ? "lightgreen" : "white" - required property bool selected - - TapHandler { - onTapped: { - selectedX = column - selectedY = row - } - } + x: positionSubRect.x + y: positionSubRect.y + width: positionSubRect.width + height: positionSubRect.height + border.color: "red" + visible: useSubRect.checked + } - Text { - anchors.centerIn: parent - text: column + ", " + row - } + Text { + anchors.centerIn: parent + visible: drawText.checked + text: model.display } } - } } diff --git a/tests/manual/tableview/storagemodel/main.qml b/tests/manual/tableview/storagemodel/main.qml index 71c97b78f1..f188c66fb9 100644 --- a/tests/manual/tableview/storagemodel/main.qml +++ b/tests/manual/tableview/storagemodel/main.qml @@ -1,10 +1,10 @@ // Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -import QtQuick 2.12 -import QtQuick.Window 2.12 -import Qt.labs.qmlmodels 1.0 -import StorageModel 0.1 +import QtQuick +import QtQuick.Window +import Qt.labs.qmlmodels +import StorageModel Window { id: window @@ -22,6 +22,7 @@ Window { model: StorageModel { } columnSpacing: 1 rowSpacing: 1 + resizableColumns: true delegate: DelegateChooser { role: "type" DelegateChoice { |