天天看點

Python學習筆記 | 資料庫TinyDB配合多視窗界面及表格元件綜合執行個體

作者:知者不惑FYK

本篇學習筆記将利用輕量級資料庫TinyDB存儲資訊,配合Qt多視窗使用者界面,實作資料庫新增、修改、删除和查詢等功能,最終使用Qt的表格元件将資訊展現出來。

Python學習筆記 | 資料庫TinyDB配合多視窗界面及表格元件綜合執行個體

一、輕量級資料庫TinyDB介紹

Python中的TinyDB,是一個純Python編寫的輕量級的面向文檔的資料庫,源碼一共隻有1800行代碼,沒有外部依賴項。TinyDB的資料儲存方式主要是json檔案,也就是 Python 中的字典類型。對于小型項目而言,使用TinyDB資料庫非常适合。它有如下特點:

  • 輕便:源代碼很少,小巧輕便
  • 可随意遷移:TinyDB基于面向文檔的存儲,資料庫隻有一個json檔案,不依賴任何服務,拷貝即可遷移
  • 易用性:TinyDB的API非常簡單和幹淨,易于使用
  • 非依賴性:TinyDB既不需要外部伺服器,也不需要任何來自PyPI的依賴項,支援Python3.6以上的所有版本
  • 可擴充性:在中間件的幫助下,可以輕松擴充TinyDB行為,以滿足特定的需求

(一)建立json資料庫和表

TinyDB是一個第三方包,在使用之前需要先安裝,如指令行格式:pip install tinydb,或者在內建開發環境中安裝。

建立資料庫和表的代碼模闆如下:

from tinydb import TinyDB  # 導入TinyDB類

db=TinyDB('my_db.json')  # 建立名為my_db的json資料庫
table=db.table('student')  # 建立名為student的表

db.close()  # 關閉資料庫           

(二)插入資料

1、插入單條資料

table.insert({'name':'張三','age':23,'class':'三年五班'})
table.insert({'name':'李四','age':'30','class':'三年一班'})           

【注】插入語句insert的傳回值是所添加資料在表中的索引值,表的索引值從1開始,順序遞增。運作以下代碼觀察索引值變化情況:

from tinydb import TinyDB

db=TinyDB('my_db.json')
table=db.table('student')

index1=table.insert({'name':'張三','age':23,'class':'三年五班'})
index2=table.insert({'name':'李四','age':'30','class':'三年一班'})

print(index1,index2)
db.close()

運作結果:
第一次運作的結果是1 2,第二次是3 4,依此類推……           

2、插入多條資料

from tinydb import TinyDB

db = TinyDB('my_db.json')
table = db.table('student')

data = [{'name': '小明', 'age': 13, 'class': '三年五班'}, {'name': '小剛', 'age': '12', 'class': '三年一班'},
        {'name': '小強', 'age': '15', 'class': '三年一班'}, {'name': '小紅', 'age': '13', 'class': '三年一班'},
        {'name': '小花', 'age': '14', 'class': '三年一班'}]
index = table.insert_multiple(data)  # 插入多條資料,即資料以清單形式添加

print(index)
db.close()

運作結果:[1, 2, 3, 4, 5]           

【注】在插入多條資料時,索引值是一個清單。

(三)查詢資料

1、查詢資料庫中的表

num=db.tables()
print(num)

運作結果:
{'student', '_default'}           

【注】'_default'表是預設存在的

2、使用all函數查詢所有資料

string=table.all()
print(string)  # 輸出all函數查詢到的所有結果

運作結果:
[{'name': '小明', 'age': 13, 'class': '三年五班'}, {'name': '小剛', 'age': '12', 'class': '三年一班'}, {'name': '小強', 'age': '15', 'class': '三年一班'}, {'name': '小紅', 'age': '13', 'class': '三年一班'}, {'name': '小花', 'age': '14', 'class': '三年一班'}, {'name': '小明', 'age': 13, 'class': '三年五班'}, {'name': '小剛', 'age': '12', 'class': '三年一班'}, {'name': '小強', 'age': '15', 'class': '三年一班'}, {'name': '小紅', 'age': '13', 'class': '三年一班'}, {'name': '小花', 'age': '14', 'class': '三年一班'}]           

3、使用where進行條件查詢

from tinydb import TinyDB, where  # 需要導入where

str1 = table.search(where('name') == '小花')  # 在表中查詢名字是小花的記錄
str2 = table.search(where('age') > 13)
print(str1)
print(str2)

運作結果:
[{'name': '小花', 'age': '14', 'class': '三年一班'}]
[{'name': '小強', 'age': 15, 'class': '三年一班'}, {'name': '小花', 'age': 14, 'class': '三年一班'}]           

4、使用Query查詢

Query是資料庫查詢元件,使用非常友善靈活,需要事先導入Query。示範代碼如下:

stuQuery = Query()  # 建立查詢對象

query_data = table.search(stuQuery.cls == '三年一班')  # 查詢班級是三年一班的記錄
print(query_data)

query_data = table.search(stuQuery.age <= 13)  # 查詢年齡小于等于13的記錄
print(query_data)

query_data=table.search(stuQuery.age<=13 and stuQuery.cls=='三年五班')
print(query_data)

query_data=table.search((stuQuery.age<13) | (stuQuery.age>14))
print(query_data)

