Przeglądaj źródła

simple model view

simon 1 rok temu
rodzic
commit
e230ada0f6

+ 30 - 0
basic/events_1.py

@@ -0,0 +1,30 @@
+import sys
+
+from PyQt5.QtWidgets import QMainWindow, QLabel, QApplication
+
+
+class MainWindow(QMainWindow):
+    def __init__(self):
+        super().__init__()
+        self.label = QLabel("Click in this window")
+        self.setCentralWidget(self.label)
+        self.setMouseTracking(True)
+        self.label.setMouseTracking(True)
+
+    def mouseMoveEvent(self, event):
+        self.label.setText("Mouse Moved")
+
+    def mousePressEvent(self, event):
+        self.label.setText("Mouse Press")
+
+    def mouseReleaseEvent(self, event):
+        self.label.setText("Mouse release")
+
+    def mouseDoubleClickEvent(self, event):
+        self.label.setText("Mouse Double Click")
+
+
+app = QApplication(sys.argv)
+window = MainWindow()
+window.show()
+sys.exit(app.exec_())

+ 25 - 0
basic/events_3.py

@@ -0,0 +1,25 @@
+import sys
+
+from PyQt5.QtWidgets import QMainWindow, QMenu, QAction, QApplication
+from PyQt5.QtCore import Qt, pyqtSlot
+
+
+class MainWindow(QMainWindow):
+    def __init__(self):
+        super().__init__()
+
+        self.setContextMenuPolicy(Qt.CustomContextMenu)
+        self.customContextMenuRequested.connect(self.on_content_menu)
+
+    def on_content_menu(self, pos):
+        context = QMenu(self)
+        context.addAction(QAction("test1", self))
+        context.addAction(QAction("test2", self))
+        context.addAction(QAction("test3", self))
+        context.exec_(self.mapToGlobal(pos))
+
+
+app = QApplication(sys.argv)
+window = MainWindow()
+window.show()
+sys.exit(app.exec_())

+ 7 - 4
basic/windows_7.py

@@ -1,16 +1,19 @@
-import sys 
+import sys
 from random import randint
-from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QPushButton, QWidget, QInputDialog, QLineEdit, QLabel
+from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QPushButton, QWidget, QInputDialog, QLineEdit, \
+    QLabel
+
 
 class AnotherWindow(QWidget):
     """
     This "window" is a Qwidget. If it has no parent, it 
     will appear as a free-floating window.
     """
+
     def __init__(self):
         super().__init__()
         layout = QVBoxLayout()
-        self.label = QLabel("Another Window %d" % randint(0,100))
+        self.label = QLabel("Another Window %d" % randint(0, 100))
         layout.addWidget(self.label)
         self.setLayout(layout)
 
@@ -42,4 +45,4 @@ class MainWindow(QMainWindow):
 app = QApplication(sys.argv)
 window = MainWindow()
 window.show()
-app.exec_()
+app.exec_()

+ 41 - 0
designer/MainWindow.py

@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'mainwindow.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.11
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again.  Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_MainWindow(object):
+    def setupUi(self, MainWindow):
+        MainWindow.setObjectName("MainWindow")
+        MainWindow.resize(300, 300)
+        self.centralwidget = QtWidgets.QWidget(MainWindow)
+        self.centralwidget.setObjectName("centralwidget")
+        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
+        self.verticalLayout.setObjectName("verticalLayout")
+        self.label = QtWidgets.QLabel(self.centralwidget)
+        self.label.setObjectName("label")
+        self.verticalLayout.addWidget(self.label)
+        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
+        self.pushButton.setObjectName("pushButton")
+        self.verticalLayout.addWidget(self.pushButton)
+        MainWindow.setCentralWidget(self.centralwidget)
+        self.menubar = QtWidgets.QMenuBar(MainWindow)
+        self.menubar.setGeometry(QtCore.QRect(0, 0, 300, 24))
+        self.menubar.setObjectName("menubar")
+        MainWindow.setMenuBar(self.menubar)
+
+        self.retranslateUi(MainWindow)
+        QtCore.QMetaObject.connectSlotsByName(MainWindow)
+
+    def retranslateUi(self, MainWindow):
+        _translate = QtCore.QCoreApplication.translate
+        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
+        self.label.setText(_translate("MainWindow", "TextLabel"))
+        self.pushButton.setText(_translate("MainWindow", "PushButton"))

+ 33 - 0
designer/compiled_example.py

