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()

输出:

相关推荐
旋风菠萝14 分钟前
项目复习(1)
java·数据库·八股·八股文·复习·项目、
w236173460117 分钟前
Django框架漏洞深度剖析:从漏洞原理到企业级防御实战指南——为什么你的Django项目总被黑客盯上?
数据库·django·sqlite
2302_8097983244 分钟前
【JavaWeb】MySQL
数据库·mysql
drowingcoder1 小时前
MySQL相关
数据库
Musennn2 小时前
MySQL刷题相关简单语法集合
数据库·mysql
Think Spatial 空间思维3 小时前
【HTTPS基础概念与原理】TLS握手过程详解
数据库·网络协议·https
laowangpython3 小时前
MySQL基础面试通关秘籍(附高频考点解析)
数据库·mysql·其他·面试
mooyuan天天3 小时前
SQL注入报错“Illegal mix of collations for operation ‘UNION‘”解决办法
数据库·web安全·sql注入·dvwa靶场·sql报错
运维-大白同学3 小时前
go-数据库基本操作
开发语言·数据库·golang
R-sz4 小时前
通过从数据库加载MinIO配置并初始化MinioClient,spring boot之Minio上传
数据库·oracle