
CustomTableModel.h
#pragma once #include#include class CustomTableModel : public QAbstractTableModel { Q_OBJECT const static int kColumnCnt = 5; const static int kInvalidRow = -1; enum { NodeSelectStateRole = Qt::UserRole + 1, NodeFileName, NodeSize, NodeTime }; enum TableColumn { TableColumnCheckbox, TableColumnFilefilename, TableColumnSize, TableColumnTime }; struct FileInfo { QString qstrFilename; QString qstrSize; QString qstrTime; }; public: CustomTableModel(QObject* pParent = nullptr); int rowCount(const QModelIndex & = QModelIndex()) const override; int columnCount(const QModelIndex & = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; Q_INVOKABLE QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; Q_INVOKABLE void initModel(); Q_INVOKABLE bool isItemSelected(int nRow) const; Q_INVOKABLE QVariant getData(int nRow, int nCol); private slots: private: QVariant get_data(int row, int role) const; private: std::vector m_listFile; std::set m_setSelect; std::vector vecHeader; };
CustomTableModel.cpp
#include "CustomTableModel.h" #includeqml文件#include CustomTableModel::CustomTableModel(QObject* pParent) : QAbstractTableModel(pParent) { vecHeader.emplace_back(QString("")); vecHeader.emplace_back(QString("filenmae")); vecHeader.emplace_back(QString("size")); vecHeader.emplace_back(QString("time")); } int CustomTableModel::rowCount(const QModelIndex &) const { return m_listFile.size(); } int CustomTableModel::columnCount(const QModelIndex &) const { return kColumnCnt; } QVariant CustomTableModel::data(const QModelIndex &index, int role) const { return get_data(index.row(), role); } QHash CustomTableModel::roleNames() const { return { {NodeFileName, "fileName"}, {NodeSize, "size"}, {NodeTime,"time"} }; } QVariant CustomTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if(role == Qt::DisplayRole) { // horizontal header if(orientation == Qt::Horizontal) { if(section >= vecHeader.size()) return QVariant(); return vecHeader[section]; } } return QVariant(); } bool CustomTableModel::isItemSelected(int nRow) const { return m_setSelect.find(nRow) != m_setSelect.end(); } void CustomTableModel::initModel() { beginResetModel(); m_listFile.clear(); m_setSelect.clear(); for(int i = 0; i < 15; ++i) { FileInfo infoFile; infoFile.qstrFilename = QString("file") + QString::number(i); infoFile.qstrSize = "66 Mb"; infoFile.qstrTime = "2022-08-02 21:00:00"; if(i % 2) m_setSelect.insert(i); m_listFile.emplace_back(infoFile); } endResetModel(); } QVariant CustomTableModel::getData(int nRow, int nCol) { return get_data(nRow, nCol + NodeSelectStateRole); } QVariant CustomTableModel::get_data(int row, int role) const { switch (role) { case NodeSelectStateRole: return isItemSelected(row); case NodeFileName: return m_listFile[row].qstrFilename; case NodeSize: { return m_listFile[row].qstrSize; } case NodeTime: return m_listFile[row].qstrTime; default: return QVariant(); } return QString("hello"); }
CustomTableview.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
TableView
{
id: root
// state for load more
property bool bContentYChanged: false
// flick stop at bounds
boundsBehavior: Flickable.StopAtBounds
clip: true
// scroll bar for vertical
ScrollBar.vertical: ScrollBar {
parent: flickable.parent
anchors.top: flickable.top
anchors.left: flickable.right
anchors.bottom: flickable.bottom
}
// triggered on load more
signal signalLoadMore();
// width for cell
//columnWidthProvider: function (column) { return 40; }
// height for each row
//rowHeightProvider: function (column) { return 40; }
Connections {
target: root
// suitable for width
function onWidthChanged() {
// force strench width for tableview
viewContent.forceLayout();
//console.log("width changed")
}
// check load more(down)
function onAtYEndChanged() {
if(bContentYChanged) {
bContentYChanged = false;
// load more with last record
//console.log("need load more triggered, state: " + root.atYEnd)
if(root.atYEnd)
signalLoadMore();
}
}
// triggered on content Y changed
function onContentYChanged() {
bContentYChanged = true;
//console.log("Content Y changed: " + root.contentY)
}
}
}
CustomCheckbox.qml
import QtQuick 2.0
import QtQuick.Controls 2.15
import QtGraphicalEffects 1.15
Rectangle {
// border color
//border.color: "#222222"
// border width
//border.width: 0
color:"#00000000"
property string imgSource
property bool clickable:true
// custom signal
signal mouseClicked
signal mouseClickpos(int x, int y)
signal hovered
property int radiusBtn : 2
property bool checked: false
property string imgChecked;
property string imgUnchecked;
// image for button
Image {
id: imgButton
anchors.fill: parent
anchors.centerIn: parent
source: parent.checked ? parent.imgChecked: parent.imgUnchecked
fillMode: Image.PreserveAspectFit
visible: false
}
Rectangle{
id: mask
anchors.fill: parent
radius: radiusBtn
visible: false
}
OpacityMask {
anchors.fill: parent
source: imgButton
maskSource: mask
}
// mouse area
MouseArea
{
id: btnMouse
hoverEnabled: true
anchors.fill: parent
// handle style on image button
//cursorShape: clickable ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: {
if(!clickable)return
checked = !checked;
mouseClicked()
mouseClickpos(mouse.x,mouse.y)
updateCheckState(checked);
}
onEntered: hovered()
}
// set button type
function setButtonType(bCycle) {
if(bCycle)
radius = width /2
}
function setMouseShape(bEnable) {
if(bEnable)
btnMouse.cursorShape = Qt.PointingHandCursor
else
btnMouse.cursorShape = Qt.ArrowCursor
}
function setButtonIcon(qstrIcon) {
imgButton.source = qstrIcon
}
function setClickable(enable) {
clickable = enable;
}
function updateCheckState(checked) {
imgButton.source = checked ? imgChecked: imgUnchecked
checked = bchecked
//console.log("update checkbox img: " + checked)
}
}
main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
FileView {
anchors.fill: parent
}
}
FileView.qml
import QtQuick.Controls 2.15
import QtQuick 2.15
import Qt.labs.qmlmodels 1.0
import CustomTableModel 1.0
Rectangle {
id: root
enum TableColumn {
TableColumnCheckbox,
TableColumnFilefilename,
TableColumnSize,
TableColumnTime
}
property int widthCheckbox : 40
property int widthFilename : 180
property int widthTime : 180
property int widthSize : 150
property bool checkedAll: false
signal selectAll(bool bSelectAll);
// tableview
CustomTableview {
id:viewContent
anchors.fill: parent
columnWidthProvider: function (column) {
var nWidth = widthCheckbox;
switch(column){
case FileView.TableColumnCheckbox: {
nWidth = widthCheckbox;
break;
}
case FileView.TableColumnFilefilename:{
nWidth = viewContent.width - widthTime - widthCheckbox - widthSize - 150;
if(nWidth < 0)
nWidth = widthFilename
break;
}
case FileView.TableColumnSize:{
nWidth = widthSize;
break;
}
case FileView.TableColumnTime:{
nWidth = widthTime;
break;
}
case 4:{
nWidth = 150;
break;
}
}
//console.log("get column width, col: " + column+ ", width: " + nWidth)
return nWidth;
}
rowHeightProvider: function (column) { return 40; }
model:modelContent
// item deletegate
delegate:DelegateChooser{
DelegateChoice{
column: FileView.TableColumnCheckbox
delegate: Rectangle{
width: widthCheckbox //viewContent.columnWidthProvider(FileView.TableColumnCheckbox);
//implicitHeight: 32
CustomCheckbox {
id: checkboxItem
width: 20
height: 20
imgChecked: "qrc:/box_checked@2x.png"
imgUnchecked: "qrc:/box_uncheck@2x.png"
anchors{
centerIn: parent
verticalCenter: parent.verticalCenter
}
checked: modelContent.getData(row, FileView.TableColumnCheckbox)
onMouseClicked: {
console.log("clicked at index:" + index)
}
// select all/none
Connections {
target: root
function onSelectAll(bSelectAll) {
checkboxItem.updateCheckState(bSelectAll);
}
}
}
}
}
DelegateChoice{
column: FileView.TableColumnFilefilename
delegate: Rectangle{
id: rectFilename
//color: "#666666"
width: viewContent.columnWidthProvider(FileView.TableColumnFilefilename);
//implicitHeight: 32
//border.width: 1
//border.color: "#848484"
TextMetrics {
id: textMetrics
text: modelContent.getData(row, FileView.TableColumnFilefilename)
}
Text {
id: textFilename
text: modelContent.getData(row, FileView.TableColumnFilefilename)
width: viewContent.columnWidthProvider(FileView.TableColumnFilefilename)
anchors {
left: parent.left
right: parent.right
top: parent.top
bottom: parent.bottom
}
font.pointSize: 12
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
}
MouseArea{
id:areaMouse
hoverEnabled: true
anchors.fill: parent
}
ToolTip
{
height: 26
visible: areaMouse.containsMouse && textFilename.text !== "" && textMetrics.width > (rectFilename.width-6)
contentItem: Text {
text: textFilename.text
color: "#D6D6D6"
}
background: Rectangle {
color: "#222222"
}
}
}
}
DelegateChoice{
column: FileView.TableColumnSize
delegate: Rectangle{
width: viewContent.columnWidthProvider(FileView.TableColumnSize);
//implicitHeight: 32
Text {
text: modelContent.getData(row, FileView.TableColumnSize)
anchors.fill: parent
font.pointSize: 12
//color: "white"
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
}
}
}
DelegateChoice{
column: FileView.TableColumnTime
delegate: Rectangle{
id:rect
width: viewContent.columnWidthProvider(FileView.TableColumnTime);
//implicitHeight: 32
clip: true
Text {
id: textTime
text: modelContent.getData(row, FileView.TableColumnTime)
anchors.centerIn: parent
font.pointSize: 12
width: parent.width
elide: Text.ElideRight
leftPadding: 3
rightPadding: 3
}
}
}
}
}
CustomTableModel {
id: modelContent
}
// header view
HorizontalHeaderView {
id: headerContent
visible: true
// disbale drag on table
interactive: false
// sync with tableview
syncView: viewContent
//model: ["123", "filename", "size", "time"]
model:modelContent
delegate:Rectangle{
CustomCheckbox {
id: checkboxHeader
width: 20
height: 20
imgChecked: "qrc:/box_checked@2x.png"
imgUnchecked: "qrc:/box_uncheck@2x.png"
anchors{
centerIn: parent
verticalCenter: parent.verticalCenter
}
visible: (0 == index) ? true: false;
checked: checkedAll
onMouseClicked: {
console.log("clicked at index:" + index + ", check state: " + checkboxHeader.checked)
headerSelectAll(checkboxHeader.checked);
}
}
Text {
// model declared in qml, modelData = header-data
text: modelContent.headerData(column, Qt.Horizontal)
//text: modelData
font.pointSize: 12
verticalAlignment: Text.AlignVCenter
anchors {
left: parent.left;
verticalCenter: parent.verticalCenter
}
visible: !checkboxHeader.visible
}
}
}
Component.onCompleted: {
modelContent.initModel();
}
function headerSelectAll(bSelectAll) {
checkedAll = bSelectAll;
selectAll(checkedAll);
}
}