2025年10月17日 星期五

SQLite 資料庫 -4 一對一關聯

 SQLite 資料庫 -4   一對一關聯






一對一關聯

操作步驟:

  • 1. 建立資料庫: 點擊「1. 建立資料庫」,它會創建 MySchoolDB.db 檔案和 score2 資料表。

  • 2. 新增資料: 點擊「2. 新增資料」,輸入學號和兩門課程的分數後確定。

  • 5. 顯示所有資料: 點擊「5. 顯示所有資料」,成績資料會顯示在下方的表格中。

  • 3. 更正資料: 在表格中點選一列資料,然後點擊「3. 更正資料」,可以修改分數但不能修改學號。

  • 4. 刪除資料: 在表格中點選一列資料,然後點擊「4. 刪除資料」,確認後該筆成績會被刪除。

  • 6. 刪除資料庫: 點擊「6. 刪除資料庫」會刪除整個 MySchoolDB.db 檔案。


import tkinter as tk

from tkinter import messagebox, simpledialog, ttk

import sqlite3


# --- 資料庫操作類別 ---

class DatabaseManager:

    def __init__(self, db_name="MySchoolDB.db"):

        self.db_name = db_name

        self.conn = None

        self.cursor = None


    def connect(self):

        """建立資料庫連線"""

        try:

            self.conn = sqlite3.connect(self.db_name)

            self.cursor = self.conn.cursor()

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"無法連線到資料庫: {e}")

            self.conn = None

            self.cursor = None


    def close(self):

        """關閉資料庫連線"""

        if self.conn:

            self.conn.close()


    def create_tables(self):

        """1. 建立資料庫(如果不存在)及資料表 student2 和 score2"""

        self.connect()

        if not self.conn:

            return False


        try:

            # 建立 student2 資料表

            self.cursor.execute('''

                CREATE TABLE IF NOT EXISTS student2 (

                    學號 TEXT PRIMARY KEY,

                    姓名 TEXT NOT NULL,

                    性別 TEXT

                )

            ''')

            

            # 建立 score2 資料表 (使用學號作為主鍵和外鍵,實現一對一)

            self.cursor.execute('''

                CREATE TABLE IF NOT EXISTS score2 (

                    學號 TEXT PRIMARY KEY,

                    資料庫 INTEGER,

                    手機程式 INTEGER,

                    FOREIGN KEY (學號) REFERENCES student2(學號) ON DELETE CASCADE

                )

            ''')

            self.conn.commit()

            return True

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"建立資料表失敗: {e}")

            return False

        finally:

            self.close()


    def insert_data(self, stu_data, score_data):

        """2. 新增資料 (同時新增學生資料和成績資料)"""

        self.connect()

        if not self.conn:

            return False

        

        stu_id, name, gender = stu_data

        db_score, mobile_score = score_data

        

        try:

            # 檢查學號是否已存在

            self.cursor.execute("SELECT 學號 FROM student2 WHERE 學號=?", (stu_id,))

            if self.cursor.fetchone():

                messagebox.showwarning("新增失敗", f"學號 {stu_id} 已存在!")

                return False


            # 插入 student2

            self.cursor.execute("INSERT INTO student2 (學號, 姓名, 性別) VALUES (?, ?, ?)", (stu_id, name, gender))

            

            # 插入 score2

            self.cursor.execute("INSERT INTO score2 (學號, 資料庫, 手機程式) VALUES (?, ?, ?)", (stu_id, db_score, mobile_score))

            

            self.conn.commit()

            return True

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"新增資料失敗: {e}")

            self.conn.rollback()

            return False

        finally:

            self.close()


    def update_data(self, stu_id, stu_data=None, score_data=None):

        """3. 更正資料 (更新學生資料或成績資料)"""

        self.connect()

        if not self.conn:

            return False

        

        try:

            # 檢查學號是否存在

            self.cursor.execute("SELECT 學號 FROM student2 WHERE 學號=?", (stu_id,))

            if not self.cursor.fetchone():

                messagebox.showwarning("更正失敗", f"學號 {stu_id} 不存在!")

                return False


            if stu_data:

                name, gender = stu_data

                self.cursor.execute("UPDATE student2 SET 姓名=?, 性別=? WHERE 學號=?", (name, gender, stu_id))

            

            if score_data:

                db_score, mobile_score = score_data

                # 嘗試更新 score2,如果不存在則插入 (因為是一對一,所以學生表有資料,成績表也應該有,但為保險起見使用 REPLACE)

                # 但一般情況下,一對一的資料應該同時建立,這裡使用 UPDATE

                self.cursor.execute("UPDATE score2 SET 資料庫=?, 手機程式=? WHERE 學號=?", (db_score, mobile_score, stu_id))


            self.conn.commit()

            return True

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"更正資料失敗: {e}")

            self.conn.rollback()

            return False

        finally:

            self.close()


    def delete_data(self, stu_id):

        """4. 刪除資料 (刪除學生資料和相關成績資料)"""

        self.connect()

        if not self.conn:

            return False


        try:

            # 檢查學號是否存在

            self.cursor.execute("SELECT 學號 FROM student2 WHERE 學號=?", (stu_id,))

            if not self.cursor.fetchone():

                messagebox.showwarning("刪除失敗", f"學號 {stu_id} 不存在!")

                return False


            # 刪除 student2 的資料。由於 score2 設有 ON DELETE CASCADE,相關的成績也會被刪除。

            self.cursor.execute("DELETE FROM student2 WHERE 學號=?", (stu_id,))

            

            self.conn.commit()

            return True

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"刪除資料失敗: {e}")

            self.conn.rollback()

            return False

        finally:

            self.close()

            

    def get_all_data(self):

        """5. 顯示所有資料 (連線 student2 和 score2)"""

        self.connect()

        if not self.conn:

            return []


        try:

            # 使用 LEFT JOIN 來連線兩個表,即使 score2 沒有資料也能顯示 student2 的基本資訊

            self.cursor.execute('''

                SELECT 

                    s.學號, s.姓名, s.性別, c.資料庫, c.手機程式

                FROM 

                    student2 s

                LEFT JOIN 

                    score2 c ON s.學號 = c.學號

                ORDER BY s.學號

            ''')

            rows = self.cursor.fetchall()

            return rows

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"查詢資料失敗: {e}")

            return []

        finally:

            self.close()

            

    def delete_database_file(self):

        """6. 刪除資料庫檔案"""

        import os

        if os.path.exists(self.db_name):

            try:

                # 確保連線已關閉

                self.close()

                os.remove(self.db_name)

                return True

            except OSError as e:

                messagebox.showerror("刪除失敗", f"無法刪除資料庫檔案 {self.db_name}: {e}")

                return False

        else:

            messagebox.showwarning("刪除失敗", f"資料庫檔案 {self.db_name} 不存在。")

            return False


