MVC(Model-View-Controller)framework using Python ,Tkinter and SQLite

1.项目结构

sql:

sql 复制代码
            CREATE TABLE IF NOT EXISTS School (
                SchoolId TEXT not null,  
                SchoolName TEXT NOT NULL,
                SchoolTelNo TEXT NOT NULL
            )

整体思路

  • Model:负责与 SQLite 数据库进行交互,包括创建表、插入、删除、更新和查询数据等操作。
  • View :使用 Tkinter 和 ttk.Treeview 创建用户界面,包含输入框、按钮和分页控件,用于显示数据并处理用户的交互。
  • Controller:处理用户界面的事件,调用 Model 中的方法进行数据操作,并更新 View 中的显示。

代码实现:

Model 部分

python 复制代码
# encoding: utf-8
# 版权所有 2024 ©涂聚文有限公司
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : PyCharm 2023.1 python 3.11
# OS        : windows 10
# database  : mysql 9.0 sql server 2019, poostgreSQL 17.0  oracle 11g sqlite
# Datetime  : 2024-11-20 22:35:21
# database  :sql server 2019
# User      : geovindu
# Product   : PyCharm
# Project   : pySQLiteMvcDemo
# File      : bll/School.py
# explain   : 学习
from __future__ import annotations
from abc import ABC, abstractmethod
import os
import sys
from Model.school import SchoolInfo
from Factory.AbstractFactory import AbstractFactory

class SchoolBll(object):
    """
    学校表                    
    """ 

    dal = AbstractFactory.createSchool
    """
    类属性 接口DAL
    """
    def __init__(self):
        """

        """
        self.__name = "SchoolBll"
        self.createtable()  # 如果不存在,初始化表

  

    def __del__(self):
        """

        :return:
        """
        print(f"{self.__name} ERASE MEMORY")

    def Close(cls):
        """
        关闭
        :return:
        """
        cls.dal().Close()

    def createtable(self):
        """
        建表
        """
        self.dal().createtable()


    def selectData(self) -> list:
        """

        :return:
        """
        data = self.dal().selectSql()
        return data


    def select(self) -> list[SchoolInfo]:
        """

        :return:
        """

        schools = []

        data = self.dal().selectSql()
        if len(data) > 0:
            for SchoolId,SchoolName,SchoolTelNo in data[0]:
                info = SchoolInfo()
                info.SchoolId = SchoolId
                info.SchoolName = SchoolName
                info.SchoolTelNo = SchoolTelNo
                schools.append(info)
        return schools

    def selectSql(cls) -> list[SchoolInfo]:
        """
        元组数据
        :return: list 列表
        """
        schools = []

        data = cls.dal().selectSql()
        if len(data) > 0:
            for SchoolId,SchoolName,SchoolTelNo in data[0]:
                info=SchoolInfo()
                info.SchoolId = SchoolId
                info.SchoolName = SchoolName
                info.SchoolTelNo = SchoolTelNo
                schools.append(info)
        return schools

    def selectSqlCount(cls) -> int:
        """
        查询数据 总数
        :return:
        """
        #print(cls.dal().selectSqlCount()[0][0])
        total = cls.dal().selectSqlCount()[0][0]
        return total

    def Count(self) -> int:
        """
        查询数据 总数
        :return:
        """
        total = self.dal().selectSqlCount()[0][0]
        return total


    def getcount(cls, search_query=""):
        """
        计算
        :param search_query:
        :return:
        """
        return cls.dal().getcount(search_query)


    def getschools(cls, page, limit, search_query=""):
        """
        查询
        :param page:
        :param limit:
        :param search_query:
        :return:
        """
        data=cls.dal().getschools(page, limit, search_query)
        print("data:",data)
        return data


    def selectSqlOrder(cls, order: str) -> list[SchoolInfo]:
        """
        元组数据
        :param order: SchoolName desc/asc
        :return:
        """
        schools = []
        data = cls.dal().selectSqlOrder(order)
        if len(data) > 0:
            for SchoolId,SchoolName,SchoolTelNo in data[0]:
                info=SchoolInfo()
                info.SchoolId = SchoolId
                info.SchoolName = SchoolName
                info.SchoolTelNo = SchoolTelNo
                schools.append(info)
        return schools


    def selectSort(cls,field:str,isOrder:bool)->list[SchoolInfo]:
        """

        :param field SchoolId
        :param order:  desc/asc
        :return:
        """
        schools = []
        data = cls.dal().selectSort(field,isOrder)
        if len(data) > 0:
            for SchoolId,SchoolName,SchoolTelNo in data[0]:
                info = SchoolInfo()
                info.SchoolId = SchoolId
                info.SchoolName = SchoolName
                info.SchoolTelNo = SchoolTelNo
                schools.append(info)
        return schools


    def selectIdSql(cls,SchoolId:str) -> list[SchoolInfo]:
        """

        :param SchoolId:ID
        :return:
        """
        schools = []
        data = cls.dal().selectIdSql(SchoolId)
        #print(data)
        if len(data)>0:
            for SchoolId,SchoolName,SchoolTelNo in data[0]:
                info = SchoolInfo()
                info.SchoolId = SchoolId
                info.SchoolName = SchoolName
                info.SchoolTelNo = SchoolTelNo
                schools.append(info)
        return schools

    def addSql(cls,info:SchoolInfo) -> int:
        """

        :param info:实体类
        :return:
        """
        return cls.dal().addSql(info)


    def add(self,info:SchoolInfo) -> int:
        """

        :param info:实体类
        :return:
        """
        return self.dal().addSql(info)


    def editSql(cls,info:SchoolInfo) -> int:
        """

        :param info:实体类
        :return:
        """
        #print(info)
        return cls.dal().editSql(info)

    def edit(self,info:SchoolInfo) -> int:
        """

        :param info:实体类
        :return:
        """
        #print(info)
        return self.dal().editSql(info)



    def delSql(cls, SchoolId: str) -> int:
        """

        :param SchoolId:
        :return:
        """
        return cls.dal().delSql(SchoolId)

    def delinfo(self, SchoolId: str) -> int:
        """

        :param SchoolId:
        :return:
        """
        return self.dal().delSql(SchoolId)


            