@@ -0,0 +1,33 @@
+import os.path
+import random
+import sys
+
+from PyQt5.QtCore import Qt
+from PyQt5.QtWidgets import QApplication, QMainWindow
+
+from MainWindow import Ui_MainWindow
+
+basedir = os.path.dirname(__file__)
+
+
+class MainWindow(QMainWindow, Ui_MainWindow):
+    def __init__(self):
+        super().__init__()
+        self.setupUi(self)
+
+        f = self.label.font()
+        f.setPointSize(25)
+        self.label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
+        self.label.setFont(f)
+
+        self.pushButton.pressed.connect(self.update_label)
+
+    def update_label(self):
+        n = random.randint(1, 6)
+        self.label.setText("%d" % n)
+
+
+app = QApplication(sys.argv)
+window = MainWindow()
+window.show()
+app.exec_()

+ 14 - 0
designer/example_1.py

@@ -0,0 +1,14 @@
+import os.path
+import sys
+
+from PyQt5.QtWidgets import QApplication, QMainWindow
+from PyQt5 import uic
+
+basedir = os.path.dirname(__file__)
+
+app = QApplication(sys.argv)
+
+window = uic.loadUi(os.path.join(basedir, "mainwindow.ui"))
+window.show()
+
+app.exec_()

+ 20 - 0
designer/example_2.py

@@ -0,0 +1,20 @@
+import os.path
+import sys
+
+from PyQt5 import uic
+from PyQt5.QtWidgets import QApplication, QMainWindow
+from MainWindow import Ui_MainWindow
+
+basedir = os.path.dirname(__file__)
+
+
+class MainWindow(QMainWindow, Ui_MainWindow):
+    def __init__(self):
+        super().__init__()
+        self.setupUi(self)
+
+
+app = QApplication(sys.argv)
+window = MainWindow()
+window.show()
+app.exec_()

+ 47 - 0
designer/mainwindow.ui

@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>300</width>
+    <height>300</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QVBoxLayout" name="verticalLayout">
+    <item>
+     <widget class="QLabel" name="label">
+      <property name="text">
+       <string>TextLabel</string>
+      </property>
+     </widget>
+    </item>
+    <item>
+     <widget class="QPushButton" name="pushButton">
+      <property name="text">
+       <string>PushButton</string>
+      </property>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>300</width>
+     <height>24</height>
+    </rect>
+   </property>
+  </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 63 - 0
further/mvc_1.py

@@ -0,0 +1,63 @@
+import sys
+
+from PyQt5.QtCore import Qt
+from PyQt5.QtWidgets import QMainWindow, QFormLayout, QLineEdit, QSpinBox, QComboBox, QPushButton, QApplication, QWidget
+
+model = {
+    "name": "Johnina Smith",
+    "age": 10,
+    "favorite_icecream": "Vanilla",
+}
+
+
+class MainWindow(QMainWindow):
+    def __init__(self):
+        super().__init__()
+        self.setWindowTitle("MVC Example")
+
+        layout = QFormLayout()
+
+        # Dictionary to store the form data, with default data.
+        self.name = QLineEdit()
+        self.name.setText(model["name"])
+        self.name.textChanged.connect(self.on_name_changed)
+        self.age = QSpinBox()
+        self.age.setRange(0, 200)
+        self.age.setValue(model["age"])
+        self.age.valueChanged.connect(self.on_age_changed)
+        self.icecream = QComboBox()
+        self.icecream.addItems(["Vanilla", "Strawberry", "Chocolate"])
+        self.icecream.setCurrentText(model["favorite_icecream"])
+        self.icecream.currentTextChanged.connect(self.on_icecream_changed)
+        self.save_btn = QPushButton("Save")
+        self.restore_btn = QPushButton("Restore")
+
+        layout.addRow("Name", self.name)
+        layout.addRow("Age", self.age)
+        layout.addRow("Favorite Ice Cream", self.icecream)
+        layout.addRow(self.save_btn)
+        layout.addRow(self.restore_btn)
+        layout.setLabelAlignment(Qt.AlignLeft)
+
+        widget = QWidget()
+        widget.setLayout(layout)
+        self.setCentralWidget(widget)
+
+    def on_name_changed(self, value):
+        model["name"] = value
+        print(model)
+
+    def on_age_changed(self, value):
+        model["age"] = value
+        print(model)
+
+    def on_icecream_changed(self, value):
+        model["favorite_icecream"] = value
+        print(model)
+
+
+if __name__ == "__main__":
+    app = QApplication(sys.argv)
+    window = MainWindow()
+    window.show()
+    app.exec_()