# --- Tkinter GUI 應用程式類別 ---

class Application(tk.Tk):

    def __init__(self):

        super().__init__()

        self.title("學生資料與成績管理系統")

        self.db = DatabaseManager("MySchoolDB.db")

        

        # 設定視窗大小和位置

        self.geometry("800x600")

        

        self.create_widgets()

        self.load_data_to_treeview()


    def create_widgets(self):

        # 功能按鈕區塊

        button_frame = tk.Frame(self, padx=10, pady=10)

        button_frame.pack(fill='x')


        # 按鈕清單: (文字, 執行函數)

        buttons = [

            ("1. 建立資料庫", self.create_db_command),

            ("2. 新增資料", self.add_data_dialog),

            ("3. 更正資料", self.update_data_dialog),

            ("4. 刪除資料", self.delete_data_dialog),

            ("5. 顯示所有資料", self.load_data_to_treeview),

            ("6. 刪除資料庫", self.delete_db_command)

        ]


        for i, (text, command) in enumerate(buttons):

            btn = tk.Button(button_frame, text=text, command=command, width=15)

            btn.grid(row=0, column=i, padx=5, pady=5)


        # 顯示資料的 Treeview

        self.tree = ttk.Treeview(self, columns=('學號', '姓名', '性別', '資料庫', '手機程式'), show='headings')

        

        # 設定欄位標題和寬度

        self.tree.heading('學號', text='學號')

        self.tree.column('學號', width=80, anchor='center')

        self.tree.heading('姓名', text='姓名')

        self.tree.column('姓名', width=100, anchor='center')

        self.tree.heading('性別', text='性別')

        self.tree.column('性別', width=50, anchor='center')

        self.tree.heading('資料庫', text='資料庫(分數)')

        self.tree.column('資料庫', width=100, anchor='center')

        self.tree.heading('手機程式', text='手機程式(分數)')

        self.tree.column('手機程式', width=120, anchor='center')


        # 新增捲軸

        vsb = ttk.Scrollbar(self, orient="vertical", command=self.tree.yview)

        vsb.pack(side='right', fill='y')

        self.tree.configure(yscrollcommand=vsb.set)


        self.tree.pack(fill='both', expand=True, padx=10, pady=10)


    # --- 功能執行函數 ---

    def create_db_command(self):

        """執行建立資料庫和資料表的命令"""

        if self.db.create_tables():

            messagebox.showinfo("成功", "資料庫 MySchoolDB.db 及其資料表 (student2, score2) 已成功建立或已存在。")

            self.load_data_to_treeview() # 重新整理顯示

        else:

            # 錯誤已在 DatabaseManager 中處理

            pass


    def load_data_to_treeview(self):

        """5. 顯示所有資料到 Treeview"""

        # 清空現有資料

        for item in self.tree.get_children():

            self.tree.delete(item)

            

        data = self.db.get_all_data()

        

        if data:

            for row in data:

                # 處理分數為 None 的情況 (在 LEFT JOIN 中,如果 score2 沒資料,分數會是 None)

                display_row = list(row)

                display_row[3] = display_row[3] if display_row[3] is not None else 'N/A'

                display_row[4] = display_row[4] if display_row[4] is not None else 'N/A'

                self.tree.insert('', tk.END, values=display_row)

            messagebox.showinfo("查詢成功", f"成功載入 {len(data)} 筆資料。")

        else:

            messagebox.showinfo("查詢結果", "資料庫中沒有資料,或連線失敗。")


    def add_data_dialog(self):

        """2. 新增資料的對話框"""

        dialog = DataDialog(self, "新增資料", mode='add')

        if dialog.result and self.db.insert_data(dialog.result['stu'], dialog.result['score']):

            messagebox.showinfo("成功", "資料新增成功!")

            self.load_data_to_treeview() # 重新整理顯示


    def update_data_dialog(self):

        """3. 更正資料的對話框"""

        # 取得選定的項目

        selected_item = self.tree.focus()

        if not selected_item:

            messagebox.showwarning("更正失敗", "請先在列表中選取一筆要更正的資料。")

            return

            

        # 取得學號

        stu_id = self.tree.item(selected_item, 'values')[0]

        

        dialog = DataDialog(self, "更正資料", mode='update', initial_data=self.tree.item(selected_item, 'values'))

        

        if dialog.result:

            stu_data = (dialog.result['stu'][1], dialog.result['stu'][2]) # 姓名, 性別

            score_data = (dialog.result['score'][0], dialog.result['score'][1]) # 資料庫, 手機程式

            

            if self.db.update_data(stu_id, stu_data, score_data):

                messagebox.showinfo("成功", f"學號 {stu_id} 資料更正成功!")

                self.load_data_to_treeview() # 重新整理顯示


    def delete_data_dialog(self):

        """4. 刪除資料的對話框"""

        selected_item = self.tree.focus()

        if not selected_item:

            messagebox.showwarning("刪除失敗", "請先在列表中選取一筆要刪除的資料。")

            return

            

        stu_id = self.tree.item(selected_item, 'values')[0]

        name = self.tree.item(selected_item, 'values')[1]


        if messagebox.askyesno("確認刪除", f"確定要刪除學號 {stu_id} (姓名: {name}) 的所有資料嗎?"):

            if self.db.delete_data(stu_id):

                messagebox.showinfo("成功", "資料刪除成功!")

                self.load_data_to_treeview() # 重新整理顯示

            # 錯誤已在 DatabaseManager 中處理


    def delete_db_command(self):

        """6. 刪除資料庫檔案"""

        if messagebox.askyesno("確認刪除資料庫", f"**警告:** 確定要刪除整個資料庫檔案 {self.db.db_name} 嗎?所有資料將會遺失!"):

            if self.db.delete_database_file():

                messagebox.showinfo("成功", "資料庫檔案已刪除。")

                self.load_data_to_treeview() # 重新整理顯示為空