View 部分

python 复制代码
# encoding: utf-8
# 版权所有 2025 ©涂聚文有限公司
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : PyCharm 2023.1 python 3.11
# OS        : windows 10
# database  : mysql 9.0 sql server 2019, poostgreSQL 17.0  oracle 21c Neo4j sqlite
# Datetime  : 2025/2/11 20:37
# User      : geovindu
# Product   : PyCharm
# Project   : pySQLiteMvcDemo
# File      : ViewUI/SchoolView.py
# explain   : 学习

import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3
from Model.school import SchoolInfo

class SchoolView(tk.Frame):
    """
    View 类:负责创建和管理用户界面
    """
    def __init__(self, master=None):
        """

        :param master:
        """
        super().__init__(master)

        self.master = master
        self.pack()
        self.create_widgets()

    def create_widgets(self):
        """

        :return:
        """
        self.search_frame = tk.Frame(self)
        self.search_frame.pack(pady=10)
        # 搜索输入框
        self.search_label = tk.Label(self.search_frame, text="搜索:")
        #self.search_label.pack()
        self.search_label.grid(row=0, column=0)
        self.search_entry = tk.Entry(self.search_frame)
        #self.search_entry.pack()
        self.search_entry.grid(row=0, column=1)
        # 搜索按钮
        self.search_button = tk.Button(self.search_frame, text="搜索")
        #self.search_button.pack()
        self.search_button.grid(row=0, column=2)

        self.tree_frame = tk.Frame(self)
        self.tree_frame.pack(pady=10)
        # Treeview 控件
        self.tree = ttk.Treeview(self.tree_frame, columns=('ID', '校名', '电话'), show='headings')
        self.tree.heading('ID', text='ID')
        self.tree.heading('校名', text='校名')
        self.tree.heading('电话', text='电话')
        self.tree.pack()

        # Pagination Frame
        self.pagination_frame = tk.Frame(self)
        self.pagination_frame.pack(pady=10)

        # 分页控件
        self.prev_button = tk.Button(self.pagination_frame, text="上一页")
        self.prev_button.pack(side=tk.LEFT)
        self.page_label = tk.Label(self.pagination_frame, text="第 1 页")
        self.page_label.pack(side=tk.LEFT)

        self.pagetotal_label = tk.Label(self.pagination_frame, text="/共 1 条")
        self.pagetotal_label.pack(side=tk.LEFT)

        self.next_button = tk.Button(self.pagination_frame, text="下一页")
        self.next_button.pack(side=tk.LEFT)

        # Add/Update/Delete Frame
        self.action_frame = tk.Frame(self)
        self.action_frame.pack(pady=10)
        self.id_label = tk.Label(self.action_frame, text="編號:")
        #self.id_label.pack()
        self.id_label.grid(row=0, column=0)

        self.id_entry = tk.Entry(self.action_frame)
        self.id_entry.grid(row=0, column=1)

        # 校名输入框
        self.name_label = tk.Label(self.action_frame, text="校名:")
        self.name_label.grid(row=1, column=0)

        self.name_entry = tk.Entry(self.action_frame)
        self.name_entry.grid(row=1, column=1)
        # 电话输入框
        self.phone_label = tk.Label(self.action_frame, text="电话:")
        self.phone_label.grid(row=2, column=0)
        self.phone_entry = tk.Entry(self.action_frame)
        self.phone_entry.grid(row=2, column=1)
        # 添加按钮
        self.add_button = tk.Button(self.action_frame, text="添加")
        self.add_button.grid(row=3, column=0)
        # 修改按钮
        self.update_button = tk.Button(self.action_frame, text="修改")
        self.update_button.grid(row=3, column=1)
        # 删除按钮
        self.delete_button = tk.Button(self.action_frame, text="删除")
        self.delete_button.grid(row=3, column=2)

    def clear_entries(self):
        """

        :return:
        """
        self.id_entry.delete(0, tk.END)
        self.name_entry.delete(0, tk.END)
        self.phone_entry.delete(0, tk.END)
        self.search_entry.delete(0, tk.END)

    def populate_treeview(self, contacts):
        """

        :param contacts:
        :return:
        """
        for i in self.tree.get_children():
            self.tree.delete(i)
        for contact in contacts:
            self.tree.insert('', 'end', values=contact)


    def update_page_label(self, page, total_pages):
        """

        :param page:
        :param total_pages:
        :return:
        """
        self.page_label.config(text=f"第 {page} 页/共 {total_pages} 页")

    def update_page_total(self,tatal):
        """

        :param tatal:
        :return:
        """
        self.pagetotal_label.config(text=f"共{tatal} 条")