運作結果:
[{'name': '小剛', 'age': 12, 'cls': '三年一班'}, {'name': '小強', 'age': 15, 'cls': '三年一班'}, {'name': '小紅', 'age': 13, 'cls': '三年一班'}, {'name': '小花', 'age': 14, 'cls': '三年一班'}]
[{'name': '小明', 'age': 13, 'cls': '三年五班'}, {'name': '小剛', 'age': 12, 'cls': '三年一班'}, {'name': '小紅', 'age': 13, 'cls': '三年一班'}]
[{'name': '小明', 'age': 13, 'cls': '三年五班'}]
[{'name': '小剛', 'age': 12, 'cls': '三年一班'}, {'name': '小強', 'age': 15, 'cls': '三年一班'}]           

【注】資料庫中的字段名一定不能與系統的保留關鍵字重複,例如上面的學生資料庫的班級字段,如果寫成class,那麼在使用Query查詢時程式就會出錯。

(四)修改資料

1、使用where方法

index = table.update({'cls': '三年二班'}, where('name') == '小剛')
print('修改記錄的索引是:', index)
print(table.search(where('name') == '小剛'))

運作結果:
修改記錄的索引是: [2]
[{'name': '小剛', 'age': 12, 'cls': '三年二班'}]           

2、使用Query方法

index=table.update({'cls': '三年三班'}, stuQuery.name=='小強')
print('修改記錄的索引是:', index)
print(table.search(where('name') == '小強'))

運作結果:
修改記錄的索引是: [3]
[{'name': '小強', 'age': 15, 'cls': '三年三班'}]           

(五)删除資料

1、删除所有資料

table.truncate()
print(table.all())

運作結果:
[]           

2、删除符合條件的資料

del_index = table.remove(stuQuery.cls == '三年一班')  # 删除班級是三年一班的資料
print('删除的索引是:', del_index)
print(table.all())

運作結果:
删除的索引是: [4, 5]
[{'name': '小明', 'age': 13, 'cls': '三年五班'}, {'name': '小剛', 'age': 12, 'cls': '三年二班'}, {'name': '小強', 'age': 15, 'cls': '三年三班'}]           

二、多視窗圖形界面介紹

在實際開發項目時,多視窗圖形界面是經常要使用的,例如登入視窗、點選功能按鈕彈出對應功能實作視窗等等。使用多視窗圖形界面,必須要理清視窗的繼承關系,以及設定視窗的模态屬性,也就是子視窗是否阻塞主視窗。

(一)建立多視窗的基本模型

下面執行個體示範建立多視窗的基礎模型。首先,在Qt設計大師中建立一個主視窗和兩個子視窗,主視窗設定了背景顔色,分别儲存這三個視窗檔案,并将三個視窗的ui檔案轉換成py檔案,準備靜态加載。

1、加載多視窗代碼

from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtGui import Qt
from ui_test1 import Ui_MForm  # 導入視窗1的Ui類
from ui_test2 import Ui_SForm1  # 導入視窗2的Ui類
from ui_test3 import Ui_SForm2  # 導入視窗3的Ui類