# --- 輸入/更正資料的獨立對話框 ---

class DataDialog(simpledialog.Dialog):

    def __init__(self, parent, title=None, mode='add', initial_data=None):

        self.mode = mode

        self.initial_data = initial_data

        super().__init__(parent, title)


    def body(self, master):

        tk.Label(master, text="學號:").grid(row=0, sticky='w')

        tk.Label(master, text="姓名:").grid(row=1, sticky='w')

        tk.Label(master, text="性別 (男/女):").grid(row=2, sticky='w')

        tk.Label(master, text="資料庫分數:").grid(row=3, sticky='w')

        tk.Label(master, text="手機程式分數:").grid(row=4, sticky='w')


        self.e1 = tk.Entry(master)

        self.e2 = tk.Entry(master)

        self.e3 = tk.Entry(master)

        self.e4 = tk.Entry(master)

        self.e5 = tk.Entry(master)


        self.e1.grid(row=0, column=1, padx=5, pady=5)

        self.e2.grid(row=1, column=1, padx=5, pady=5)

        self.e3.grid(row=2, column=1, padx=5, pady=5)

        self.e4.grid(row=3, column=1, padx=5, pady=5)

        self.e5.grid(row=4, column=1, padx=5, pady=5)


        if self.mode == 'update' and self.initial_data:

            # 載入現有資料

            self.e1.insert(0, self.initial_data[0]) # 學號

            self.e2.insert(0, self.initial_data[1]) # 姓名

            self.e3.insert(0, self.initial_data[2]) # 性別

            self.e4.insert(0, self.initial_data[3] if self.initial_data[3] != 'N/A' else '') # 資料庫

            self.e5.insert(0, self.initial_data[4] if self.initial_data[4] != 'N/A' else '') # 手機程式

            self.e1.config(state='readonly') # 更正模式下,學號不可修改


        if self.mode == 'add':

            tk.Label(master, text="*新增模式下,學號必須為新的*").grid(row=5, column=0, columnspan=2, sticky='w')

        if self.mode == 'update':

            tk.Label(master, text="*更正模式下,學號不可修改*").grid(row=5, column=0, columnspan=2, sticky='w')


        return self.e1 # 初始焦點


    def validate(self):

        """資料驗證"""

        stu_id = self.e1.get().strip()

        name = self.e2.get().strip()

        gender = self.e3.get().strip()

        db_score_str = self.e4.get().strip()

        mobile_score_str = self.e5.get().strip()


        if not stu_id or not name or not gender:

            messagebox.showwarning("輸入錯誤", "學號、姓名和性別為必填欄位。")

            return 0

        

        if gender not in ('男', '女'):

            messagebox.showwarning("輸入錯誤", "性別請輸入 '男' 或 '女'。")

            return 0


        # 分數驗證

        try:

            db_score = int(db_score_str)

            if not (0 <= db_score <= 100):

                messagebox.showwarning("輸入錯誤", "資料庫分數必須在 0-100 之間。")

                return 0

        except ValueError:

            messagebox.showwarning("輸入錯誤", "資料庫分數必須為整數。")

            return 0

            

        try:

            mobile_score = int(mobile_score_str)

            if not (0 <= mobile_score <= 100):

                messagebox.showwarning("輸入錯誤", "手機程式分數必須在 0-100 之間。")

                return 0

        except ValueError:

            messagebox.showwarning("輸入錯誤", "手機程式分數必須為整數。")

            return 0

            

        return 1


    def apply(self):

        """將驗證後的資料存儲到 result"""

        stu_id = self.e1.get().strip()

        name = self.e2.get().strip()

        gender = self.e3.get().strip()

        db_score = int(self.e4.get().strip())

        mobile_score = int(self.e5.get().strip())

        

        self.result = {

            'stu': (stu_id, name, gender),

            'score': (db_score, mobile_score)

        }



