How to create a tableview (5.12 )with column headers? - qt

I am creating a Table using the new qml tableview (Qt 5.12).
I am able to create a model in C++ and able to populate the model in tabular format along with scrollbar.How do I add column headers to this table?
Code:
import QtQuick 2.12
import QtQuick.Controls 2.5
import Qt.labs.qmlmodels 1.0
//import QtQuick.Controls.Styles 1.4
import TableModel 0.1
Rectangle {
id:table
border.width: 3
border.color: 'dark blue'
QtObject{
id:internals
property int rows:0
property int col:0
property int colwidth:0
property var columnName:[]
}
function setRows(num){ internals.rows = num}
function setCols(num){ internals.col = num}
function setColWidth(num){internals.colwidth = num}
function setColNames(stringlist){
if(stringlist.length > 1)
internals.col = stringlist.length
dataModel.setColumnName(stringlist);
}
function addRowData(stringlist){
var len = stringlist.length
if(len >0)
{
dataModel.addData(stringlist)
}
}
TableModel {
id:dataModel
}
TableView{
id:tbl
anchors.top: headerCell
anchors.fill: parent
//columnSpacing: 1
//rowSpacing: 1
clip: true
ScrollBar.horizontal: ScrollBar{}
ScrollBar.vertical: ScrollBar{}
model:dataModel
Component{
id:datacell
Rectangle {
implicitWidth: 100
implicitHeight: 20
color: 'white'
border.width: 1
border.color: 'dark grey'
Text {
id:txtbox
anchors.fill: parent
wrapMode: Text.NoWrap
clip: true
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: display
}
}
}
}
function init(){
console.log("Calling init")
tbl.delegate= datacell
}
}

