參考視頻:Using C++ Models in QML {tutorial}
這個視頻比較長,40多分鐘,內容也比較豐富。
主要功能
- 一個簡單的待辦事項
- 可以增加新的待辦事項,也可以刪除已經完成的事情
源碼
ToDoList.qml
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
import ToDo 1.0
ColumnLayout{
Frame {
Layout.fillWidth: true
ListView{
id:listView
implicitWidth: 300
implicitHeight: 300
anchors.fill: parent
clip: true
model: ToDoModel{
list:toDoList
}
delegate: RowLayout{
width: listView.width
CheckBox{
checked: model.done
onCheckedChanged: model.done = checked
}
TextField{
Layout.fillWidth: true
text: model.description
onEditingFinished: model.description = text
}
}
}
}
RowLayout{
Button{
text:qsTr("Add new item")
onClicked: toDoList.appendItem()
Layout.fillWidth: true
}
Button{
text:qsTr("Remove complete items")
onClicked: toDoList.removeCompletedItem()
Layout.fillWidth: true
}
}
}
main.qml
import QtQuick 2.11
import QtQuick.Controls 2.4
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("To Do List")
ToDoList{
anchors.centerIn: parent
}
}
ToDoList.h
#ifndef TODOLIST_H
#define TODOLIST_H
#include <QObject>
#include <QVector>
struct ToDoItem{
bool done;
QString description;
bool equals(const ToDoItem& item) const {
return this->done == item.done && this->description == item.description;
}
};
class ToDoList : public QObject
{
Q_OBJECT
public:
explicit ToDoList(QObject *parent = nullptr);
QVector<ToDoItem> items() const;
bool setItemAt(int index, const ToDoItem &item);
signals:
void preItemAppended();
void postItemAppended();
void preItemRemoved(int index);
void postItemRemoved();
public slots:
void appendItem();
void removeCompletedItem();
private:
QVector<ToDoItem> m_items;
};
#endif // TODOLIST_H
ToDoList.cpp
#include "ToDoList.h"
ToDoList::ToDoList(QObject *parent) : QObject(parent)
{
m_items.append({true,"Wash the car"});
m_items.append({false,"Fix the sink"});
}
QVector<ToDoItem> ToDoList::items() const
{
return m_items;
}
bool ToDoList::setItemAt(int index, const ToDoItem &item)
{
if(index < 0 || index >= m_items.size()){
return false;
}
const ToDoItem& oldItem = m_items[index];
if(oldItem.equals(item)){
return false;
}
m_items[index] = item;
return true;
}
void ToDoList::appendItem()
{
emit preItemAppended();
ToDoItem item;
item.done = false;
m_items.append(item);
emit postItemAppended();
}
void ToDoList::removeCompletedItem()
{
for(int i=0;i<m_items.size();){
if(m_items[i].done){
emit preItemRemoved(i);
m_items.remove(i);
emit postItemRemoved();
}else{
i++;
}
}
}
ToDoModel.h
#ifndef TODOMODEL_H
#define TODOMODEL_H
#include <QAbstractListModel>
class ToDoList;
class ToDoModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(ToDoList *list READ list WRITE setList)
public:
explicit ToDoModel(QObject *parent = nullptr);
enum{
DoneRole = Qt::UserRole,
DescriptionRole
};
// Basic functionality:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
// Editable:
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
private:
ToDoList* m_list;
// QAbstractItemModel interface
public:
virtual QHash<int, QByteArray> roleNames() const override;
ToDoList *list() const;
void setList(ToDoList *list);
};
#endif // TODOMODEL_H
ToDoModel.cpp
#include "ToDoModel.h"
#include "ToDoList.h"
ToDoModel::ToDoModel(QObject *parent)
: QAbstractListModel(parent),
m_list(nullptr)
{
}
int ToDoModel::rowCount(const QModelIndex &parent) const
{
// For list models only the root node (an invalid parent) should return the list's size. For all
// other (valid) parents, rowCount() should return 0 so that it does not become a tree model.
if (parent.isValid() || m_list == nullptr)
return 0;
return m_list->items().size();
}
QVariant ToDoModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || m_list==nullptr)
return QVariant();
auto item = m_list->items().at(index.row());
switch (role) {
case DoneRole:{
return QVariant(item.done);
}
case DescriptionRole:{
return QVariant(item.description);
}
}
return QVariant();
}
bool ToDoModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if(m_list == nullptr){
return false;
}
auto item = m_list->items().at(index.row());
switch (role) {
case DoneRole:{
item.done = value.toBool();
break;
}
case DescriptionRole:{
item.description = value.toString();
break;
}
}
if (m_list->setItemAt(index.row(),item)) {
emit dataChanged(index, index, QVector<int>() << role);
return true;
}
return false;
}
Qt::ItemFlags ToDoModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsEditable;
}
ToDoList *ToDoModel::list() const
{
return m_list;
}
void ToDoModel::setList(ToDoList *list)
{
beginResetModel();
if(m_list != nullptr){
m_list->disconnect(this);
}
m_list = list;
if(m_list!=nullptr){
connect(m_list,&ToDoList::preItemAppended,[=](){
const int index = m_list->items().size();
beginInsertRows(QModelIndex(),index,index);
});
connect(m_list,&ToDoList::postItemAppended,[=](){
endInsertRows();
});
connect(m_list,&ToDoList::preItemRemoved,[=](int index){
beginRemoveRows(QModelIndex(),index,index);
});
connect(m_list,&ToDoList::postItemRemoved,[=](){
endRemoveRows();
});
}
endResetModel();
}
QHash<int, QByteArray> ToDoModel::roleNames() const
{
QHash<int, QByteArray> names;
names[DoneRole] = "done";
names[DescriptionRole] = "description";
return names;
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "ToDoModel.h"
#include "ToDoList.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<ToDoModel>("ToDo",1,0,"ToDoModel");
qmlRegisterUncreatableType<ToDoList>("ToDo",1,0,"ToDoList",
QStringLiteral("ToDoList should not be created in QMl"));
ToDoList toDoList;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("toDoList",&toDoList);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
截圖
最後的運行效果
自己做的時候留下的部分截圖
思考與總結
雖然是一個簡單的ToDo-List,卻是目前做過的最難的一個qml Demo,牽涉到的Qt相關的知識非常多。
- 在Layout中使用fillWidth: true使控件填充滿
不同於css中設置display的方式,qml的layout採用這種方式使得控件填充滿layout,同時還可以實現對其,如這個例子中的兩個Button的寬度和ListView寬度一樣
- Model-View-Delegate的使用
在我的使用經歷中,Qt的這個模式是一個很難的點,我看了很多視頻教程,也嘗試着自己去寫基於這個模式的Demo,往往是失敗告終。在這次的ToDo-List中,視頻非常詳細的介紹了從簡單的model,一步一步到複雜的model,如何編寫,如何使用。我認爲是這個視頻對我最大的幫助之一。
- 使用信號槽實現的雙向綁定
我開始只能把model的數據展示在listview上,如果我去勾選框或者去編輯,model是感應不到的,是不會改變的。後來是onCheckedChanged和onEditingFinished兩個槽函數,把改變的數據賦給模型,才完成這個雙向綁定。
- 在QML是用C++編寫的models
本來這是這個視頻的重點,結果發現,在最後使用的時候,非常簡單,兩個註冊函數就搞定了。反倒是把大量的精力花在瞭如何編寫model上。在編輯器中,qml對於C++槽函數的智能提示也做得非常的好,很方便。
GitHub: https://github.com/PikachuHy/qml-demo/tree/master/using-cpp-models-in-qml