if __name__ == '__main__':

    app = Application()

    app.mainloop()


學生資料1



import tkinter as tk

from tkinter import messagebox, simpledialog, ttk

import sqlite3

import os


# --- 資料庫操作類別 ---

class DatabaseManager:

    """管理 MySchoolDB.db 中 student2 資料表的 CRUD 操作。"""

    

    def __init__(self, db_name="MySchoolDB.db"):

        self.db_name = db_name

        self.table_name = "student2"


    def connect(self):

        """建立資料庫連線"""

        try:

            conn = sqlite3.connect(self.db_name)

            return conn

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"無法連線到資料庫: {e}")

            return None


    def create_table(self):

        """1. 建立資料表 (MySchoolDB.db 及 student2 表)"""

        conn = self.connect()

        if not conn:

            return False


        try:

            cursor = conn.cursor()

            # 建立 student2 資料表

            # 欄位: 學號 (主鍵), 姓名, 性別

            cursor.execute(f'''

                CREATE TABLE IF NOT EXISTS {self.table_name} (

                    學號 TEXT PRIMARY KEY,

                    姓名 TEXT NOT NULL,

                    性別 TEXT

                )

            ''')

            conn.commit()

            return True

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"建立資料表失敗: {e}")

            return False

        finally:

            conn.close()


    def insert_data(self, stu_id, name, gender):

        """2. 新增資料"""

        conn = self.connect()

        if not conn:

            return False

        

        try:

            cursor = conn.cursor()

            # 檢查學號是否已存在

            cursor.execute(f"SELECT 學號 FROM {self.table_name} WHERE 學號=?", (stu_id,))

            if cursor.fetchone():

                messagebox.showwarning("新增失敗", f"學號 {stu_id} 已存在!")

                return False


            cursor.execute(f"INSERT INTO {self.table_name} (學號, 姓名, 性別) VALUES (?, ?, ?)", 

                           (stu_id, name, gender))

            conn.commit()

            return True

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"新增資料失敗: {e}")

            conn.rollback()

            return False

        finally:

            conn.close()


    def update_data(self, stu_id, name, gender):

        """3. 更正資料"""

        conn = self.connect()

        if not conn:

            return False

        

        try:

            cursor = conn.cursor()

            # 檢查學號是否存在

            cursor.execute(f"SELECT 學號 FROM {self.table_name} WHERE 學號=?", (stu_id,))

            if not cursor.fetchone():

                messagebox.showwarning("更正失敗", f"學號 {stu_id} 不存在!")

                return False


            cursor.execute(f"UPDATE {self.table_name} SET 姓名=?, 性別=? WHERE 學號=?", 

                           (name, gender, stu_id))

            conn.commit()

            return True

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"更正資料失敗: {e}")

            conn.rollback()

            return False

        finally:

            conn.close()


    def delete_data(self, stu_id):

        """4. 刪除資料"""

        conn = self.connect()

        if not conn:

            return False


        try:

            cursor = conn.cursor()

            # 檢查學號是否存在

            cursor.execute(f"SELECT 學號 FROM {self.table_name} WHERE 學號=?", (stu_id,))

            if not cursor.fetchone():

                messagebox.showwarning("刪除失敗", f"學號 {stu_id} 不存在!")

                return False


            cursor.execute(f"DELETE FROM {self.table_name} WHERE 學號=?", (stu_id,))

            conn.commit()

            return True

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"刪除資料失敗: {e}")

            conn.rollback()

            return False

        finally:

            conn.close()

            

    def get_all_data(self):

        """5. 顯示所有資料"""

        conn = self.connect()

        if not conn:

            return []


        try:

            cursor = conn.cursor()

            cursor.execute(f"SELECT 學號, 姓名, 性別 FROM {self.table_name} ORDER BY 學號")

            rows = cursor.fetchall()

            return rows

        except sqlite3.Error as e:

            messagebox.showerror("資料庫錯誤", f"查詢資料失敗: {e}")

            return []

        finally:

            conn.close()

            

    def delete_database_file(self):

        """6. 刪除資料庫檔案"""

        if os.path.exists(self.db_name):

            try:

                # 確保所有連線都已關閉

                # (因為 connect/close 都是在各方法內操作,所以通常不會有問題)

                os.remove(self.db_name)

                return True

            except OSError as e:

                messagebox.showerror("刪除失敗", f"無法刪除資料庫檔案 {self.db_name}: {e}\n請確認是否有其他程式正在使用此檔案。")

                return False

        else:

            messagebox.showwarning("刪除失敗", f"資料庫檔案 {self.db_name} 不存在。")

            return False