+ 101 - 0
further/mvc_2.py

@@ -0,0 +1,101 @@
+import random
+import sys
+
+from PyQt5.QtCore import Qt
+from PyQt5.QtWidgets import QMainWindow, QFormLayout, QLineEdit, QSpinBox, QComboBox, QPushButton, QApplication, \
+    QWidget, QCheckBox
+
+model = {
+    "name": "Johnina Smith",
+    "age": 10,
+    "favorite_icecream": "Vanilla",
+    "disable_details": False,
+}
+
+backups = []
+
+
+class MainWindow(QMainWindow):
+    def __init__(self):
+        super().__init__()
+        self.setWindowTitle("MVC Example")
+
+        layout = QFormLayout()
+
+        # Dictionary to store the form data, with default data.
+        self.name = QLineEdit()
+        self.name.textChanged.connect(self.on_name_changed)
+        self.age = QSpinBox()
+        self.age.setRange(0, 200)
+        self.age.valueChanged.connect(self.on_age_changed)
+        self.icecream = QComboBox()
+        self.icecream.addItems(["Vanilla", "Strawberry", "Chocolate"])
+        self.icecream.currentTextChanged.connect(self.on_icecream_changed)
+        self.save_btn = QPushButton("Save")
+        self.save_btn.clicked.connect(self.on_save_clicked)
+        self.restore_btn = QPushButton("Restore")
+        self.restore_btn.clicked.connect(self.on_restore_clicked)
+        self.disable_details = QCheckBox("Disable Details?")
+        self.disable_details.toggled.connect(self.on_disable_details_toggled)
+
+        layout.addRow("Name", self.name)
+        layout.addRow("Age", self.age)
+        layout.addRow("Favorite Ice Cream", self.icecream)
+        layout.addWidget(self.disable_details)  # QCheckBox has its own label.
+        layout.addRow(self.save_btn)
+        layout.addRow(self.restore_btn)
+        layout.setLabelAlignment(Qt.AlignLeft)
+
+        widget = QWidget()
+        widget.setLayout(layout)
+        self.setCentralWidget(widget)
+
+        self.update_ui()
+
+    def update_ui(self):
+        self.name.setText(model["name"])
+        self.age.setValue(model["age"])
+        self.icecream.setCurrentText(model["favorite_icecream"])
+        self.disable_details.setChecked(model["disable_details"])
+
+        self.age.setDisabled(model["disable_details"])
+        self.icecream.setDisabled(model["disable_details"])
+
+    def on_name_changed(self, value):
+        model["name"] = value
+        print(model)
+
+    def on_age_changed(self, value):
+        model["age"] = value
+        print(model)
+
+    def on_icecream_changed(self, value):
+        model["favorite_icecream"] = value
+        print(model)
+
+    def on_save_clicked(self):
+        backups.append(model.copy())
+        print("SAVE:", model)
+        print("BACKUPS:", len(backups))
+
+    def on_restore_clicked(self):
+        if not backups:
+            return
+        random.shuffle(backups)
+        backup = backups.pop()  # Remove a backup
+        model.update(backup)  # Overwrite the data in the model
+        self.update_ui()
+        print("RESTORE:", model)
+        print("BACKUPS:", len(backups))
+
+    def on_disable_details_toggled(self, value):
+        model["disable_details"] = value
+        print(model)
+        self.update_ui()
+
+
+if __name__ == "__main__":
+    app = QApplication(sys.argv)
+    window = MainWindow()
+    window.show()
+    app.exec_()

+ 134 - 0
further/mvc_5.py

