CustomTableModel.h
#pragma once
#include <QAbstractTableModel>
#include <set>
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<int, QByteArray> 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<FileInfo> m_listFile;
std::set<int> m_setSelect;
std::vector<QString> vecHeader;
};
CustomTableModel.cpp
#include "CustomTableModel.h"
#include <QDebug>
#include <sstream>
CustomTableModel::CustomTableModel(QObject* pParent/* = nullptr*/)
: 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 &/* = QModelIndex()*/) const
{
return m_listFile.size();
}
int CustomTableModel::columnCount(const QModelIndex &/* = QModelIndex()*/) const
{
return kColumnCnt;
}
QVariant CustomTableModel::data(const QModelIndex &index, int role) const
{
return get_data(index.row(), role);
}
QHash<int, QByteArray> CustomTableModel::roleNames() const
{
return {
{NodeFileName, "fileName"},
{NodeSize, "size"},
{NodeTime,"time"}
};
}
QVariant CustomTableModel::headerData(int section, Qt::Orientation orientation, int role/* = Qt::DisplayRole*/) 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
/************************************************************************************************
* properties *
*************************************************************************************************/
// 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
}
/************************************************************************************************
* signals *
*************************************************************************************************/
// triggered on load more
signal signalLoadMore();
// width for cell
//columnWidthProvider: function (column) { return 40; }
// height for each row
//rowHeightProvider: function (column) { return 40; }
/************************************************************************************************
* connection *
*************************************************************************************************/
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);
}
}