# --- Tkinter GUI 應用程式類別 ---

class Application(tk.Tk):

    def __init__(self):

        super().__init__()

        self.title("學生資料管理系統 (student2)")

        self.db = DatabaseManager()

        

        self.geometry("600x450")

        

        self.create_widgets()

        self.load_data_to_treeview() # 啟動時載入資料


    def create_widgets(self):

        # 功能按鈕區塊

        button_frame = tk.Frame(self, padx=10, pady=10)

        button_frame.pack(fill='x')


        # 按鈕清單

        buttons = [

            ("1. 建立資料庫", self.create_db_command),

            ("2. 新增資料", self.add_data_dialog),

            ("3. 更正資料", self.update_data_dialog),

            ("4. 刪除資料", self.delete_data_command),

            ("5. 顯示所有資料", self.load_data_to_treeview),

            ("6. 刪除資料庫", self.delete_db_command)

        ]


        # 將按鈕排成一行

        for i, (text, command) in enumerate(buttons):

            btn = tk.Button(button_frame, text=text, command=command, width=12)

            btn.grid(row=0, column=i, padx=5, pady=5)


        # 顯示資料的 Treeview

        self.tree = ttk.Treeview(self, columns=('學號', '姓名', '性別'), show='headings')

        

        # 設定欄位標題和寬度

        self.tree.heading('學號', text='學號')

        self.tree.column('學號', width=120, anchor='center')

        self.tree.heading('姓名', text='姓名')

        self.tree.column('姓名', width=150, anchor='w')

        self.tree.heading('性別', text='性別')

        self.tree.column('性別', width=80, anchor='center')

        

        self.tree.pack(fill='both', expand=True, padx=10, pady=10)


    # --- 功能執行函數 ---

    

    def create_db_command(self):

        """執行建立資料庫和資料表的命令"""

        if self.db.create_table():

            messagebox.showinfo("成功", "資料庫 MySchoolDB.db 及其資料表 student2 已成功建立或已存在。")

            self.load_data_to_treeview() 

        # 錯誤處理已在 DatabaseManager 中完成


    def load_data_to_treeview(self):

        """5. 顯示所有資料到 Treeview"""

        # 清空現有資料

        for item in self.tree.get_children():

            self.tree.delete(item)

            

        data = self.db.get_all_data()

        

        if data:

            for row in data:

                self.tree.insert('', tk.END, values=row)

            # messagebox.showinfo("查詢成功", f"成功載入 {len(data)} 筆資料。") # 避免過多彈窗

        else:

            # messagebox.showinfo("查詢結果", "資料庫中沒有資料,或連線失敗。")

            pass


    def add_data_dialog(self):

        """2. 新增資料的對話框"""

        dialog = StudentDataDialog(self, "新增學生資料", mode='add')

        

        if dialog.result:

            stu_id, name, gender = dialog.result

            if self.db.insert_data(stu_id, name, gender):

                messagebox.showinfo("成功", "資料新增成功!")

                self.load_data_to_treeview()


    def update_data_dialog(self):

        """3. 更正資料的對話框"""

        selected_item = self.tree.focus()

        if not selected_item:

            messagebox.showwarning("更正失敗", "請先在列表中選取一筆要更正的資料。")

            return

            

        initial_data = self.tree.item(selected_item, 'values') # (學號, 姓名, 性別)

        stu_id = initial_data[0]

        

        dialog = StudentDataDialog(self, "更正學生資料", mode='update', initial_data=initial_data)

        

        if dialog.result:

            # 更正模式下學號不可變,只更新姓名和性別

            _, new_name, new_gender = dialog.result 

            if self.db.update_data(stu_id, new_name, new_gender):

                messagebox.showinfo("成功", f"學號 {stu_id} 資料更正成功!")

                self.load_data_to_treeview()


    def delete_data_command(self):

        """4. 刪除資料"""

        selected_item = self.tree.focus()

        if not selected_item:

            messagebox.showwarning("刪除失敗", "請先在列表中選取一筆要刪除的資料。")

            return

            

        stu_id, name, _ = self.tree.item(selected_item, 'values')


        if messagebox.askyesno("確認刪除", f"確定要刪除學號 {stu_id} (姓名: {name}) 的資料嗎?"):

            if self.db.delete_data(stu_id):

                messagebox.showinfo("成功", "資料刪除成功!")

                self.load_data_to_treeview() 


    def delete_db_command(self):

        """6. 刪除資料庫檔案"""

        if messagebox.askyesno("確認刪除資料庫", f"**警告:** 確定要刪除整個資料庫檔案 {self.db.db_name} 嗎?所有資料將會遺失!"):

            if self.db.delete_database_file():

                messagebox.showinfo("成功", "資料庫檔案已刪除。")

                self.load_data_to_treeview() # 清空顯示