@@ -0,0 +1,134 @@
+import random
+import sys
+from collections import UserDict
+
+from PyQt5.QtCore import QObject, pyqtSignal, Qt
+from PyQt5.QtWidgets import QMainWindow, QApplication, QFormLayout, QLineEdit, QSpinBox, QComboBox, QPushButton, \
+    QCheckBox, QWidget
+
+
+class DataModelSignals(QObject):
+    # Emit an "updated" signal when a property changes.
+    updated = pyqtSignal()
+
+
+class DataModel(UserDict):
+    def __init__(self, *args, **kwargs):
+        self.signals = DataModelSignals()
+        super().__init__(*args, **kwargs)
+
+    def __setitem__(self, key, value):
+        previous = self.get(key)  # Get the existing value.
+        super().__setitem__(key, value)
+        if value != previous:  # There is a change
+            self.signals.updated.emit()  # Emit the signal
+            print(self)  # Show the current state.
+
+
+model = DataModel(
+    name="Johnina Smith",
+    age=10,
+    favorite_icecream="Vanilla",
+    disable_details=False,
+)
+
+
+class Controller:
+    """ Simple controller, which handles backups and other operations. """
+    backups = []
+
+    def capitalize(self):
+        model["name"] = model["name"].upper()
+
+    def store_backup(self):
+        self.backups.append(model.copy())
+
+    def restore_backup(self):
+        if not self.backups:
+            return
+        random.shuffle(self.backups)
+        backup = self.backups.pop()
+        model.update(backup)  # Overwrite the data in the model
+        print("RESTORE:", model)
+        print("BACKUPS:", len(self.backups))
+
+    def apply_title_case(self):
+        model["name"] = model["name"].title()
+
+
+controller = Controller()
+
+
+class MainWindow(QMainWindow):
+    def __init__(self):
+        super().__init__()
+        self.setWindowTitle("MVC Example")
+        layout = QFormLayout()
+
+        # Dictionary to store the form data, with default data.
+        self.name = QLineEdit()
+        self.name.textChanged.connect(self.on_name_changed)
+        self.age = QSpinBox()
+        self.age.setRange(0, 200)
+        self.age.valueChanged.connect(self.on_age_changed)
+        self.icecream = QComboBox()
+        self.icecream.addItems(["Vanilla", "Strawberry", "Chocolate"])
+        self.icecream.currentTextChanged.connect(self.on_icecream_changed)
+
+        self.title_btn = QPushButton("Set Title Case")
+        self.title_btn.pressed.connect(controller.apply_title_case)
+
+        # tag::connect[]
+        self.save_btn = QPushButton("Save")
+        self.save_btn.pressed.connect(controller.store_backup)
+        self.restore_btn = QPushButton("Restore")
+        self.restore_btn.pressed.connect(controller.restore_backup)
+        # end::connect[]
+
+        self.disable_details = QCheckBox("Disable Details?")
+        self.disable_details.toggled.connect(self.on_disable_details_toggled)
+
+        layout.addRow("Name", self.name)
+        layout.addRow(self.title_btn)
+        layout.addRow("Age", self.age)
+        layout.addRow("Favorite Ice Cream", self.icecream)
+        layout.addWidget(self.disable_details)  # QCheckBox has its own label.
+        layout.addRow(self.save_btn)
+        layout.addRow(self.restore_btn)
+        layout.setLabelAlignment(Qt.AlignLeft)
+
+        widget = QWidget()
+        widget.setLayout(layout)
+        self.setCentralWidget(widget)
+
+        self.update_ui()
+        # Hook our UI sync into the model updated signal
+        model.signals.updated.connect(self.update_ui)
+
+    def update_ui(self):
+        self.name.setText(model["name"])
+        self.age.setValue(model["age"])
+        self.icecream.setCurrentText(model["favorite_icecream"])
+        self.disable_details.setChecked(model["disable_details"])
+
+        self.age.setDisabled(model["disable_details"])
+        self.icecream.setDisabled(model["disable_details"])
+
+    def on_name_changed(self, name):
+        model["name"] = name
+
+    def on_age_changed(self, age):
+        model["age"] = age
+
+    def on_icecream_changed(self, icecream):
+        model["favorite_icecream"] = icecream
+
+    def on_disable_details_toggled(self, checked):
+        model["disable_details"] = checked
+
+
+if __name__ == "__main__":
+    app = QApplication(sys.argv)
+    window = MainWindow()
+    window.show()
+    sys.exit(app.exec_())

+ 61 - 0
model-views/MainWindow.py

