This commit is contained in:
2025-09-28 17:05:51 +08:00
parent 8eb80ab66d
commit d97fa3e0fe
398 changed files with 18737 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
[General]
buildDir="D:/Sandbox/Qt6QMLBeginnersCode/11-ModelViewArchitecture/14-TodoListModelView/build"
no-cmake-calls=false
docDir=C:/Qt/Docs/Qt-6.9.0
importPaths="C:/Qt/6.9.0/mingw_64/qml"

View File

@@ -0,0 +1,52 @@
cmake_minimum_required(VERSION 3.16)
project(TodoList VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS Quick QuickControls2)
qt_standard_project_setup(REQUIRES 6.8)
qt_add_executable(TodoList
src/main.cpp
)
qt_add_qml_module(TodoList
URI TodoList
VERSION 1.0
QML_FILES
# Main View
src/views/MainView.qml
# Components
src/components/AppHeader.qml
src/components/AddTaskBar.qml
src/components/TaskItem.qml
src/components/TaskStats.qml
# Models
src/models/TaskListModel.qml
)
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
set_target_properties(TodoList PROPERTIES
# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.app01_todolist_starter
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
target_link_libraries(TodoList
PRIVATE Qt6::Quick Qt6::QuickControls2
)
include(GNUInstallDirs)
install(TARGETS TodoList
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

View File

@@ -0,0 +1,110 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
id: root
height: 60
signal taskAdded(string taskText)
property color backgroundColor: "#ffffff"
property color textColor: "#333333"
property color primaryColor: "#007aff"
property bool darkMode: false
Rectangle {
id: background
anchors.fill: parent
color: "transparent"
Rectangle {
anchors.fill: parent
color: root.backgroundColor
radius: 12
border.color: root.darkMode ? "#404040" : "#e0e0e0"
border.width: 1
RowLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 12
// Add icon
Rectangle {
Layout.preferredWidth: 36
Layout.preferredHeight: 36
color: root.primaryColor
radius: 18
Text {
anchors.centerIn: parent
text: "+"
color: "#ffffff"
font.pixelSize: 20
font.weight: Font.Bold
}
}
// Text input
TextField {
id: taskInput
Layout.fillWidth: true
Layout.preferredHeight: 36
placeholderText: qsTr("Add a new task...")
placeholderTextColor: root.darkMode ? "#888888" : "#999999"
color: root.textColor
font.pixelSize: 16
selectByMouse: true
background: Rectangle {
color: "transparent"
border.color: "transparent"
}
Keys.onReturnPressed: root.addTask()
Keys.onEnterPressed: root.addTask()
}
// Add button
Button {
id: addButton
Layout.preferredWidth: 80
Layout.preferredHeight: 36
text: qsTr("Add")
enabled: taskInput.text.trim().length > 0
background: Rectangle {
color: addButton.enabled ?
(addButton.pressed ? Qt.darker(root.primaryColor, 1.2) : root.primaryColor) :
(root.darkMode ? "#404040" : "#e0e0e0")
radius: 8
}
contentItem: Text {
text: addButton.text
color: addButton.enabled ? "#ffffff" : (root.darkMode ? "#888888" : "#cccccc")
font.pixelSize: 14
font.weight: Font.Medium
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
onClicked: root.addTask()
}
}
}
}
function addTask() {
if (taskInput.text.trim().length > 0) {
root.taskAdded(taskInput.text.trim())
taskInput.text = ""
taskInput.focus = false
}
}
}

View File

@@ -0,0 +1,86 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
id: root
height: 80
signal toggleDarkMode()
// Properties that will be bound from parent
property color textColor: "#333333"
property color textSecondary: "#999999"
property bool darkMode: false
Rectangle {
anchors.fill: parent
color: "transparent"
RowLayout{
anchors.fill: parent
anchors.leftMargin: 8
anchors.rightMargin: 8
spacing: 12
// App icon
Text {
text: "📝"
font.pixelSize: 32
}
// App title and subtitle
Column {
Layout.fillWidth: true
spacing: 2
Text {
text: "My Tasks"
font.pixelSize: 28
font.weight: Font.Bold
color: root.textColor
}
Text {
text: qsTr("Stay organized, stay productive")
font.pixelSize: 12
color: root.textSecondary
opacity: 0.8
}
}
// Dark mode toggle
Button {
id: themeToggle
Layout.preferredWidth: 50
Layout.preferredHeight: 50
background: Rectangle {
color: themeToggle.pressed ?
(root.darkMode ? "#404040" : "#e0e0e0") :
(root.darkMode ? "#2d2d2d" : "#f5f5f5")
radius: 25
border.color: root.darkMode ? "#555555" : "#d0d0d0"
border.width: 1
Behavior on color {
ColorAnimation { duration: 150 }
}
}
contentItem: Text{
text: root.darkMode ? "☀️" : "🌙"
font.pixelSize: 20
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
//Emit the signal when the button is clicked
onClicked: root.toggleDarkMode()
}
}
}
}

View File

@@ -0,0 +1,129 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
id: root
height: 56
required property string taskTitle
required property bool taskDone
signal toggleDone()
signal deleteTask()
property color backgroundColor: "#ffffff"
property color textColor: "#333333"
property color completedColor: "#999999"
property color primaryColor: "#007aff"
property bool darkMode: false
// Constants for this component
readonly property int itemHeight: 56
readonly property int margins: 4
readonly property int innerMargins: 12
readonly property int checkboxSize: 24
readonly property int deleteButtonSize: 32
Rectangle {
id: taskItemBackground
anchors.fill: parent
color: "transparent"
Rectangle {
id: itemBackground
anchors.fill: parent
anchors.margins: root.margins
color: root.backgroundColor
radius: 8
border.color: root.taskDone ? "transparent" : (root.darkMode ? "#404040" : "#f0f0f0")
border.width: 1
opacity: root.taskDone ? 0.7 : 1.0
RowLayout {
anchors.fill: parent
anchors.margins: root.innerMargins
spacing: 12
// Checkbox
Rectangle {
id: checkbox
Layout.preferredWidth: root.checkboxSize
Layout.preferredHeight: root.checkboxSize
color: root.taskDone ? root.primaryColor : "transparent"
border.color: root.taskDone ? root.primaryColor : (root.darkMode ? "#666666" : "#cccccc")
border.width: 2
radius: 4
// Checkmark
Text {
anchors.centerIn: parent
text: "✓"
color: "#ffffff"
font.pixelSize: 16
font.weight: Font.Bold
opacity: root.taskDone ? 1.0 : 0.0
}
MouseArea {
anchors.fill: parent
onClicked: {
root.toggleDone()
}
cursorShape: Qt.PointingHandCursor
}
}
// Task text
Text {
id: taskText
Layout.fillWidth: true
text: root.taskTitle
color: root.taskDone ? root.completedColor : root.textColor
font.pixelSize: 16
font.strikeout: root.taskDone
wrapMode: Text.WordWrap
MouseArea {
anchors.fill: parent
onClicked: root.toggleDone()
cursorShape: Qt.PointingHandCursor
}
}
// Delete button
Button {
id: deleteButton
Layout.preferredWidth: root.deleteButtonSize
Layout.preferredHeight: root.deleteButtonSize
opacity: itemBackground.hovered ? 1.0 : 0.3
background: Rectangle {
color: deleteButton.pressed ? "#ff3b30" : "transparent"
radius: 16
}
contentItem: Text {
text: "🗑"
font.pixelSize: 16
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
onClicked: {
root.deleteTask()
}
}
}
// Hover effect
property bool hovered: false
HoverHandler {
onHoveredChanged: itemBackground.hovered = hovered
}
}
}
}

View File

@@ -0,0 +1,95 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
id: root
height: visible ? 60 : 0
visible: totalTasks > 0
property int totalTasks: 0
property int completedTasks: 0
property int remainingTasks: 0
property bool hasCompleted: false
property color backgroundColor: "#ffffff"
property color textColor: "#333333"
property color secondaryTextColor: "#666666"
property color primaryColor: "#007aff"
property color dangerColor: "#ff3b30"
property bool darkMode: false
signal clearCompleted()
Rectangle {
anchors.fill: parent
color: "transparent"
Rectangle {
anchors.fill: parent
color: root.backgroundColor
radius: 12
border.color: root.darkMode ? "#404040" : "#e0e0e0"
border.width: 1
RowLayout {
anchors.fill: parent
anchors.margins: 16
spacing: 16
// Statistics text
Column {
Layout.fillWidth: true
spacing: 2
Text {
text: root.totalTasks === 1 ?
qsTr("%1 task").arg(root.totalTasks) :
qsTr("%1 tasks").arg(root.totalTasks)
color: root.textColor
font.pixelSize: 16
font.weight: Font.Medium
}
Text {
text: root.completedTasks > 0 ?
qsTr("%1 completed, %2 remaining").arg(root.completedTasks).arg(root.remainingTasks) :
qsTr("No tasks completed yet")
color: root.secondaryTextColor
font.pixelSize: 14
visible: root.totalTasks > 0
}
}
// Clear completed button
Button {
id: clearButton
Layout.preferredHeight: 36
text: qsTr("Clear Completed")
visible: root.hasCompleted
enabled: root.hasCompleted
background: Rectangle {
color: clearButton.pressed ?
Qt.darker(root.dangerColor, 1.2) :
(clearButton.hovered ? root.dangerColor : "transparent")
border.color: root.dangerColor
border.width: 1
radius: 8
}
contentItem: Text {
text: clearButton.text
color: clearButton.hovered ? "#ffffff" : root.dangerColor
font.pixelSize: 14
font.weight: Font.Medium
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
onClicked: root.clearCompleted()
}
}
}
}
}

View File

@@ -0,0 +1,22 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickStyle>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
// Set Qt Quick Controls style to Basic to enable customization
QQuickStyle::setStyle("Basic");
QQmlApplicationEngine engine;
QObject::connect(
&engine,
&QQmlApplicationEngine::objectCreationFailed,
&app,
[]() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
engine.loadFromModule("TodoList", "MainView");
return app.exec();
}

View File

@@ -0,0 +1,89 @@
import QtQuick
ListModel {
id: root
// Add some sample tasks for testing
Component.onCompleted: {
addTask("Learn Qt QML")
addTask("Build a todo app")
addTask("Practice QML components")
}
// Add a new task
function addTask(title) {
if (title && title.trim().length > 0) {
append({
"title": title.trim(),
"completed": false,
"id": generateId()
})
}
}
// Toggle task completion status
function toggleTask(index) {
if (index >= 0 && index < count) {
setProperty(index, "completed", !get(index).completed)
}
}
// Delete a task
function deleteTask(index) {
if (index >= 0 && index < count) {
remove(index)
}
}
// Delete task by ID
function deleteTaskById(taskId) {
for (let i = 0; i < count; i++) {
if (get(i).id === taskId) {
remove(i)
break
}
}
}
// Clear all completed tasks
function clearCompletedTasks() {
for (let i = count - 1; i >= 0; i--) {
if (get(i).completed) {
remove(i)
}
}
}
// Get statistics
function getStats() {
let total = count
let completed = 0
for (let i = 0; i < count; i++) {
if (get(i).completed) {
completed++
}
}
return {
"total": total,
"completed": completed,
"remaining": total - completed
}
}
// Check if there are any completed tasks
function hasCompletedTasks() {
for (let i = 0; i < count; i++) {
if (get(i).completed) {
return true
}
}
return false
}
// Generate a unique ID for tasks
function generateId() {
return Date.now() + Math.random().toString(36).substr(2, 9)
}
}

View File

@@ -0,0 +1,165 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
ApplicationWindow {
id: root
width: 400
height: 700
visible: true
title: qsTr("My Tasks")
// Theme properties
property bool darkMode: false
property color backgroundColor: darkMode ? "#1e1e1e" : "#f0f2f5"
property color primaryColor: "#007aff"
property color textColor: darkMode ? "#ffffff" : "#333333"
property color cardColor: darkMode ? "#2d2d2d" : "#ffffff"
property color completedColor: darkMode ? "#666666" : "#999999"
// Task model - Using the dedicated TaskListModel
TaskListModel {
id: taskModel
}
// Background
Rectangle {
anchors.fill: parent
color: root.backgroundColor
}
//The components of the ui
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 20
// Header
AppHeader {
id: header
Layout.fillWidth: true
textColor: root.textColor
textSecondary: root.completedColor
darkMode: root.darkMode
onToggleDarkMode: root.darkMode = !root.darkMode
}
// Add task bar
AddTaskBar {
id: addTaskBar
Layout.fillWidth: true
backgroundColor: root.cardColor
textColor: root.textColor
primaryColor: root.primaryColor
darkMode: root.darkMode
onTaskAdded: function(taskText) {
taskModel.addTask(taskText)
}
}
// Tasks list
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: root.cardColor
radius: 12
border.width: 1
border.color: root.darkMode ? "#404040" : "#e0e0e0"
ScrollView {
anchors.fill: parent
anchors.margins: 1
clip: true
ListView {
id: taskListView
model: taskModel
spacing: 8
anchors.margins: 12
delegate: TaskItem {
required property int index
required property string title
required property bool completed
width: taskListView.width
taskTitle: title
taskDone: completed
backgroundColor: root.cardColor
textColor: root.textColor
completedColor: root.completedColor
primaryColor: root.primaryColor
darkMode: root.darkMode
onToggleDone: {
taskModel.toggleTask(index)
}
onDeleteTask: {
taskModel.deleteTask(index)
}
}
// Empty state
Rectangle {
anchors.centerIn: parent
width: parent.width - 40
height: 120
color: "transparent"
visible: taskListView.count === 0
Column {
anchors.centerIn: parent
spacing: 16
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "📝"
font.pixelSize: 48
opacity: 0.3
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("No tasks yet")
color: root.completedColor
font.pixelSize: 18
font.weight: Font.Medium
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("Add a task above to get started")
color: root.completedColor
font.pixelSize: 14
horizontalAlignment: Text.AlignHCenter
}
}
}
}
}
}
// Task statistics at bottom
TaskStats {
id: taskStats
Layout.fillWidth: true
backgroundColor: root.cardColor
textColor: root.textColor
secondaryTextColor: root.completedColor
primaryColor: root.primaryColor
darkMode: root.darkMode
totalTasks: taskModel.count
completedTasks: taskModel.getStats().completed
remainingTasks: taskModel.getStats().remaining
hasCompleted: taskModel.hasCompletedTasks()
onClearCompleted: {
taskModel.clearCompletedTasks()
}
}
}
}