# --- 輸入/更正資料的獨立對話框 ---

class StudentDataDialog(simpledialog.Dialog):

    def __init__(self, parent, title=None, mode='add', initial_data=None):

        self.mode = mode

        self.initial_data = initial_data

        self.result = None

        super().__init__(parent, title)


    def body(self, master):

        tk.Label(master, text="學號:").grid(row=0, sticky='w', padx=5, pady=5)

        tk.Label(master, text="姓名:").grid(row=1, sticky='w', padx=5, pady=5)

        tk.Label(master, text="性別 (男/女):").grid(row=2, sticky='w', padx=5, pady=5)


        self.e1 = tk.Entry(master, width=30)

        self.e2 = tk.Entry(master, width=30)

        self.e3 = tk.Entry(master, width=30)


        self.e1.grid(row=0, column=1, padx=5, pady=5)

        self.e2.grid(row=1, column=1, padx=5, pady=5)

        self.e3.grid(row=2, column=1, padx=5, pady=5)


        if self.mode == 'update' and self.initial_data:

            # 載入現有資料

            self.e1.insert(0, self.initial_data[0]) # 學號

            self.e2.insert(0, self.initial_data[1]) # 姓名

            self.e3.insert(0, self.initial_data[2]) # 性別

            self.e1.config(state='readonly') # 更正模式下,學號不可修改

            return self.e2 # 初始焦點設為姓名

        

        return self.e1 # 初始焦點設為學號


    def validate(self):

        """資料驗證"""

        stu_id = self.e1.get().strip()

        name = self.e2.get().strip()

        gender = self.e3.get().strip()


        if not stu_id or not name or not gender:

            messagebox.showwarning("輸入錯誤", "所有欄位皆為必填。")

            return 0

        

        if gender not in ('男', '女'):

            messagebox.showwarning("輸入錯誤", "性別請輸入 '男' 或 '女'。")

            return 0

            

        return 1


    def apply(self):

        """將驗證後的資料存儲到 result"""

        stu_id = self.e1.get().strip()

        name = self.e2.get().strip()

        gender = self.e3.get().strip()

        

        self.result = (stu_id, name, gender)



if __name__ == '__main__':

    app = Application()

    app.mainloop()


學生成績2


import tkinter as tk
from tkinter import messagebox, simpledialog, ttk
import sqlite3
import os