@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'mainwindow.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.11
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again.  Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_MainWindow(object):
+    def setupUi(self, MainWindow):
+        MainWindow.setObjectName("MainWindow")
+        MainWindow.resize(280, 362)
+        self.centralwidget = QtWidgets.QWidget(MainWindow)
+        self.centralwidget.setObjectName("centralwidget")
+        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
+        self.verticalLayout.setObjectName("verticalLayout")
+        self.todoView = QtWidgets.QListView(self.centralwidget)
+        self.todoView.setObjectName("todoView")
+        self.verticalLayout.addWidget(self.todoView)
+        self.horizontalLayout = QtWidgets.QHBoxLayout()
+        self.horizontalLayout.setObjectName("horizontalLayout")
+        self.deleteButton = QtWidgets.QPushButton(self.centralwidget)
+        self.deleteButton.setObjectName("deleteButton")
+        self.horizontalLayout.addWidget(self.deleteButton)
+        self.completeButton = QtWidgets.QPushButton(self.centralwidget)
+        self.completeButton.setObjectName("completeButton")
+        self.horizontalLayout.addWidget(self.completeButton)
+        self.verticalLayout.addLayout(self.horizontalLayout)
+        self.todoEdit = QtWidgets.QLineEdit(self.centralwidget)
+        self.todoEdit.setObjectName("todoEdit")
+        self.verticalLayout.addWidget(self.todoEdit)
+        self.addButton = QtWidgets.QPushButton(self.centralwidget)
+        self.addButton.setObjectName("addButton")
+        self.verticalLayout.addWidget(self.addButton)
+        MainWindow.setCentralWidget(self.centralwidget)
+        self.menubar = QtWidgets.QMenuBar(MainWindow)
+        self.menubar.setGeometry(QtCore.QRect(0, 0, 280, 24))
+        self.menubar.setObjectName("menubar")
+        MainWindow.setMenuBar(self.menubar)
+        self.statusbar = QtWidgets.QStatusBar(MainWindow)
+        self.statusbar.setObjectName("statusbar")
+        MainWindow.setStatusBar(self.statusbar)
+
+        self.retranslateUi(MainWindow)
+        QtCore.QMetaObject.connectSlotsByName(MainWindow)
+        MainWindow.setTabOrder(self.todoView, self.deleteButton)
+        MainWindow.setTabOrder(self.deleteButton, self.todoEdit)
+        MainWindow.setTabOrder(self.todoEdit, self.completeButton)
+        MainWindow.setTabOrder(self.completeButton, self.addButton)
+
+    def retranslateUi(self, MainWindow):
+        _translate = QtCore.QCoreApplication.translate
+        MainWindow.setWindowTitle(_translate("MainWindow", "Todo"))
+        self.deleteButton.setText(_translate("MainWindow", "Delete"))
+        self.completeButton.setText(_translate("MainWindow", "Complet"))
+        self.addButton.setText(_translate("MainWindow", "Add Todo"))

+ 1 - 0
model-views/data.json

@@ -0,0 +1 @@
+[[true, "My first item"], [true, "Second item"], [false, "Another todo"]]

BIN
model-views/flag.png


+ 72 - 0
model-views/mainwindow.ui

@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>280</width>
+    <height>362</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Todo</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QVBoxLayout" name="verticalLayout">
+    <item>
+     <widget class="QListView" name="todoView"/>
+    </item>
+    <item>
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <item>
+       <widget class="QPushButton" name="deleteButton">
+        <property name="text">
+         <string>Delete</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="completeButton">
+        <property name="text">
+         <string>Complet</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </item>
+    <item>
+     <widget class="QLineEdit" name="todoEdit"/>
+    </item>
+    <item>
+     <widget class="QPushButton" name="addButton">
+      <property name="text">
+       <string>Add Todo</string>
+      </property>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>280</width>
+     <height>24</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+ </widget>
+ <tabstops>
+  <tabstop>todoView</tabstop>
+  <tabstop>deleteButton</tabstop>
+  <tabstop>todoEdit</tabstop>
+  <tabstop>completeButton</tabstop>
+  <tabstop>addButton</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>

+ 89 - 0
model-views/tableview_demo.py