class AddSchoolWindow(tk.Toplevel):
    """
    弹出窗口 - 添加学校
    """
    def __init__(self, master, controller):
        """

        :param master:
        :param controller:
        """
        super().__init__(master)
        self.controller = controller
        self.title("添加学校")
        self.create_widgets()
    def create_widgets(self):
        """

        :return:
        """
        self.action_frame = tk.Frame(self)
        self.action_frame.pack(pady=10)
        # 学校 ID 输入框
        self.id_label = tk.Label(self.action_frame, text="学校 ID:")
        self.id_label.grid(row=0, column=0)
        self.id_entry = tk.Entry(self.action_frame)
        self.id_entry.grid(row=0, column=1)
        # 学校名称输入框
        self.name_label = tk.Label(self.action_frame, text="学校名称:")
        self.name_label.grid(row=1, column=0)
        self.name_entry = tk.Entry(self.action_frame)
        self.name_entry.grid(row=1, column=1)
        # 学校电话输入框
        self.tel_label = tk.Label(self.action_frame, text="学校电话:")
        self.tel_label.grid(row=2, column=0)
        self.tel_entry = tk.Entry(self.action_frame)
        self.tel_entry.grid(row=2, column=1)
        # 保存按钮
        self.save_button = tk.Button(self.action_frame, text="保存", command=self.save_school)
        self.save_button.grid(row=3, column=0)
    def save_school(self):
        """

        :return:
        """
        school_id = self.id_entry.get()
        school_name = self.name_entry.get()
        school_tel = self.tel_entry.get()
        info = SchoolInfo()
        info.SchoolId = school_id
        info.SchoolName = school_name
        info.SchoolTelNo = school_tel

        if school_id and school_name and school_tel:
            self.controller.add_school(info)
            self.destroy()
        else:
            messagebox.showerror("错误", "所有字段均为必填项")