# --- 資料庫操作類別 ---
class DatabaseManager:
    """管理 MySchoolDB.db 中 score2 資料表的 CRUD 操作。"""
    
    def __init__(self, db_name="MySchoolDB.db"):
        self.db_name = db_name
        self.table_name = "score2"

    def connect(self):
        """建立資料庫連線"""
        try:
            conn = sqlite3.connect(self.db_name)
            return conn
        except sqlite3.Error as e:
            messagebox.showerror("資料庫錯誤", f"無法連線到資料庫: {e}")
            return None

    def create_table(self):
        """1. 建立資料表 (MySchoolDB.db 及 score2 表)"""
        conn = self.connect()
        if not conn:
            return False

        try:
            cursor = conn.cursor()
            # 建立 score2 資料表
            # 欄位: 學號 (主鍵), 資料庫(分數), 手機程式(分數)
            cursor.execute(f'''
                CREATE TABLE IF NOT EXISTS {self.table_name} (
                    學號 TEXT PRIMARY KEY,
                    資料庫 INTEGER,
                    手機程式 INTEGER
                )
            ''')
            conn.commit()
            return True
        except sqlite3.Error as e:
            messagebox.showerror("資料庫錯誤", f"建立資料表失敗: {e}")
            return False
        finally:
            if conn:
                conn.close()

    def insert_data(self, stu_id, db_score, mobile_score):
        """2. 新增資料"""
        conn = self.connect()
        if not conn:
            return False
        
        try:
            cursor = conn.cursor()
            # 檢查學號是否已存在 (學號是主鍵,不能重複)
            cursor.execute(f"SELECT 學號 FROM {self.table_name} WHERE 學號=?", (stu_id,))
            if cursor.fetchone():
                messagebox.showwarning("新增失敗", f"學號 {stu_id} 的成績已存在!")
                return False

            cursor.execute(f"INSERT INTO {self.table_name} (學號, 資料庫, 手機程式) VALUES (?, ?, ?)", 
                           (stu_id, db_score, mobile_score))
            conn.commit()
            return True
        except sqlite3.Error as e:
            messagebox.showerror("資料庫錯誤", f"新增資料失敗: {e}")
            conn.rollback()
            return False
        finally:
            if conn:
                conn.close()

    def update_data(self, stu_id, db_score, mobile_score):
        """3. 更正資料"""
        conn = self.connect()
        if not conn:
            return False
        
        try:
            cursor = conn.cursor()
            # 檢查學號是否存在
            cursor.execute(f"SELECT 學號 FROM {self.table_name} WHERE 學號=?", (stu_id,))
            if not cursor.fetchone():
                messagebox.showwarning("更正失敗", f"學號 {stu_id} 的成績不存在!")
                return False

            cursor.execute(f"UPDATE {self.table_name} SET 資料庫=?, 手機程式=? WHERE 學號=?", 
                           (db_score, mobile_score, stu_id))
            conn.commit()
            return True
        except sqlite3.Error as e:
            messagebox.showerror("資料庫錯誤", f"更正資料失敗: {e}")
            conn.rollback()
            return False
        finally:
            if conn:
                conn.close()

    def delete_data(self, stu_id):
        """4. 刪除資料"""
        conn = self.connect()
        if not conn:
            return False

        try:
            cursor = conn.cursor()
            # 檢查學號是否存在
            cursor.execute(f"SELECT 學號 FROM {self.table_name} WHERE 學號=?", (stu_id,))
            if not cursor.fetchone():
                messagebox.showwarning("刪除失敗", f"學號 {stu_id} 的成績不存在!")
                return False

            cursor.execute(f"DELETE FROM {self.table_name} WHERE 學號=?", (stu_id,))
            conn.commit()
            return True
        except sqlite3.Error as e:
            messagebox.showerror("資料庫錯誤", f"刪除資料失敗: {e}")
            conn.rollback()
            return False
        finally:
            if conn:
                conn.close()
            
    def get_all_data(self):
        """5. 顯示所有資料"""
        conn = self.connect()
        if not conn:
            return []

        try:
            cursor = conn.cursor()
            cursor.execute(f"SELECT 學號, 資料庫, 手機程式 FROM {self.table_name} ORDER BY 學號")
            rows = cursor.fetchall()
            return rows
        except sqlite3.Error as e:
            messagebox.showerror("資料庫錯誤", f"查詢資料失敗: {e}")
            return []
        finally:
            if conn:
                conn.close()
            
    def delete_database_file(self):
        """6. 刪除資料庫檔案"""
        if os.path.exists(self.db_name):
            try:
                # 確保檔案沒有被鎖定
                os.remove(self.db_name)
                return True
            except OSError as e:
                messagebox.showerror("刪除失敗", f"無法刪除資料庫檔案 {self.db_name}: {e}\n請確認是否有其他程式正在使用此檔案。")
                return False
        else:
            messagebox.showwarning("刪除失敗", f"資料庫檔案 {self.db_name} 不存在。")
            return False