@@ -0,0 +1,89 @@
+import sys
+from datetime import datetime
+
+from PyQt5.QtCore import QAbstractTableModel, Qt
+from PyQt5.QtGui import QColor
+from PyQt5.QtWidgets import QTableView, QMainWindow, QApplication
+
+COLORS = ['#053061', '#2166ac', '#4393c3', '#92c5de', '#d1e5f0', '#f7f7f7', '#fddbc7', '#f4a582', '#d6604d', '#b2182b',
+          '#67001f']
+
+
+class TableModel(QAbstractTableModel):
+    def __init__(self, data):
+        super(TableModel, self).__init__()
+        self._data = data
+
+    def data(self, index, role):
+        if role == Qt.DecorationRole:
+            value = self._data[index.row()][index.column()]
+            if isinstance(value, int) or isinstance(value, float):
+                value = int(value)
+
+                value = max(-5, value)  # value < -5 becomes -5
+                value = min(5, value)  # value > 5 becomes 5
+                value = value + 5  # -5 becomes 0, 5 becomes 10
+
+                return QColor(COLORS[value])
+        if role == Qt.ForegroundRole:
+            value = self._data[index.row()][index.column()]
+            if (isinstance(value, int) or isinstance(value, float)) and value < 0:
+                return QColor(Qt.red)
+
+        if role == Qt.TextAlignmentRole:
+            value = self._data[index.row()][index.column()]
+            if isinstance(value, int) or isinstance(value, float):
+                # Align right, vertical middle.
+                return Qt.AlignVCenter + Qt.AlignRight
+
+        if role == Qt.DisplayRole:
+            # See below for the nested-list data structure.
+            # .row() indexes into the outer list,
+            # .column() indexes into the sub-list
+            value = self._data[index.row()][index.column()]
+
+            if isinstance(value, datetime):
+                # Here, we use the date's string representation.
+                return value.strftime('%Y-%m-%d')
+            if isinstance(value, str):
+                # Here, we use the date
+                return '"%s"' % value
+            if isinstance(value, float):
+                # Here, we use the date
+                return "%.2f" % value
+            return value
+
+    def rowCount(self, index):
+        # The length of the outer list.
+        return len(self._data)
+
+    def columnCount(self, index):
+        # The following takes the first sub-list, and returns
+        # the length (only works if all rows are an equal length)
+        return len(self._data[0])
+
+
+class MainWindow(QMainWindow):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        self.table = QTableView()
+
+        data = [
+            [4, 9, 2],
+            [1, -1, 'hello'],
+            [3.141592653589793, 2.718281828459045, 'world'],
+            [3, 3, datetime(2025, 2, 18)],
+            [7, 1, -5],
+        ]
+
+        self.model = TableModel(data)
+        self.table.setModel(self.model)
+
+        self.setCentralWidget(self.table)
+
+
+app = QApplication(sys.argv)
+window = MainWindow()
+window.show()
+sys.exit(app.exec_())

+ 49 - 0
model-views/tableview_numpy.py

@@ -0,0 +1,49 @@
+import sys
+
+import numpy as np
+from PyQt5.QtCore import QAbstractTableModel, Qt
+from PyQt5.QtWidgets import QMainWindow, QTableView, QApplication
+
+
+class TableModel(QAbstractTableModel):
+    def __init__(self, data):
+        super(TableModel, self).__init__()
+        self._data = data
+
+    def data(self, index, role):
+        if role == Qt.DisplayRole:
+            value = self._data[index.row(), index.column()]
+            return str(value)
+
+    def rowCount(self, index):
+        return self._data.shape[0]
+
+    def columnCount(self, index):
+        return self._data.shape[1]
+
+
+class MainWindow(QMainWindow):
+    def __init__(self):
+        super().__init__()
+
+        self.table = QTableView()
+
+        data = np.array([
+            [1, 9, 2],
+            [4, 0, -1],
+            [7, 8, 9],
+            [3, 3, 2],
+            [5, 8, 9],
+        ])
+
+        self.model = TableModel(data)
+        self.table.setModel(self.model)
+
+        self.setCentralWidget(self.table)
+        self.setGeometry(600, 100, 400, 200)
+
+
+app = QApplication(sys.argv)
+window = MainWindow()
+window.show()
+sys.exit(app.exec_())

+ 58 - 0
model-views/tableview_pandas.py

@@ -0,0 +1,58 @@
+import sys
+
+import pandas as pd
+from PyQt5.QtCore import QAbstractTableModel, Qt
+from PyQt5.QtWidgets import QMainWindow, QTableView, QApplication
+
+
+class TableModel(QAbstractTableModel):
+    def __init__(self, data):
+        super(TableModel, self).__init__()
+        self._data = data
+
+    def data(self, index, role):
+        if role == Qt.DisplayRole:
+            value = self._data.iloc[index.row(), index.column()]
+            return str(value)
+
+    def rowCount(self, index):
+        return self._data.shape[0]
+
+    def columnCount(self, index):
+        return self._data.shape[1]
+
+    def headerData(self, section, orientation, role):
+        if role == Qt.DisplayRole:
+            if orientation == Qt.Horizontal:
+                return str(self._data.columns[section])
+            if orientation == Qt.Vertical:
+                return str(self._data.index[section])
+
+
+class MainWindow(QMainWindow):
+    def __init__(self):
+        super().__init__()
+
+        self.table = QTableView()
+
+        data = pd.DataFrame([
+            [1, 9, 2],
+            [4, 0, -1],
+            [7, 8, 9],
+            [3, 3, 2],
+            [5, 8, 9],
+        ],
+            columns=['a', 'b', 'c'], index=['Row 1', 'Row 2', 'Row 3', 'Row 4', 'Row 5'],
+        )
+
+        self.model = TableModel(data)
+        self.table.setModel(self.model)
+
+        self.setCentralWidget(self.table)
+        self.setGeometry(600, 100, 400, 200)
+
+
+app = QApplication(sys.argv)
+window = MainWindow()
+window.show()
+sys.exit(app.exec_())