class EditSchoolWindow(tk.Toplevel):
    """
    弹出窗口 - 修改学校
    """
    def __init__(self, master, controller, school_id, school_name, school_tel):
        """

        :param master:
        :param controller:
        :param school_id:
        :param school_name:
        :param school_tel:
        """
        super().__init__(master)
        self.controller = controller
        self.school_id = school_id
        self.title("修改学校")
        self.create_widgets(school_name, school_tel)
    def create_widgets(self, school_name, school_tel):
        """

        :param school_name:
        :param school_tel:
        :return:
        """
        self.action_frame = tk.Frame(self)
        self.action_frame.pack(pady=10)
        # 学校 ID 标签(不可编辑)
        self.id_label = tk.Label(self.action_frame, text="学校 ID:")
        self.id_label.grid(row=0, column=0)
        self.id_entry = tk.Entry(self.action_frame)
        self.id_entry.insert(0, self.school_id)
        self.id_entry.config(state='readonly')
        self.id_entry.grid(row=0, column=1)
        # 学校名称输入框
        self.name_label = tk.Label(self.action_frame, text="学校名称:")
        self.name_label.grid(row=1, column=0)
        self.name_entry = tk.Entry(self.action_frame)
        self.name_entry.insert(0, school_name)
        self.name_entry.grid(row=1, column=1)
        # 学校电话输入框
        self.tel_label = tk.Label(self.action_frame, text="学校电话:")
        self.tel_label.grid(row=2, column=0)
        self.tel_entry = tk.Entry(self.action_frame)
        self.tel_entry.insert(0, school_tel)
        self.tel_entry.grid(row=2, column=1)
        # 保存按钮
        self.save_button = tk.Button(self.action_frame, text="保存", command=self.save_school)
        self.save_button.grid(row=3, column=0)


    def save_school(self):
        """

        :return:
        """

        school_name = self.name_entry.get()
        school_tel = self.tel_entry.get()
        if school_name and school_tel:
            print("edit",self.school_id)
            info=SchoolInfo()
            info.SchoolId=self.school_id
            info.SchoolName=school_name
            info.SchoolTelNo=school_tel
            #self.controller.update_school(self.school_id, school_name, school_tel)
            self.controller.update_school(info)
            self.destroy()
        else:
            messagebox.showerror("错误", "所有字段均为必填项")

Controller 部分

python 复制代码
# encoding: utf-8
# 版权所有 2025 ©涂聚文有限公司
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : PyCharm 2023.1 python 3.11
# OS        : windows 10
# database  : mysql 9.0 sql server 2019, poostgreSQL 17.0  oracle 21c Neo4j sqlite
# Datetime  : 2025/2/11 20:36
# User      : geovindu
# Product   : PyCharm
# Project   : Controller/pySQLiteMvcDemo
# File      : SchoolController.py
# explain   : 学习
import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3
from Model.school import SchoolInfo
from BLLModel.school import SchoolBll
from ViewUI.SchoolView import AddSchoolWindow
from ViewUI.SchoolView import EditSchoolWindow


class SchoolController(object):
    """
    Controller 类:负责处理用户输入和更新模型和视图
    """
    def __init__(self, model, view):
        """

        :param model:
        :param view:
        """
        self.model = SchoolInfo()
        self.view = view
        self.current_page = 1
        self.limit = 10
        self.search_query = ""
        # 绑定按钮点击事件到相应的处理方法
        self.view.add_button.config(command=self.addschools)
        #self.view.add_button.config(command=self.open_add_window)
        self.view.update_button.config(command=self.updateschools)
        self.view.delete_button.config(command=self.deleteschools)
        self.view.search_button.config(command=self.searchschools)
        self.view.prev_button.config(command=self.prev_page)
        self.view.next_button.config(command=self.next_page)

        self.view.tree.bind("<Double-1>", self.open_edit_window)
        self.showschools()
        self.bll = SchoolBll()

    def open_add_window(self):
        """

        :return:
        """
        AddSchoolWindow(self.view.master, self)

    def open_edit_window(self, event):
        """

        :param event:
        :return:
        """
        selected_item = self.view.tree.selection()
        if selected_item:
            values = self.view.tree.item(selected_item)['values']
            school_id = values[0]
            school_name = values[1]
            school_tel = values[2]
            EditSchoolWindow(self.view.master, self, school_id, school_name, school_tel)

    def add_school(self, info:SchoolInfo):
        """

        :param info:
        :return:
        """
        self.bll.addSql(info)
        self.show_schools()

    def delete_school(self):
        """

        :return:
        """
        selected_item = self.view.tree.selection()
        if selected_item:
            school_id = self.view.tree.item(selected_item)['values'][0]
            self.bll.delSql(school_id)
            self.show_schools()
        else:
            messagebox.showerror("错误", "请选择要删除的学校")

    def update_school(self, info):
        """

        :param info:
        :return:
        """
        self.bll.editSql(info)
        self.show_schools()

    def search_schools(self):
        """

        :return:
        """
        self.search_query = self.view.search_entry.get()
        self.current_page = 1
        self.show_schools()

    def show_schools(self):
        """

        :return:
        """
        total_count = self.bll.getcount(self.search_query)[0][0]
        print(total_count)
        if total_count >= 1:
            total_pages = (total_count + self.limit - 1) // self.limit
            contacts = self.bll.getschools(self.current_page, self.limit, self.search_query)
            self.view.populate_treeview(contacts)
            self.view.update_page_label(self.current_page, total_pages)
            self.view.update_page_total(total_count)


    def addschools(self):
        """

        :return:
        """
        id = self.view.id_entry.get()
        name = self.view.name_entry.get()
        phone = self.view.phone_entry.get()
        self.model.SchoolId=id
        self.model.SchoolName=name
        self.model.SchoolTelNo=phone;
        if name and phone:
            self.bll.add(self.model)
            self.view.clear_entries()
            self.showschools()
        else:
            messagebox.showerror("错误", "校名和电话不能为空")
    def deleteschools(self):
        """

        :return:
        """
        selected_item = self.view.tree.selection()
        if selected_item:
            id = str(self.view.tree.item(selected_item)['values'][0])
            print("id",id)
            self.bll.delSql(id)
            self.showschools()
        else:
            messagebox.showerror("错误", "请选择要删除的学校")

    def updateschools(self, event):
        """

        :return:
        """
        selected_item = self.view.tree.selection()
        if selected_item:
            id = str(self.view.tree.item(selected_item)['values'][0])
            #values = self.view.tree.item(selected_item)['values']
            name = self.view.name_entry.get()
            phone = self.view.phone_entry.get()
            self.model.SchoolId = id
            self.model.SchoolName = name
            self.model.SchoolTelNo = phone
            if name and phone:
                self.bll.editSql(self.model)

                self.view.clear_entries()
                self.showschools()
            else:
                messagebox.showerror("错误", "校名和电话不能为空")
        else:
            messagebox.showerror("错误", "请选择要修改的学校")

    def searchschools(self):
        """

        :return:
        """
        self.search_query = self.view.search_entry.get()
        self.current_page = 1
        self.showschools()
    def prev_page(self):
        """

        :return:
        """
        if self.current_page > 1:
            self.current_page -= 1
            self.showschools()
    def next_page(self):
        """

        :return:
        """
        total_count = self.bll.getcount(self.search_query)[0][0]
        total_pages = (total_count + self.limit - 1) // self.limit
        if self.current_page < total_pages:
            self.current_page += 1
            self.showschools()
    def showschools(self):
        """

        :return:
        """
        bll=SchoolBll()
        total_count = bll.getcount(self.search_query)[0][0]
        print(total_count)
        if total_count >= 1:
            total_pages = (total_count + self.limit - 1) // self.limit
            contacts = bll.getschools(self.current_page, self.limit, self.search_query)
            self.view.populate_treeview(contacts)
            self.view.update_page_label(self.current_page, total_pages)
            self.view.update_page_total(total_count)