# 自定義主視窗的類
class MainWindow(QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.ui1 = Ui_MForm()
        self.ui1.setupUi(self)

        # 設定兩個子視窗對象值為None,代表視窗未打開
        self.s_window1 = None
        self.s_window2 = None

        # 主視窗的按鈕單擊信号和槽
        self.ui1.pushButton.clicked.connect(self.show_Form)

    # 顯示子視窗的方法
    def show_Form(self):
        # 如果子視窗1是未打開狀态,則建立該視窗
        # 沒有這個檢測的環節,點選打開視窗按鈕會反複打開子視窗
        if self.s_window1 is None:
            self.s_window1 = SubWindow1()
            self.s_window1.show()
            self.s_window1.move(300, 200)
        # 如果子視窗2是未打開狀态,則建立該視窗
        if self.s_window2 is None:
            self.s_window2 = SubWindow2()
            self.s_window2.show()
            self.s_window2.move(900, 200)


# 自定義子視窗1的類,繼承主視窗的類MainWindow,是以主視窗的背景顔色設定在子視窗1裡生效
class SubWindow1(MainWindow):
    def __init__(self):
        super(SubWindow1, self).__init__()
        self.ui2 = Ui_SForm1()
        self.ui2.setupUi(self)


# 自定義子視窗2的類,繼承通用元件QWidget,是以主視窗的背景顔色設定在子視窗2裡不生效
class SubWindow2(QWidget):
    def __init__(self):
        super(SubWindow2, self).__init__()
        self.ui3 = Ui_SForm2()
        self.ui3.setupUi(self)


if __name__ == '__main__':
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec()           

【注】子視窗1的自定義類繼承了主視窗的自定義類MainWindow,子視窗2的自定義類繼承通用元件QWidget,是以子視窗1的背景顔色跟主視窗一樣,而子視窗2沒有背景顔色。

2、效果示範

視訊加載中...

(二)實作子視窗跟随主視窗一起關閉

在上面多視窗基本模型的視訊示範中可以看到,主視窗和兩個子視窗可以自由關閉,但是當主視窗關閉時,子視窗并不能跟随關閉。為了解決這個問題,我們要重寫主類的closeEvent關閉事件方法。

1、重寫主類closeEvent方法代碼

# 一定要在主視窗的自定義類裡書寫此代碼
def closeEvent(self, event):
    sys.exit(0)           

2、效果示範

視訊加載中...

【注】程式運作後,關閉主視窗則子視窗1和子視窗2都跟着關閉;關閉子視窗1,主視窗和子視窗2也跟着關閉,因為子視窗1繼承自主視窗類,關閉事件跟主視窗是相同的;關閉子視窗2不會影響主視窗和子視窗1。

(三)設定子視窗模态屬性

在做多視窗項目開發時,有時要求子視窗不能同時打開,以免處理不好發生資料混亂的情況,例如新增資料和删除資料子視窗。為此,我們就需要用子視窗來阻塞主視窗程序,這裡就需要設定子視窗的模态屬性。

1、在Qt設計大師中設定

點選需要設定模态屬性的子視窗,在視窗的QWidget屬性設定中找到windowModality,将屬性值改成ApplicationModal,儲存視窗并将相應的ui檔案轉換成py檔案即可。模态屬性設定如下圖:

Python學習筆記 | 資料庫TinyDB配合多視窗界面及表格元件綜合執行個體

設定子視窗的模态屬性

2、在代碼中設定

self.s_window2 = SubWindow2()
# 子視窗模态屬性設定一定要放在show()方法之前
self.s_window2.setWindowModality(Qt.ApplicationModal)
self.s_window2.show()           

(四)多視窗示範完整代碼

import sys
from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtGui import Qt
from ui_test1 import Ui_MForm  # 導入視窗1的Ui類
from ui_test2 import Ui_SForm1  # 導入視窗2的Ui類
from ui_test3 import Ui_SForm2  # 導入視窗3的Ui類


# 自定義主視窗的類
class MainWindow(QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.ui1 = Ui_MForm()
        self.ui1.setupUi(self)

        # 設定兩個子視窗對象值為None,代表視窗未打開
        self.s_window1 = None
        self.s_window2 = None

        # 主視窗的按鈕單擊信号和槽
        self.ui1.pushButton.clicked.connect(self.show_Form)

    # 顯示子視窗的方法
    def show_Form(self):
        # 如果子視窗1是未打開狀态,則建立該視窗
        if self.s_window1 is None:
            self.s_window1 = SubWindow1()
            self.s_window1.show()
            self.s_window1.move(300, 200)
        # 如果子視窗2是未打開狀态,則建立該視窗
        if self.s_window2 is None:
            self.s_window2 = SubWindow2()
            # 設定子視窗2的模态方式為阻塞主程式,或者在Qt設計大師的視窗屬性裡設定
            # 程式運作後,隻有子視窗2是活動的,主視窗不可點選,隻有子視窗2關閉後才可以操作主視窗
            self.s_window2.setWindowModality(Qt.ApplicationModal)
            self.s_window2.show()
            self.s_window2.move(900, 200)

    # 重寫父類的關閉事件方法,實作關閉主視窗後子視窗跟着關閉的功能
    def closeEvent(self, event):
        sys.exit(0)


# 自定義子視窗1的類,繼承主視窗的類MainWindow,是以主視窗的背景顔色設定在子視窗1裡生效
class SubWindow1(MainWindow):
    def __init__(self):
        super(SubWindow1, self).__init__()
        self.ui2 = Ui_SForm1()
        self.ui2.setupUi(self)


# 自定義子視窗2的類,繼承通用元件QWidget,是以主視窗的背景顔色設定在子視窗2裡不生效
class SubWindow2(QWidget):
    def __init__(self):
        super(SubWindow2, self).__init__()
        self.ui3 = Ui_SForm2()
        self.ui3.setupUi(self)


if __name__ == '__main__':
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec()           

視訊加載中...

三、綜合執行個體開發

在介紹圖形界面開發之PySimpleGUI子產品時,我們使用SQLite3和PySimpleGUI子產品制作了一個員工檔案資訊管理系統,現在我們使用PySide6和TinyDB對該系統進行重新開發,旨在加深對PySide6和TinyDB的了解,并未在安全性方面做任何處理。

(一)準備資料庫

1、資料庫描述

本案例采用輕量級資料庫TinyDB存儲員工檔案資訊。資料庫、表和字段設定如下:

【資料庫檔案】:company.json

【檔案資訊表】:employee
【字段】:編号、姓名、性别、出生年月、民族、籍貫、黨派、學曆、職稱、身份證号碼
電話号碼、所在部門、獲得的榮譽、所受的處罰

【使用者名密碼儲存表】:user
字段:uid、pwd           

2、初始化資料庫

首先建立一個init.py檔案,使用代碼建立資料庫和使用者名密碼儲存表,并插入一條預設使用者名和密碼資料,代碼如下:

from tinydb import TinyDB

db = TinyDB('company.json')  # 建立json資料庫,資料庫名稱為company.json

table = db.table('user')  # 建立用來存儲使用者名和密碼的表,表名為user

table.insert({'uid': 'admin', 'pwd': '1111'})  # 設定初始使用者名和密碼

db.close()           

(二)設計視窗

視窗包括:主視窗,登入視窗,新增資訊視窗,查詢/删除資訊視窗,修改資訊視窗和修改密碼視窗。

1、主視窗

在Qt設計大師裡建立主視窗時,最好使用Main Window模版,如果不需要菜單欄、工具欄和位址欄可以手動去除。Main Window類的視窗功能比較健全,設定背景圖檔、布局等都不會出現問題。采用Form類建立的視窗,在代碼中建立視窗類時,如果繼承QMainWindow類,那麼布局會混亂;如果繼承QWidget類,那麼背景圖檔就會不顯示。

我在做這個執行個體時,就是采用Form模版建立的主視窗,然後在代碼中建立視窗類時繼承QWidget類,就發生了背景圖檔不顯示的情況,折磨了很久才找到原因。由于不想重新設計主視窗,于是将主視窗的最大尺寸和最小尺寸均設定成視窗實際尺寸,讓視窗不能通過拖動改變尺寸,也就不需要布局了,最後在建立視窗類的代碼中繼承QMainWindow類,總算解決了背景圖檔不顯示的問題。

  • 視窗ui檔案名稱:ui_main.ui
  • 視窗對象名稱:MainForm,由于是多視窗應用,是以視窗對象的名稱最好不采用預設的Form或者MainWindow
  • 包含元件:四個Tool Button,工具按鈕的toolButtonStyle屬性設定為ToolButtonTextUnderIcon,即按鈕文本在圖示下面。autoRaise屬性設定為真,即工具按鈕自動浮起,否則是凹陷狀态,按鈕周圍有外框影響美觀
  • 背景圖檔:在視窗對象的樣式表中,使用#MainForm{}的形式設定背景圖檔,這是為了隻給名稱為MainForm的元件即視窗添加背景圖檔,否則視窗上的所有元素都會加載這個背景圖檔。樣式表代碼如下:
#MainForm{background-image: url(:/images/icon/background.jpg);}           
  • 視窗外觀:如下圖
Python學習筆記 | 資料庫TinyDB配合多視窗界面及表格元件綜合執行個體

主視窗示例圖檔

2、登入視窗

設計登入視窗不要采用Dialog模版,無論點選OK還是Cancel按鈕,Dialog類的視窗都會關閉,不能持久顯示。

  • 視窗ui檔案名稱:ui_login.ui
  • 視窗對象名稱:LoginForm
  • 包含元件:兩個标簽元件,兩個單行文本輸入框元件,一個按鈕元件
  • 背景圖檔:與主視窗設定背景圖檔方法相同
  • 視窗外觀:如下圖
Python學習筆記 | 資料庫TinyDB配合多視窗界面及表格元件綜合執行個體

登入視窗示例圖檔

3、新增資訊視窗

  • 視窗ui檔案名稱:ui_add.ui
  • 視窗對象名稱:AddForm
  • 包含元件:一個Group Box容器類元件,用來裝載錄入員工基本資訊的所有元件,起到歸類的作用。一個Tab Widget标簽元件,用來放置“獲得的榮譽”和“所受的處罰”兩個多行文本輸入框,節省空間。一個Table Widget表格元件,用來顯示新錄入的員工資訊。剩餘是标簽、單行文本輸入框和按鈕元件,整個視窗設定了布局,支援拖放改變視窗大小。另外,在視窗的樣式表中對表格元件的标題欄進行了設定,樣式表代碼如下:
QHeaderView::section{;background-color:rgb(225,225,225);height:28px;}           
  • 背景圖檔:無
  • 視窗外觀:如下圖
Python學習筆記 | 資料庫TinyDB配合多視窗界面及表格元件綜合執行個體

新增資訊視窗示例圖檔

4、查詢/删除資訊視窗

  • 視窗ui檔案名稱:ui_search_del.ui
  • 視窗對象名稱:SDForm
  • 包含元件:一個Table Widget表格元件,标題欄設定與新增資訊中相同。剩餘是标簽、單行文本輸入框和按鈕元件,整個視窗設定了布局,支援拖放改變視窗大小
  • 背景圖檔:無
  • 視窗外觀:如下圖
Python學習筆記 | 資料庫TinyDB配合多視窗界面及表格元件綜合執行個體

查詢/删除資訊視窗示例圖檔

5、修改資訊視窗

  • 視窗ui檔案名稱:ui_change.ui
  • 視窗對象名稱:ChangeForm
  • 包含元件:與新增資訊視窗基本相同
  • 背景圖檔:無
  • 視窗外觀:如下圖
Python學習筆記 | 資料庫TinyDB配合多視窗界面及表格元件綜合執行個體

修改資訊視窗示例圖檔

(三)代碼部分

1、登入視窗和主視窗顯示代碼

(1)思路分析

  • 建立登入視窗類
  • 建立主視窗類
  • 建立登入視窗執行個體對象
  • 顯示登入視窗,即程式運作首先顯示登入視窗
  • 判斷登入是否成功,如果登入成功,則關閉登入視窗本身,然後建立主視窗執行個體對象并顯示主視窗

(2)執行個體代碼

from PySide6.QtWidgets import QApplication, QMainWindow, QMessageBox
from tinydb import TinyDB, Query
from ui_login import Ui_LoginForm  # 導入登入視窗ui檔案
from ui_main import Ui_MainForm  # 導入主視窗ui檔案


# 建立登入視窗類,這裡必須繼承QMainWindow類,如果繼承QWidget,那麼背景圖檔無法顯示
class LoginWindow(QMainWindow):
    def __init__(self):
        super(LoginWindow, self).__init__()
        self.uiLogin = Ui_LoginForm()
        self.uiLogin.setupUi(self)

        self.main_window = None  # 預設主視窗對象

        # 密碼輸入框回車和登入按鈕單擊信号,都連接配接到登入方法
        self.uiLogin.lineEdit_2.returnPressed.connect(self.login)
        self.uiLogin.pushButton.clicked.connect(self.login)

    # 登入方法
    def login(self):
        if self.uiLogin.lineEdit.text() == '' or self.uiLogin.lineEdit_2.text() == '':
            return
        db = TinyDB('company.json')
        table = db.table('user')
        query = Query()
        username = 'admin'
        # 搜尋結果是清單包含字典的格式,是以需要先用索引值獲得字典資料,再用字典的key擷取value值
        password = table.search(query.uid == username)[0]['pwd']
        if self.uiLogin.lineEdit.text() == username and self.uiLogin.lineEdit_2.text() == password:
            self.close()  # 如果登入成功,則登入視窗本身關閉。因為不再使用登入視窗,是以沒有使用隐藏方法。
            self.main_window = MainWindow()  # 建立主視窗執行個體對象
            self.main_window.show()  # 顯示主視窗
        else:
            QMessageBox.information(self, '提示資訊', '使用者名或密碼錯誤,請重新輸入!')


# 建立主視窗類,同樣要繼承QMainWindow類,以顯示背景圖檔
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.uiMain = Ui_MainForm()
        self.uiMain.setupUi(self)


if __name__ == '__main__':
    app = QApplication([])
    window = LoginWindow()  # 建立登入視窗執行個體對象
    window.show()  # 顯示登入視窗
    app.exec()           

2、主程式完整代碼

在主程式中,除了上面介紹的登入視窗和主程式視窗顯示代碼外,剩下就是顯示新增資訊、查詢/删除資訊和修改資訊視窗的代碼。每個視窗具體功能實作部分,都在各自的py檔案中,在主程式中将這些py檔案以子產品的形式導入,再引用子產品内的視窗類就可以了。修改密碼功能,在主程式中直接采用QInputDialog輸入對話框完成。主程式檔案名為mainProgram.py,完整代碼如下:

from PySide6.QtWidgets import QApplication, QMainWindow, QMessageBox, QInputDialog
from PySide6.QtGui import Qt
from tinydb import TinyDB, Query
from ui_login import Ui_LoginForm
from ui_main import Ui_MainForm
from add import AddWindow  # 導入新增資訊子產品即py檔案中的視窗類
from search_del import SDWindow  # 導入搜尋/删除資訊子產品中的視窗類
from change import ChangeWindow  # 導入修改資訊子產品中的視窗類


# 建立登入視窗類,這裡必須繼承QMainWindow類,如果繼承QWidget,那麼背景圖檔無法顯示
class LoginWindow(QMainWindow):
    def __init__(self):
        super(LoginWindow, self).__init__()
        self.uiLogin = Ui_LoginForm()
        self.uiLogin.setupUi(self)

        self.main_window = None  # 預設主視窗對象

        # 密碼輸入框回車和登入按鈕單擊信号,都連接配接到登入方法
        self.uiLogin.lineEdit_2.returnPressed.connect(self.login)
        self.uiLogin.pushButton.clicked.connect(self.login)

    # 登入方法
    def login(self):
        if self.uiLogin.lineEdit.text() == '' or self.uiLogin.lineEdit_2.text() == '':
            return
        db = TinyDB('company.json')
        table = db.table('user')
        query = Query()
        username = 'admin'
        # 搜尋結果是清單包含字典的格式,是以需要先用索引值獲得字典資料,再用字典的key擷取value值
        password = table.search(query.uid == username)[0]['pwd']
        db.close()
        if self.uiLogin.lineEdit.text() == username and self.uiLogin.lineEdit_2.text() == password:
            self.close()  # 如果登入成功,則登入視窗本身關閉。因為不再使用登入視窗,是以沒有使用隐藏方法。
            self.main_window = MainWindow()  # 建立主視窗執行個體對象
            self.main_window.show()  # 顯示主視窗
        else:
            QMessageBox.information(self, '提示資訊', '使用者名或密碼錯誤,請重新輸入!')


# 建立主視窗類,同樣要繼承QMainWindow類,以顯示背景圖檔
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.uiMain = Ui_MainForm()
        self.uiMain.setupUi(self)

        self.add_window = None  # 預設新增資訊視窗對象
        self.search_window = None  # 預設搜尋删除資訊視窗對象
        self.change_window = None  # 預設修改資訊視窗對象

        self.uiMain.toolButton_add.clicked.connect(self.show_add)  # 新增資訊按鈕點選信号和槽
        self.uiMain.toolButton_search.clicked.connect(self.show_search)  # 查詢删除資訊按鈕點選信号和槽
        self.uiMain.toolButton_change.clicked.connect(self.show_change)  # 新增資訊按鈕點選信号和槽
        self.uiMain.toolButton_pwd.clicked.connect(self.show_pwd)  # 修改密碼按鈕單擊信号和槽

    # 顯示新增資訊視窗方法
    def show_add(self):
        self.add_window = AddWindow()
        self.add_window.setWindowModality(Qt.ApplicationModal)  # 為視窗設定模态屬性
        self.add_window.show()

    # 顯示查詢/删除資訊視窗方法
    def show_search(self):
        self.search_window = SDWindow()
        self.search_window.setWindowModality(Qt.ApplicationModal)  # 為視窗設定模态屬性
        self.search_window.show()

    # 顯示修改資訊視窗方法
    def show_change(self):
        self.change_window = ChangeWindow()
        self.change_window.setWindowModality(Qt.ApplicationModal)  # 為視窗設定模态屬性
        self.change_window.show()

    # 修改密碼方法
    def show_pwd(self):
        pwd1, okPressed1 = QInputDialog.getText(self, '修改密碼', '請輸入新密碼:')
        if okPressed1:
            pwd2, okPressed2 = QInputDialog.getText(self, '确認修改', '請再次輸入新密碼:')
            if okPressed2:
                if pwd1 == pwd2:
                    db = TinyDB('company.json')
                    table = db.table('user')
                    query = Query()
                    table.update({'pwd': pwd1}, query.uid == 'admin')
                    db.close()
                    QMessageBox.information(self, '修改密碼', '密碼修改成功!')
                else:
                    QMessageBox.information(self, '修改密碼', '兩次輸入不相同,請重新輸入!')


if __name__ == '__main__':
    app = QApplication([])
    window = LoginWindow()  # 建立登入視窗執行個體對象
    window.show()  # 顯示登入視窗
    app.exec()           

3、新增資訊子產品代碼

新增資訊子產品名為add.py,代碼如下:

import copy
from PySide6.QtWidgets import QApplication, QWidget, QMessageBox, QTableWidgetItem
from PySide6.QtGui import Qt
from tinydb import TinyDB
from ui_add import Ui_AddForm


# 建立新增資訊視窗類
class AddWindow(QWidget):
    def __init__(self):
        super(AddWindow, self).__init__()
        self.uiAdd = Ui_AddForm()
        self.uiAdd.setupUi(self)

        self.add_list = []  # 定義存儲所有新增資訊的清單,為的是往資料庫裡添加
        self.add_dict = dict()  # 定義存儲每行新增資訊的字典

        self.uiAdd.pushButton.clicked.connect(self.addList)  # 添加到新增清單按鈕信号和槽
        self.uiAdd.pushButton_2.clicked.connect(self.saveDB)  # 儲存到資料庫按鈕信号和槽

        self.table_init()  # 調用table表格初始化方法
        self.id_init()  # 調用編号初始化方法

    # 編号初始化方法,自動生成編号
    def id_init(self):
        db = TinyDB('company.json')
        table = db.table('employee')
        content = table.all()  # 讀取資料表的所有内容
        rows = self.uiAdd.tableWidget.rowCount()  # 擷取Table的行數
        
        if rows == 0 and content == []:
            self.uiAdd.edit_0.setText('A001')  # 如果資料表和Table中的内容都是空的,則編号設定為A001
            self.uiAdd.edit_1.setFocus()  # 将焦點設定在姓名輸入框
            return
        if rows != 0:  # 如果Table中有資料,則取得最後一行的編号
            id_data = self.uiAdd.tableWidget.item(rows - 1, 0).text()
        else:
            id_data = content[-1]['編号']  # 如果Table中沒有資料,則取得資料表中最後一條記錄的編号資料

        id_new = 'A%03d' % (int(id_data[1:]) + 1)  # 将原有編号後面數字加1,一共顯示3位,前面用0補充
        self.uiAdd.edit_0.setText(id_new)  # 将新增資訊視窗的編号輸入框内容設定為新編号
        self.uiAdd.edit_1.setFocus()  # 将焦點設定在姓名輸入框

    # 表格初始化方法
    def table_init(self):
        # 調整各列的列寬
        self.uiAdd.tableWidget.setColumnWidth(0, 50)
        self.uiAdd.tableWidget.setColumnWidth(1, 60)
        self.uiAdd.tableWidget.setColumnWidth(2, 40)
        self.uiAdd.tableWidget.setColumnWidth(3, 60)
        self.uiAdd.tableWidget.setColumnWidth(4, 40)
        self.uiAdd.tableWidget.setColumnWidth(5, 60)
        self.uiAdd.tableWidget.setColumnWidth(6, 60)
        self.uiAdd.tableWidget.setColumnWidth(7, 60)
        self.uiAdd.tableWidget.setColumnWidth(8, 80)
        self.uiAdd.tableWidget.setColumnWidth(9, 150)
        self.uiAdd.tableWidget.setColumnWidth(10, 90)

    # 添加到新增清單按鈕單擊方法
    def addList(self):
        # 判斷編号和姓名輸入框内容是否為空
        if self.uiAdd.edit_0.text() == '' or self.uiAdd.edit_1.text() == '':
            QMessageBox.critical(self, '錯誤資訊', '編号和姓名不能為空,請重新輸入')
        else:
            row = self.uiAdd.tableWidget.rowCount()  # 擷取表格總行數
            self.uiAdd.tableWidget.setRowCount(row + 1)  # 表格目前行等于總行數加1

            # 将單行文本輸入框的名稱設定為連續序列形式,如edit_0、edit_1……,利用eval函數循環将各個單行文本輸入框内容添加到表格目前行中
            for col in range(12):
                label_text = eval(f'self.uiAdd.label_{col}.text()')
                edit_text = eval(f'self.uiAdd.edit_{col}.text()')
                self.uiAdd.tableWidget.setItem(row, col, QTableWidgetItem(edit_text))
                # 設定單元格對齊方式,需要使用Qt類的AlignmentFlag屬性
                self.uiAdd.tableWidget.item(row, col).setTextAlignment(Qt.AlignmentFlag.AlignCenter)

                # 建構新增資訊的字典資料,下面語句還可以寫成self.add_dict.update({f'{label_text}':f'{edit_text}'})
                self.add_dict[f'{label_text}'] = edit_text

            # 單行文本輸入框内容添加完畢後,将兩個多行文本輸入框的内容添加到最後兩列中
            edit_text = self.uiAdd.plainTextEdit_0.toPlainText()
            self.uiAdd.tableWidget.setItem(row, 12, QTableWidgetItem(edit_text))
            self.add_dict['獲得的榮譽'] = edit_text  # 字典裡也添加該資訊
            edit_text = self.uiAdd.plainTextEdit_1.toPlainText()
            self.uiAdd.tableWidget.setItem(row, 13, QTableWidgetItem(edit_text))
            self.add_dict['所受的處罰'] = edit_text
            # 将一行資訊的字典添加到清單中,這裡必須對字典進行深拷貝,否則下次輸入改變字典内容,清單内容随着更改
            self.add_list.append(copy.deepcopy(self.add_dict))

            # 使用跟上面相同方法,将所有輸入框内容清除
            for i in range(12):
                eval(f'self.uiAdd.edit_{i}.clear()')
            self.uiAdd.plainTextEdit_0.clear()
            self.uiAdd.plainTextEdit_1.clear()
            self.id_init()  # 所有輸入框内容清空後,調用編号初始化方法,自動生成編号

    # 儲存到資料庫按鈕單擊方法
    def saveDB(self):
        choice = QMessageBox.question(self, '确認資訊', '确認要将資料儲存到資料庫嗎?')
        if choice == QMessageBox.No:
            return
        db = TinyDB('company.json')
        table = db.table('employee')
        table.insert_multiple(self.add_list)
        QMessageBox.information(self, '提示資訊', '資料儲存成功!')
        print(table.all())
        self.uiAdd.tableWidget.clearContents()  # 清除表格内容,但不清除标題,clear方法會連标題都清除
        self.uiAdd.tableWidget.setRowCount(0)
        self.add_list.clear()


if __name__ == '__main__':
    app = QApplication([])
    window = AddWindow()
    window.show()
    app.exec()           

運作效果:

視訊加載中...

4、查詢/删除資訊子產品代碼

新增資訊子產品名為search_del.py,代碼如下:

from PySide6.QtWidgets import QApplication, QWidget, QTableWidgetItem, QMessageBox
from PySide6 import QtCore
from PySide6.QtGui import Qt
from ui_search_del import Ui_SDForm
from tinydb import TinyDB, Query


class SDWindow(QWidget):
    def __init__(self):
        super(SDWindow, self).__init__()
        self.uiSD = Ui_SDForm()
        self.uiSD.setupUi(self)

        self.table_init()  # 調用表格初始化方法

        self.uiSD.button_s.clicked.connect(self.search)  # 送出搜尋按鈕單擊信号
        self.uiSD.button_d.clicked.connect(self.delete)  # 删除按鈕單擊信号

    # 表格初始化方法
    def table_init(self):
        # 調整各列的列寬
        self.uiSD.tableWidget.setColumnWidth(0, 50)
        self.uiSD.tableWidget.setColumnWidth(1, 60)
        self.uiSD.tableWidget.setColumnWidth(2, 40)
        self.uiSD.tableWidget.setColumnWidth(3, 60)
        self.uiSD.tableWidget.setColumnWidth(4, 40)
        self.uiSD.tableWidget.setColumnWidth(5, 60)
        self.uiSD.tableWidget.setColumnWidth(6, 60)
        self.uiSD.tableWidget.setColumnWidth(7, 60)
        self.uiSD.tableWidget.setColumnWidth(8, 80)
        self.uiSD.tableWidget.setColumnWidth(9, 150)
        self.uiSD.tableWidget.setColumnWidth(10, 90)

    def search(self):
        db = TinyDB('company.json')
        table = db.table('employee')
        query = Query()
        self.query_data = []
        # 按照所輸入位置和内容搜尋資訊
        if self.uiSD.edit_id.text():
            self.query_data = table.search(query.編号 == self.uiSD.edit_id.text())
        if self.uiSD.edit_name.text():
            self.query_data = table.search(query.姓名 == self.uiSD.edit_name.text())
        if self.uiSD.edit_card.text():
            self.query_data = table.search(query.身份證号碼 == self.uiSD.edit_card.text())
        if self.uiSD.edit_cls.text():
            self.query_data = table.search(query.所在部門 == self.uiSD.edit_cls.text())

        db.close()
        self.show_data(self.query_data)  # 調用顯示資料方法

    # 顯示資料方法
    def show_data(self, data):
        row = 0  # 初始行為0
        self.uiSD.tableWidget.setRowCount(len(data))  # 設定表格總行數為搜尋到的記錄數
        for item in data:
            col = 0  # 初始列為0
            for key in item:
                self.uiSD.tableWidget.setItem(row, col, QTableWidgetItem(item[key]))
                # 設定單元格對齊方式,需要使用Qt類的AlignmentFlag屬性
                self.uiSD.tableWidget.item(row, col).setTextAlignment(Qt.AlignmentFlag.AlignCenter)
                col += 1
            row += 1

    # 删除資訊按鈕單擊方法
    def delete(self):
        selected_data = self.uiSD.tableWidget.selectedItems()  # 擷取選中的所有表格項目
        self.id_list = []  # 定義存儲選中資訊的編号清單
        rows = len(selected_data) // 14  # 計算一共選中幾行資訊,因為擷取的資料是所有單元格,每行有14個單元格

        for i in range(rows):
            self.id_list.append(selected_data[i * 14].text())  # 循環擷取所有準備删除的編号資料,放置在清單中

        choice = QMessageBox.question(self, '删除資訊', f'确認要删除編号為:{self.id_list}的資料嗎?')
        if choice == QMessageBox.No:
            return

        db = TinyDB('company.json')
        table = db.table('employee')
        query = Query()
        # 将資料庫中包含要删除編号的記錄全部删掉
        for i in self.id_list:
            table.remove(query.編号 == i)
        db.close()

        self.search()  # 調用查詢方法,更新表格顯示

        QMessageBox.information(self,'删除資訊','資訊删除成功!')


if __name__ == '__main__':
    app = QApplication([])
    window = SDWindow()
    window.show()
    app.exec()           

運作效果:

視訊加載中...

5、修改資訊子產品代碼

修改資訊子產品名為change.py,代碼如下:

from PySide6.QtWidgets import QApplication, QWidget, QMessageBox
from tinydb import TinyDB, Query
from ui_change import Ui_ChangeForm


# 建立修改資訊視窗類
class ChangeWindow(QWidget):
    def __init__(self):
        super(ChangeWindow, self).__init__()
        self.uiChange = Ui_ChangeForm()
        self.uiChange.setupUi(self)
        self.load_data = []  # 預設存儲要修改資訊清單變量
        self.uiChange.pushButton.clicked.connect(self.load)  # 讀入資訊按鈕信号
        self.uiChange.pushButton_2.clicked.connect(self.save)  # 儲存資訊按鈕信号

    # 讀入資訊方法
    def load(self):
        if not self.uiChange.edit_0.text():
            QMessageBox.information(self, '提示資訊', '請輸入要修改資訊的編号,然後點讀入資訊按鈕!')
            return
        db = TinyDB('company.json')
        table = db.table('employee')
        query = Query()
        self.load_data = table.search(query.編号 == self.uiChange.edit_0.text())
        db.close()
        if len(self.load_data) == 0:
            QMessageBox.information(self, '提示資訊', '輸入的編号不存在,請重新輸入!')
            return
        i = 0
        for item in self.load_data[0].values():
            if i in (0, 12, 13):  # 跳過第0、12和13條項目
                i+=1
                continue
            eval(f'self.uiChange.edit_{i}.setEnabled(True)')  # 循環設定單行文本輸入框的可用狀态為真
            eval(f'self.uiChange.edit_{i}.setText(item)')  # 設定文本輸入框的内容為讀入的相應資料
            i += 1

        # 設定兩個多行文本編輯器的狀态和内容
        self.uiChange.plainTextEdit_0.setEnabled(True)
        self.uiChange.plainTextEdit_1.setEnabled(True)
        self.uiChange.plainTextEdit_0.setPlainText(self.load_data[0]['獲得的榮譽'])
        self.uiChange.plainTextEdit_1.setPlainText(self.load_data[0]['所受的處罰'])

    # 儲存資訊方法
    def save(self):
        db = TinyDB('company.json')
        table = db.table('employee')
        query = Query()
        for i in range(12):  # 以相應的标簽的文本為key,以對應的單行文本輸入框的内容為value,更新資料庫内容
            key=eval(f'self.uiChange.label_{i}.text()')
            value=eval(f'self.uiChange.edit_{i}.text()')
            table.update({key:value},query.編号==self.uiChange.edit_0.text())

        table.update({'獲得的榮譽':self.uiChange.plainTextEdit_0.toPlainText()},query.編号==self.uiChange.edit_0.text())
        table.update({'所受的處罰':self.uiChange.plainTextEdit_1.toPlainText()},query.編号==self.uiChange.edit_0.text())

        QMessageBox.information(self,'提示資訊','資訊儲存成功!')


if __name__ == '__main__':
    app = QApplication([])
    window = ChangeWindow()
    window.show()
    app.exec()           

運作效果:

視訊加載中...

6、修改密碼功能

修改密碼功能沒有設計視窗,而是在主程式中直接采用QInputDialog輸入對話框完成。代碼如下:

# 修改密碼方法
def show_pwd(self):
    pwd1, okPressed1 = QInputDialog.getText(self, '修改密碼', '請輸入新密碼:')
    if okPressed1:
        pwd2, okPressed2 = QInputDialog.getText(self, '确認修改', '請再次輸入新密碼:')
        if okPressed2:
            if pwd1 == pwd2:
                db = TinyDB('company.json')
                table = db.table('user')
                query = Query()
                table.update({'pwd': pwd1}, query.uid == 'admin')
                db.close()
                QMessageBox.information(self, '修改密碼', '密碼修改成功!')
            else:
                QMessageBox.information(self, '修改密碼', '兩次輸入不相同,請重新輸入!')           

(四)整體效果示範

視訊加載中...