+ 111 - 0
model-views/todo_1.py

@@ -0,0 +1,111 @@
+import os
+import sys
+import json
+
+from PyQt5.QtCore import QAbstractListModel, Qt
+from PyQt5.QtGui import QImage
+from PyQt5.QtWidgets import QApplication, QMainWindow
+
+from MainWindow import Ui_MainWindow
+
+basedir = os.path.dirname(__file__)
+
+flag = QImage(os.path.join(basedir, 'flag.png'))
+
+
+class TodoModel(QAbstractListModel):
+    def __init__(self, todos=None):
+        super().__init__()
+        self.todos = todos or []
+
+    def data(self, index, role):
+        if role == Qt.DisplayRole:
+            status, text = self.todos[index.row()]
+            return text
+        if role == Qt.DecorationRole:
+            status, text = self.todos[index.row()]
+            if status:
+                return flag
+
+    def rowCount(self, index):
+        return len(self.todos)
+
+
+class MainWindow(QMainWindow, Ui_MainWindow):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.setupUi(self)
+        self.model = TodoModel()
+        self.load()
+        self.todoView.setModel(self.model)
+
+        # Connect the button
+        self.addButton.pressed.connect(self.add)
+        self.deleteButton.pressed.connect(self.delete)
+        self.completeButton.pressed.connect(self.complete)
+
+    def load(self):
+        try:
+            with open("data.json", "r") as f:
+                self.model.todos = json.load(f)
+        except Exception as e:
+            print(e)
+
+    def save(self):
+        with open("data.json", "w") as f:
+            json.dump(self.model.todos, f)
+
+    def add(self):
+        """
+        Add an item to our todos list, getting the QLineEdit.todoEdit
+        and then clearing it
+        :return:
+        """
+        text = self.todoEdit.text()
+        text = text.strip()  # Remove whitespace
+        if text:  # Don't add empty strings
+            self.model.todos.append((False, text))
+            # Trigger refresh
+            self.model.layoutChanged.emit()
+            # Empty the input
+            self.todoEdit.setText("")
+            self.save()
+
+    def delete(self):
+        """
+        Delete the currently selected item from our model
+        :return:
+        """
+        indexes = self.todoView.selectedIndexes()
+        if indexes:
+            index = indexes[0]
+            # Remove the item and refresh
+            del self.model.todos[index.row()]
+            self.model.layoutChanged.emit()
+            # Clear the selection (as it is no longer valid).
+            self.todoView.clearSelection()
+            self.save()
+
+    def complete(self):
+        """
+        Mark the currently selected item as completed
+        :return:
+        """
+        indexes = self.todoView.selectedIndexes()
+        if indexes:
+            index = indexes[0]
+            row = index.row()
+            status, text = self.model.todos[row]
+            self.model.todos[row] = (True, text)
+            # Emit dataChanged to update the view
+            self.model.dataChanged.emit(index, index)
+            # Clear the selection
+            self.todoView.clearSelection()
+            self.save()
+
+
+if __name__ == '__main__':
+    app = QApplication(sys.argv)
+    window = MainWindow()
+    window.show()
+    sys.exit(app.exec_())

+ 18 - 0
model-views/todo_skeleton.py

@@ -0,0 +1,18 @@
+import sys
+
+from PyQt5.QtWidgets import QApplication, QMainWindow
+
+from MainWindow import Ui_MainWindow
+
+
+class MainWindow(QMainWindow, Ui_MainWindow):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.setupUi(self)
+
+
+if __name__ == '__main__':
+    app = QApplication(sys.argv)
+    window = MainWindow()
+    window.show()
+    sys.exit(app.exec_())