调用:

python 复制代码
# encoding: utf-8
# 版权所有 2025 ©涂聚文有限公司
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:
# Author    : geovindu,Geovin Du 涂聚文.塗聚文
# IDE       : PyCharm Community Edition 2024.3 python 3.11
# OS        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  oracle 11g oracle 20c  Neo4j  sqlite
# Datetime  : 2025/2/11 20:11
# User      : geovindu
# Product   : PyCharm
# Project   : pySQLiteMVCDemo
# File      : main.py
# explain   : 学习

import tkinter as tk
from tkinter import ttk, messagebox
from BLLModel.school import SchoolBll
from ViewUI.SchoolView import SchoolView
from Controller.SchoolController import SchoolController


if __name__ == '__main__':
    """
    
    """
    root = tk.Tk()
    bllmodel = SchoolBll()
    view = SchoolView(master=root)
    controller = SchoolController(bllmodel, view)
    root.title("School Management")
    root.iconbitmap('favicon.ico')
    root.mainloop()

输出:

相关推荐
智商不在服务器35 分钟前
利用二分法进行 SQL 盲注
数据库·python·sql
明月看潮生1 小时前
青少年编程与数学 02-009 Django 5 Web 编程 07课题、数据迁移
数据库·python·青少年编程·django·编程与数学
txwtech1 小时前
QT使用QAbstractTableModel 0x8读取访问权限冲突
开发语言·数据库·qt
jay丿2 小时前
MYSQL数学函数
数据库·sql·mysql
小句2 小时前
Kafka 中基于 Segment 和 Offset 查找消息的过程
数据库·分布式·kafka
Nodejs_home4 小时前
TaskBuilder数据模型设计器使用简介
数据库·低代码·oracle
紅色彼岸花4 小时前
一、boolen盲注和时间盲注
数据库·安全性测试
小梁不秃捏5 小时前
SQL 大厂面试题目(由浅入深)
数据库·sql·面试
管理大亨5 小时前
C#+UDP接收数据,并将数据保存到redis,定时同步到数据库Sql Server中
数据库·udp·c#