# --- Tkinter GUI 應用程式類別 ---
class Application(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("成績資料管理系統 (score2)")
        self.db = DatabaseManager()
        
        self.geometry("650x450")
        
        self.create_widgets()
        self.load_data_to_treeview() # 啟動時載入資料

    def create_widgets(self):
        # 功能按鈕區塊
        button_frame = tk.Frame(self, padx=10, pady=10)
        button_frame.pack(fill='x')

        # 按鈕清單
        buttons = [
            ("1. 建立資料庫", self.create_db_command),
            ("2. 新增資料", self.add_data_dialog),
            ("3. 更正資料", self.update_data_dialog),
            ("4. 刪除資料", self.delete_data_command),
            ("5. 顯示所有資料", self.load_data_to_treeview),
            ("6. 刪除資料庫", self.delete_db_command)
        ]

        # 將按鈕排成一行
        for i, (text, command) in enumerate(buttons):
            btn = tk.Button(button_frame, text=text, command=command, width=12)
            btn.grid(row=0, column=i, padx=5, pady=5)

        # 顯示資料的 Treeview
        self.tree = ttk.Treeview(self, columns=('學號', '資料庫', '手機程式'), show='headings')
        
        # 設定欄位標題和寬度
        self.tree.heading('學號', text='學號')
        self.tree.column('學號', width=120, anchor='center')
        self.tree.heading('資料庫', text='資料庫(分數)')
        self.tree.column('資料庫', width=150, anchor='center')
        self.tree.heading('手機程式', text='手機程式(分數)')
        self.tree.column('手機程式', width=150, anchor='center')
        
        self.tree.pack(fill='both', expand=True, padx=10, pady=10)

    # --- 功能執行函數 ---
    
    def create_db_command(self):
        """執行建立資料庫和資料表的命令"""
        if self.db.create_table():
            messagebox.showinfo("成功", "資料庫 MySchoolDB.db 及其資料表 score2 已成功建立或已存在。")
            self.load_data_to_treeview() 

    def load_data_to_treeview(self):
        """5. 顯示所有資料到 Treeview"""
        for item in self.tree.get_children():
            self.tree.delete(item)
            
        data = self.db.get_all_data()
        
        if data:
            for row in data:
                self.tree.insert('', tk.END, values=row)

    def add_data_dialog(self):
        """2. 新增資料的對話框"""
        dialog = ScoreDataDialog(self, "新增學生分數", mode='add')
        
        if dialog.result:
            stu_id, db_score, mobile_score = dialog.result
            if self.db.insert_data(stu_id, db_score, mobile_score):
                messagebox.showinfo("成功", "成績新增成功!")
                self.load_data_to_treeview()

    def update_data_dialog(self):
        """3. 更正資料的對話框"""
        selected_item = self.tree.focus()
        if not selected_item:
            messagebox.showwarning("更正失敗", "請先在列表中選取一筆要更正的資料。")
            return
            
        initial_data = self.tree.item(selected_item, 'values') # (學號, 資料庫, 手機程式)
        stu_id = initial_data[0]
        
        dialog = ScoreDataDialog(self, "更正學生分數", mode='update', initial_data=initial_data)
        
        if dialog.result:
            # 更正模式下學號不可變,只更新分數
            _, new_db_score, new_mobile_score = dialog.result 
            if self.db.update_data(stu_id, new_db_score, new_mobile_score):
                messagebox.showinfo("成功", f"學號 {stu_id} 成績更正成功!")
                self.load_data_to_treeview()

    def delete_data_command(self):
        """4. 刪除資料"""
        selected_item = self.tree.focus()
        if not selected_item:
            messagebox.showwarning("刪除失敗", "請先在列表中選取一筆要刪除的資料。")
            return
            
        stu_id = self.tree.item(selected_item, 'values')[0]

        if messagebox.askyesno("確認刪除", f"確定要刪除學號 {stu_id} 的成績資料嗎?"):
            if self.db.delete_data(stu_id):
                messagebox.showinfo("成功", "成績資料刪除成功!")
                self.load_data_to_treeview() 

    def delete_db_command(self):
        """6. 刪除資料庫檔案"""
        if messagebox.askyesno("確認刪除資料庫", f"**警告:** 確定要刪除整個資料庫檔案 {self.db.db_name} 嗎?所有資料將會遺失!"):
            if self.db.delete_database_file():
                messagebox.showinfo("成功", "資料庫檔案已刪除。")
                self.load_data_to_treeview() # 清空顯示

# --- 輸入/更正資料的獨立對話框 ---
class ScoreDataDialog(simpledialog.Dialog):
    def __init__(self, parent, title=None, mode='add', initial_data=None):
        self.mode = mode
        self.initial_data = initial_data
        self.result = None
        super().__init__(parent, title)

    def body(self, master):
        tk.Label(master, text="學號:").grid(row=0, sticky='w', padx=5, pady=5)
        tk.Label(master, text="資料庫分數 (0-100):").grid(row=1, sticky='w', padx=5, pady=5)
        tk.Label(master, text="手機程式分數 (0-100):").grid(row=2, sticky='w', padx=5, pady=5)

        self.e1 = tk.Entry(master, width=30)
        self.e2 = tk.Entry(master, width=30)
        self.e3 = tk.Entry(master, width=30)

        self.e1.grid(row=0, column=1, padx=5, pady=5)
        self.e2.grid(row=1, column=1, padx=5, pady=5)
        self.e3.grid(row=2, column=1, padx=5, pady=5)

        if self.mode == 'update' and self.initial_data:
            # 載入現有資料
            self.e1.insert(0, self.initial_data[0]) # 學號
            self.e2.insert(0, self.initial_data[1]) # 資料庫
            self.e3.insert(0, self.initial_data[2]) # 手機程式
            self.e1.config(state='readonly') # 更正模式下,學號不可修改
            return self.e2 # 初始焦點設為資料庫分數
        
        return self.e1 # 初始焦點設為學號

    def validate(self):
        """資料驗證"""
        stu_id = self.e1.get().strip()
        db_score_str = self.e2.get().strip()
        mobile_score_str = self.e3.get().strip()

        if not stu_id or not db_score_str or not mobile_score_str:
            messagebox.showwarning("輸入錯誤", "所有欄位皆為必填。")
            return 0
        
        # 分數驗證
        try:
            db_score = int(db_score_str)
            if not (0 <= db_score <= 100):
                messagebox.showwarning("輸入錯誤", "資料庫分數必須在 0-100 之間。")
                return 0
        except ValueError:
            messagebox.showwarning("輸入錯誤", "資料庫分數必須為整數。")
            return 0
            
        try:
            mobile_score = int(mobile_score_str)
            if not (0 <= mobile_score <= 100):
                messagebox.showwarning("輸入錯誤", "手機程式分數必須在 0-100 之間。")
                return 0
        except ValueError:
            messagebox.showwarning("輸入錯誤", "手機程式分數必須為整數。")
            return 0
            
        return 1

    def apply(self):
        """將驗證後的資料存儲到 result"""
        stu_id = self.e1.get().strip()
        db_score = int(self.e2.get().strip())
        mobile_score = int(self.e3.get().strip())
        
        self.result = (stu_id, db_score, mobile_score)


if __name__ == '__main__':
    app = Application()
    app.mainloop()

沒有留言:

張貼留言

ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite

 ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite  ESP32 VS Code 程式 ; PlatformIO Project Configuration File ; ;   Build op...