+ 0 - 98
todo/mainwindow.ui

@@ -1,98 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>MainWindow</class>
- <widget class="QMainWindow" name="MainWindow">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>444</width>
-    <height>402</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>Todo</string>
-  </property>
-  <widget class="QWidget" name="centralwidget">
-   <widget class="QListView" name="todoView">
-    <property name="geometry">
-     <rect>
-      <x>10</x>
-      <y>10</y>
-      <width>421</width>
-      <height>192</height>
-     </rect>
-    </property>
-   </widget>
-   <widget class="QPushButton" name="deleteButton">
-    <property name="geometry">
-     <rect>
-      <x>0</x>
-      <y>220</y>
-      <width>113</width>
-      <height>32</height>
-     </rect>
-    </property>
-    <property name="text">
-     <string>Delete</string>
-    </property>
-   </widget>
-   <widget class="QPushButton" name="completeButton">
-    <property name="geometry">
-     <rect>
-      <x>320</x>
-      <y>220</y>
-      <width>113</width>
-      <height>32</height>
-     </rect>
-    </property>
-    <property name="text">
-     <string>Complet</string>
-    </property>
-   </widget>
-   <widget class="QLineEdit" name="todoEdit">
-    <property name="geometry">
-     <rect>
-      <x>10</x>
-      <y>260</y>
-      <width>421</width>
-      <height>21</height>
-     </rect>
-    </property>
-   </widget>
-   <widget class="QPushButton" name="addButton">
-    <property name="geometry">
-     <rect>
-      <x>10</x>
-      <y>300</y>
-      <width>431</width>
-      <height>32</height>
-     </rect>
-    </property>
-    <property name="text">
-     <string>Add Todo</string>
-    </property>
-   </widget>
-  </widget>
-  <widget class="QMenuBar" name="menubar">
-   <property name="geometry">
-    <rect>
-     <x>0</x>
-     <y>0</y>
-     <width>444</width>
-     <height>22</height>
-    </rect>
-   </property>
-  </widget>
-  <widget class="QStatusBar" name="statusbar"/>
- </widget>
- <tabstops>
-  <tabstop>todoView</tabstop>
-  <tabstop>deleteButton</tabstop>
-  <tabstop>todoEdit</tabstop>
-  <tabstop>completeButton</tabstop>
-  <tabstop>addButton</tabstop>
- </tabstops>
- <resources/>
- <connections/>
-</ui>

+ 0 - 60
todo/to_do.py

@@ -1,60 +0,0 @@
-import sys
-
-from PyQt5 import QtCore, uic, QtWidgets
-from PyQt5.QtCore import Qt
-
-qt_creator_file = "mainwindow.ui"
-Ui_MainWindow, QtBaseClass = uic.loadUiType(qt_creator_file)
-
-
-class TodoModel(QtCore.QAbstractListModel):
-    def __init__(self, *args, todos=None, **kwargs):
-        super(TodoModel, self).__init__(*args, **kwargs)
-        self.todos = todos or []
-
-    def data(self, index, role):
-        if role == Qt.DisplayRole:
-            status, text = self.todos[index.row()]
-            return text
-
-    def rowCount(self, index):
-        return len(self.todos)
-
-
-class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
-    def __init__(self):
-        QtWidgets.QMainWindow.__init__(self)
-        Ui_MainWindow.__init__(self)
-        self.setupUi(self)
-        self.module = TodoModel(todos=[(False, '我的第一个 Todo 项目')])
-        self.todoView.setModel(self.module)
-
-        self.addButton.pressed.connect(self.add)
-        self.deleteButton.pressed.connect(self.delete)
-
-    def add(self):
-        """
-        Add an item to our todo list, getting the text from the QLineEdit.todoEdit
-        and then clearing it.
-        :return:
-        """
-        text = self.todoEdit.text()
-        if text:
-            self.module.todos.append((False, text))
-            self.todoEdit.setText("")
-            self.module.layoutChanged.emit()
-
-    def delete(self):
-        indexes = self.todoView.selectedIndexes()
-        if indexes:
-            index = indexes[0]
-            del self.module.todos[index.row()]
-            self.todoView.clearSelection()
-            self.module.layoutChanged.emit()
-
-
-if __name__ == '__main__':
-    app = QtWidgets.QApplication(sys.argv)
-    window = MainWindow()
-    window.show()
-    app.exec_()