Currently TableView does not have headers so you should create it, in this case use Row, Column and Repeater.
On the other hand you must implement the headerData method and you must do it Q_INVOKABLE.
class TableModel : public QAbstractTableModel
{
Q_OBJECT
public:
// ...
Q_INVOKABLE QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
// ...
TableView {
id: tableView
model: table_model
// ...
Row {
id: columnsHeader
y: tableView.contentY
z: 2
Repeater {
model: tableView.columns > 0 ? tableView.columns : 1
Label {
width: tableView.columnWidthProvider(modelData)
height: 35
text: table_model.headerData(modelData, Qt.Horizontal)
color: '#aaaaaa'
font.pixelSize: 15
padding: 10
verticalAlignment: Text.AlignVCenter
background: Rectangle { color: "#333333" }
}
}
}
Column {
id: rowsHeader
x: tableView.contentX
z: 2
Repeater {
model: tableView.rows > 0 ? tableView.rows : 1
Label {
width: 60
height: tableView.rowHeightProvider(modelData)
text: table_model.headerData(modelData, Qt.Vertical)
color: '#aaaaaa'
font.pixelSize: 15
padding: 10
verticalAlignment: Text.AlignVCenter
background: Rectangle { color: "#333333" }
}
}
}
The complete example you find here.

If you're using Qt 5.15, you can use a HorizontalHeaderView for column labels.
https://doc.qt.io/qt-5/qml-qtquick-controls2-horizontalheaderview.html
There's also VerticalHeaderView for row labeling.
https://doc.qt.io/qt-5/qml-qtquick-controls2-verticalheaderview.html

I'm new to the QML. I came to the answer of eyllanesc so many times through my struggle with the new TableView (qt 5.12+), so I wanna thank you and share what helped me even more.
It's this video:
Shawn Rutledge - TableView and DelegateChooser: new in Qt 5.12
part of Qt Virtual Tech Summit 2019
The discussed code
It's a bit long but he covers
the differences between old and new TableView
how to create universal model for the views
resizable headers
different representation per column type - DelegateChooser
sortable columns
column reorder

Related

Show big text in qml

Trying to display a text file in qml. The file size is about 3 megabytes. At the same time there are:
long opening of the form,
large waste of memory.
Tried use ScrollView, Flickable, Text and TextArea. How can these problems be avoided?
QML
ScrollView {
id: idScrollView
anchors {
fill: parent
margins: Dimensions.x(15)
}
Text {
id: idContent
anchors {
left: parent.left
right: parent.right
rightMargin: Dimensions.x(20)
}
text: viewmodel.getLogText()
font.pixelSize: Dimensions.y(10)
wrapMode: Text.Wrap
}
}
C++
QString MainViewModel::getLogText()
{
const int maxSz = 1024 * 200;
QString result;
QFile file(ALog::filePath());
if (file.open(QIODevice::ReadOnly))
{
if (file.size() > maxSz)
file.seek(file.size() - maxSz);
QByteArray arr = file.read(maxSz);
result = QString::fromLatin1(arr);
if (file.size() > maxSz)
result = QString("Skip %1 Kb\n\n").arg((file.size() - maxSz)/1024) + result;
file.close();
}
return result;
}
Found a partial solution. It loads much faster and consumes several times less memory. Among the disadvantages-there is no possibility to convert Text to TextArea to be able to select the text to copy to the clipboard.
property variant stringList: null
function updateText() {
stringList = viewmodel.getLogText().split('\n')
idContentListView.positionViewAtEnd()
}
ListView {
id: idContentListView
model: stringList
anchors {
fill: parent
margins: Dimensions.x(15)
}
delegate: Text {
anchors {
left: parent.left
right: parent.right
}
text: model.modelData
font.pixelSize: Dimensions.y(10)
textFormat: Text.PlainText
wrapMode: Text.Wrap
}
ScrollBar.vertical: ScrollBar {}
}

QML GroupBox: Binding loop detected for property "implicitWidth"

I'm trying to load some QML form in QQuickWidget and keep getting: QML GroupBox: Binding loop detected for property "implicitWidth"
The simplified QML is:
import QtQuick 2.4
import QtQuick.Controls 1.2
GroupBox {
id: root
title: qsTr("1")
Column {
id: column1
width: parent.width
ComboBox {
id: cbComboBox
width: parent.width
currentIndex: 0
editable: false
}
GroupBox {
id: groupBox
title: "test"
width: parent.width //the problem
Label {
text:"1"
width: parent.width
}
}
}
}
It looks like for some reason I can't use parent.width or cbComboBox.width for groupBox. And root.width works but too wide. What I'm missing? I need nested GroupBox to have maximum width (with spacing).
According to the Qt GroupBox QML Type documentation
The implicit size of the GroupBox is calculated based on the size of its content.
It means the GroupBox will automatically use the size of its children to calculate its own size.
In your case, the only child is the Column whose width is equal to the GroupBox width. I think that is what's causing the binding loop.
If you set the width of the Column as said by #eyllanesc, the binding loop will disappear.
I would rather define the implicit width of the GroupBox (the width by default) in the QML part. In my example, the implicit width is 100px but it could be calculated from the widths of the children (ComboBox, other GroupBox,...).
// MyGroupBox.qml
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Controls 1.2
GroupBox {
title: qsTr("1")
implicitWidth: 100
Column {
id: column1
width: parent.width
ComboBox {
id: cbComboBox
currentIndex: 0
editable: false
width: parent.width
}
GroupBox {
id: groupBox
title: "test"
width: parent.width
Label {
text: "1"
}
}
}
}
And it will possible to set a real width depending on your project directly from the C++ by changing the width of the root object.
// main.cpp
#include <QtQuickWidgets/QQuickWidget>
#include <QQuickItem>
#include <QtWidgets>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QQuickWidget *view = new QQuickWidget;
view->setSource(QUrl::fromLocalFile("://MyGroupBox.qml"));
view->rootObject()->setWidth(800);
view->show();
return app.exec();
}

QML element positioning works unexpectedly

This is my custom control. It draws background. I want it to occupy the whole header. But none of methods works, expect setting width and height explicitly, using some numbers. Even parent.width and parent.height does not work.
Control - TabViewHeaderBg.
Code:
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
import MyControls 1.0
Item
{
property int preInfoColumnWidth
property int nameColumnWidth
property int progressColumnWidth
property int sizeColumnWidth
property int columnSpacing
height: 40
TabViewHeaderBg
{
anchors.fill: parent
}
Row
{
spacing: columnSpacing
Row
{
width: preInfoColumnWidth
CheckBox
{
}
}
Text
{
text: "Name"
width: nameColumnWidth
anchors.verticalCenter: parent.verticalCenter
}
Text
{
text: "Status"
width: progressColumnWidth
anchors.verticalCenter: parent.verticalCenter
}
Text
{
text: "Size"
width: sizeColumnWidth
anchors.verticalCenter: parent.verticalCenter
}
}
}
This whole control is used as the header control in ListView.
Addition #1.
TabViewHeaderBgQc code:
class TabViewHeaderBgQc :
public QQuickPaintedItem
{
Q_OBJECT
public:
TabViewHeaderBgQc(
QQuickItem *parent = 0);
void paint(QPainter *painter) override;
signals:
public slots:
};
TabViewHeaderBgQc::TabViewHeaderBgQc(
QQuickItem *parent) :
QQuickPaintedItem(parent)
{
}
void TabViewHeaderBgQc::paint(QPainter *painter)
{
QPen pen(QColor(229,229,229), 1);
painter->setPen(pen);
painter->drawRect(boundingRect());
}
Imports with the following code:
qmlRegisterType<TabViewHeaderBgQc>("MyControls", 1, 0, "TabViewHeaderBg");
Addition #2.
Qt 5.8, Windows 10, Visual Studio 2015.

MenuBar like behaviour in QML

I want to have menubar like behaviour when clicked on a rectangle. Whenever a rectangle is clicked a model is updated and a ListView is shown. I want this ListView to disappear whenever another Rectangle is clicked and the listmodel should not be appended with each click. Here is my sample code.
Card.qml
Rectangle {
id: card
width: 50
height: 100
color: "pink"
Item {
id: rec
width: 50
anchors.bottom: parent.top
ListModel {
id: menuListModel
}
Component {
id: delegate
Rectangle {
width: 50
height: 20
color: "blue"
Text {
anchors.fill: parent
anchors.centerIn: parent
text: commandText
}
}
}
ListView {
anchors.fill: parent
model:menuListModel
delegate: delegate
interactive: false
}
}
MouseArea {
anchors.fill: parent
onClicked: {
rec.height += 40;
menuListModel.append({"commandText" : "Act"});
menuListModel.append({"commandText" : "Set"});
}
}
}
main.qml
Item {
width: 120
height: 200
Row {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
spacing: 10
Card {
id: card1
}
Card {
id: card2
}
}
}
Also I want to call certain function upon clicking menu buttons i.e. Act and Set.
Edit
The following function is called with appropriate flags when a card(here rectangle) is clicked.
property int command_activate: 0x0001
property int command_summon: 0x0002
property int command_spsummon: 0x0004
property int command_mset: 0x0008
property int command_sset: 0x0010
property int command_repos: 0x0020
property int command_attack: 0x0040
property int command_list: 0x0080
function showMenu(flag) {
if(flag & command_activate) {
rec.height += 15;
menuListModel.append({"commandText" : "Activate"});
}
if(flag & command_summon) {
rec.height += 15;
menuListModel.append({"commandText" : "Normal Summon"});
}
if(flag & command_spsummon) {
rec.height += 15;
menuListModel.append({"commandText" : "Special Summon"});
}
if(flag & command_mset) {
rec.height += 15;
menuListModel.append({"commandText" : "Set"});
}
if(flag & command_sset) {
rec.height += 15;
menuListModel.append({"commandText" : "Set"});
}
if(flag & command_repos) {
if(position & pos_facedown) {
rec.height += 15;
menuListModel.append({"commandText" : "Flip Summon"});
}
else if(position & pos_attack) {
rec.height += 15;
menuListModel.append({"commandText" : "To Defense"});
}
else {
rec.height += 15;
menuListModel.append({"commandText" : "To Attack"});
}
}
if(flag & command_attack) {
rec.height += 15;
menuListModel.append({"commandText" : "Attack"});
}
if(flag & command_list) {
rec.height += 15;
menuListModel.append({"commandText" : "View"});
}
}
So, in short when a card is clicked a menu has to be shown according to the flag on the top of the card.
There are several problems with your code.
You cannot name your delegate "delegate". When you do this, the ListView uses its own delegate property to set itself, leading to nothing happening.
Also, why don't you just statically fill your ListView then use the visible property to toggle whether you display it or not? If you want it to disappear whenever another Card is clicked, you may have to use the focus property.
Indeed, setting focus to true will reset the focus of all other Items within the focus scope. Something like this might work:
Rectangle {
id: card
...
property alias model: list.model
Component {
id: mydelegate
Rectangle {
width: 50
height: 20
...
}
}
ListView {
id: list
visible: card.activeFocus
anchors.bottom: parent.top
width: card.width
delegate: mydelegate
interactive: false
height: childrenRect.height
}
MouseArea {
anchors.fill: parent
onClicked: {
card.focus = !card.focus
}
}
}
As for calling a function, you could add the name of the function to call directly in the ListModel. Add a MouseArea in your delegate, and send a signal on clicked. Then, you just have to call the matching slot (Agreed, the this[slot]() syntax is a bit hacky).
In Card.qml
Rectangle {
id: card
...
property alias model: list.model
signal itemClicked(string slot)
Component {
id: mydelegate
Rectangle {
...
MouseArea
{
anchors.fill: parent
onClicked: {
if(model.slot)
itemClicked(slot)
}
}
}
}
...
}
In main.qml
...
Card
{
model: ListModel {
ListElement{commandText: "Act"; slot: "act"}
ListElement{commandText: "Set"; slot: "set"}
}
function act()
{
print("act triggered")
}
function set()
{
print("set trigggered")
}
onItemClicked: { this[slot]() }
}
...
As I can't comment, to ask for clarification, I will state my assumptions from what I understood:
You have a number of ListModel that contain the information of the menu that should pop up, when clicking on the corresponding rectangle item in the bar.
The ListView that pops up shall disappear, once you clicked any other rectangle in this bar. (Also: if something outside the bar or menu is clicked? Or an item in the bar is selected?)
I'd go with only one ListView, and update/change the model, as well as the position (e.g. margin) once a button is clicked, so you don't have multiple unused objects flying around.
To not show anything, I think it is sufficient, to set an empty model.
You can also have a list of functions.
Row {
id: row
property var f: [a, b, c]
function a() { console.log('a'); }
function b() { console.log('b'); }
function c() { console.log('c'); }
width: 300
height: 50
Repeater {
id: rep
anchors.fill: parent
delegate: button
model: 3
Rectangle {
id: button
width: 98
height: 50
border.color: 'black'
Text {
text: "a"
anchors.fill: parent
}
MouseArea {
anchors.fill: parent
onClicked: row.f[index]()
}
}
}
}
e.g. will call the function a, b, or c - depending on the index of the rectangle.
I hope, so far, I could help.

C++ class exposed to QML error in fashion TypeError: Property '...' of object is not a function

I've successfully exposed a C++ class to QML. It is registered and found in Qt Creator. It's purpose is to connect to a database, as shown in following code:
#ifndef UESQLDATABASE_H
#define UESQLDATABASE_H
#include <QObject>
#include <QtSql/QSqlDatabase>
class UeSqlDatabase : public QObject
{
Q_OBJECT
Q_PROPERTY(bool m_ueConnected READ isConnected WRITE setConnected NOTIFY ueConnectedChanged)
private:
bool m_ueConneted;
inline void setConnected(const bool& ueConnected)
{ this->m_ueConneted=ueConnected; }
public:
explicit UeSqlDatabase(QObject *parent = 0);
Q_INVOKABLE inline const bool& isConnected() const
{ return this->m_ueConneted; }
~UeSqlDatabase();
signals:
void ueConnectedChanged();
public slots:
void ueConnectToDatabase (const QString& ueStrHost, const QString& ueStrDatabase,
const QString& ueStrUsername, const QString& ueStrPassword);
};
#endif // UESQLDATABASE_H
However, when I try to call method isConnected() from the following QML code
import QtQuick 2.0
Rectangle
{
id: ueMenuButton
property string ueText;
width: 192
height: 64
radius: 8
states: [
State
{
name: "ueStateSelected"
PropertyChanges
{
target: gradientStop1
color: "#000000"
}
PropertyChanges
{
target: gradientStop2
color: "#3fe400"
}
}
]
gradient: Gradient
{
GradientStop
{
id: gradientStop1
position: 0
color: "#000000"
}
GradientStop
{
position: 0.741
color: "#363636"
}
GradientStop
{
id: gradientStop2
position: 1
color: "#868686"
}
}
border.color: "#ffffff"
border.width: 2
antialiasing: true
Text
{
id: ueButtonText
color: "#ffffff"
text: qsTr(ueText)
clip: false
z: 0
scale: 1
rotation: 0
font.strikeout: false
anchors.fill: parent
font.bold: true
style: Text.Outline
textFormat: Text.RichText
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 16
}
MouseArea
{
id: ueClickArea
antialiasing: true
anchors.fill: parent
onClicked:
{
uePosDatabase.ueConnectToDatabase("127.0.0.1",
"testDb",
"testUser",
"testPassword");
if(uePosDatabase.isConnected()==true)
{
ueMenuButton.state="ueStateSelected";
}
else
{
ueMenuButton.state="base state"
}
}
}
}
I get the following error:
qrc:/UeMenuButton.qml:92: TypeError: Property 'isConnected' of object UeSqlDatabase(0x1772060) is not a function
What am I doing wrong?
You have this error because you have declared the property isConnected in C++ but you're calling it from QML in the wrong way: uePosDatabase.isConnected is the correct way, not uePosDatabase.isConnected().
If you want to call the function isConnected() you should change its name to differate it from the property, like getIsConnected(). Given your property declaration, you neither need to call this function directly nor you need to make it callable from QML with the Q_INVOKABLE macro.
You must first create the object you want in QML code and then use the function:
your QML Code:
import QtQuick 2.0
import QSqlDatabase 1.0
Rectangle {
id: ueMenuButton
QSqlDatabase {
id: uePosDatabase
}
.
.
.
MouseArea
{
id: ueClickArea
antialiasing: true
anchors.fill: parent
onClicked: {
console.log(uePosDatabase.isConnected())
}
}
}
Be aware in case of an error some lines earlier in the same .js file can lead to this problem. The following code will not be executed.

Resources