Tkinter制作登录界面以及登陆后页面切换
- 前言
- 序言
- 1. 由来
- 2. 思路
- 3. 项目结构描述
- 4. 项目实战
- 1. 登录界面实现(代码)
- 2. 首页界面实现(代码)
- 3. 打包build.py(与main.py同级目录)
- 4. 打包安装包
前言
本帖子,默认您已了解Tkinter的基础操作,以及原理,文中仅会对部分逻辑描述,不会对Tkinter讲解.
序言
1. 由来
入职以来,很长时间都是在做网页版的后台,用到最多的就是Java语言,框架主要是:Spring Boot,Spring Cloud 。中间件大多用:Mq,Redis,Nacos等,但是随着项目的发展,网页版已经无法满足客户的需求以及业务生态的发展,需要增加客户端应用,由于公司没有此类开发经验,重担就留给了俺,一个小趴菜(报头痛哭)…好了,废话不多说,开搞。说到客户端第一时间想到的就是python的PyQt5,因为以前了解过。但是随着深入了解,好是好,功能也齐全,但是由于作者比较笨,也比较懒实在不想去烧脑学习了,就盯上了较为简单快捷的Tkinter。
2. 思路
制作客户端主要流程被我分为(不考虑后台服务情况下):明白需求(了解业务)、设计架构(主要是使用的语言以及怎么模块化开发,方便后续升级管理)、打包部署(Inno setup 打包)、测试调优(测试人员)。
3. 项目结构描述
下文中出现的如: login/ui.py 这样的文件名称时则代表的是,在包login下的ui.py文件,后续可以自行修改。
4. 项目实战
1. 登录界面实现(代码)
-
图片展示:
-
login/ui.py
from tkinter import * from tkinter.ttk import * class WinGUI(Tk): def __init__(self): super().__init__() self.__win() self.iconbitmap('favicon.ico') self.tk_input_username_label = Entry(self, ) self.tk_input_username_label.place(relx=0.5367, rely=0.2471, relwidth=0.4000, relheight=0.1471) self.tk_input_password_label = Entry(self, show='*') self.tk_input_password_label.place(relx=0.5367, rely=0.4441, relwidth=0.4000, relheight=0.1471) self.tk_button_login_button = Button(self, text="立即登录", takefocus=False, ) self.tk_button_login_button.place(relx=0.5367, rely=0.6824, relwidth=0.4000, relheight=0.1471) self.tk_label_register = Label(self, text="还没有账号?立即注册", anchor="center", ) self.tk_label_register.place(relx=0.6933, rely=0.8676, relwidth=0.2250, relheight=0.0882) self.tk_canvas_login_canvas = Canvas(self, ) self.tk_canvas_login_canvas.place(relx=0.0000, rely=0.0000, relwidth=0.4667, relheight=1.0000) self.tk_label_welcome = Label(self, text="项目介绍(自定义)", anchor="center", ) self.tk_label_welcome.place(relx=0.4667, rely=0.0588, relwidth=0.5333, relheight=0.1471) def __win(self): self.title("登录界面") width, height = 600, 340 screenwidth = self.winfo_screenwidth() screenheight = self.winfo_screenheight() geometry = '%dx%d+%d+%d' % (width, height, (screenwidth - width) / 2, (screenheight - height) / 2) self.geometry(geometry) self.minsize(width=width, height=height) def login(self): username = self.tk_input_username_label.get() password = self.tk_input_password_label.get() return username, password class Win(WinGUI): def __init__(self, controller): self.ctl = controller super().__init__() self.__event_bind() self.__style_config() self.ctl.init(self) def __event_bind(self): self.tk_input_password_label.bind('<Return>', self.ctl.login_submit) self.tk_button_login_button.bind('<Button-1>', self.ctl.login_submit) self.tk_button_login_button.bind('<Return>', self.ctl.login_submit) self.tk_label_register.bind('<Button-1>', self.ctl.register) pass def __style_config(self): pass
-
login/control.py
import tkinter.messagebox from login.ui import WinGUI class Controller: # 导入UI类后,替换以下的 object 类型,将获得 IDE 属性提示功能 ui: WinGUI def __init__(self): self.url = "登录地址" def init(self, ui): """ 得到UI实例,对组件进行初始化配置 """ self.ui = ui def close_windows(self): print("点击了菜单") if self.ui: self.ui.destroy() def version(self): print("点击了菜单") tkinter.messagebox.showinfo("版本信息", "当前版本: V1.0.0") def login_submit(self, evt): u, p = self.ui.login() if u is None or len(u) == 0: tkinter.messagebox.showinfo("登录提示", "请输入登录用户名!") elif p is None or len(p) == 0: tkinter.messagebox.showinfo("登录提示", "请输入登录密码!") print( f"触发了登录操作,地址:{self.url},账号:{u},密码:{p}") # 模拟登录操作 if u == 'admin' and p == 'admin': print(f'登录成功') # 关闭登录窗口 self.ui.destroy() from home.control import Controller as HomeUIController from home.ui import Win as MainWin # 这里创建首页界面,并进入GUI循环,可以将登陆后的Token统一管理,不想管理的可以当作参数传过去不过比较麻烦 app = MainWin(HomeUIController()) app.mainloop() else: tkinter.messagebox.showinfo("登录提示", "登陆失败了,请检查账号密码是否正确.") def register(self, evt): print(f"触发了注册操作,地址:{self.url}")
-
启动类main.py
# 导入窗口控制器 from login.control import Controller as MainUIController # 导入布局文件 from login.ui import Win as MainWin if __name__ == "__main__": app = MainWin(MainUIController()) app.iconbitmap('favicon.ico') # 启动 app.mainloop()
2. 首页界面实现(代码)
-
图片展示
-
home/ui.py
from tkinter import * from tkinter.ttk import * class WinGUI(Tk): def __init__(self): super().__init__() self.__win() # 当前页数 self.index = 1 self.total = 10 self.data = [] self.tk_table_m1ef1meg = self.__tk_table_m1ef1meg(self) self.tk_label_m1ef4id7 = self.__tk_label_m1ef4id7(self) self.tk_input_query = self.__tk_input_query(self) self.tk_button_query = self.__tk_button_query(self) self.tk_button_head = self.__tk_button_head(self) self.tk_button_previous__page = self.__tk_button_previous__page(self) self.tk_button_next_page = self.__tk_button_next_page(self) self.tk_button_last = self.__tk_button_last(self) def __win(self): self.title("项目名称") # 设置窗口大小、居中 width = 800 height = 600 screenwidth = self.winfo_screenwidth() screenheight = self.winfo_screenheight() geometry = '%dx%d+%d+%d' % (width, height, (screenwidth - width) / 2, (screenheight - height) / 2) self.iconbitmap('favicon.ico') self.geometry(geometry) self.minsize(width=width, height=height) def scrollbar_autohide(self, vbar, hbar, widget): """自动隐藏滚动条""" def show(): if vbar: vbar.lift(widget) if hbar: hbar.lift(widget) def hide(): if vbar: vbar.lower(widget) if hbar: hbar.lower(widget) hide() widget.bind("<Enter>", lambda e: show()) if vbar: vbar.bind("<Enter>", lambda e: show()) if vbar: vbar.bind("<Leave>", lambda e: hide()) if hbar: hbar.bind("<Enter>", lambda e: show()) if hbar: hbar.bind("<Leave>", lambda e: hide()) widget.bind("<Leave>", lambda e: hide()) def v_scrollbar(self, vbar, widget, x, y, w, h, pw, ph): widget.configure(yscrollcommand=vbar.set) vbar.config(command=widget.yview) vbar.place(relx=(w + x) / pw, rely=y / ph, relheight=h / ph, anchor='ne') def h_scrollbar(self, hbar, widget, x, y, w, h, pw, ph): widget.configure(xscrollcommand=hbar.set) hbar.config(command=widget.xview) hbar.place(relx=x / pw, rely=(y + h) / ph, relwidth=w / pw, anchor='sw') def create_bar(self, master, widget, is_vbar, is_hbar, x, y, w, h, pw, ph): vbar, hbar = None, None if is_vbar: vbar = Scrollbar(master) self.v_scrollbar(vbar, widget, x, y, w, h, pw, ph) if is_hbar: hbar = Scrollbar(master, orient="horizontal") self.h_scrollbar(hbar, widget, x, y, w, h, pw, ph) self.scrollbar_autohide(vbar, hbar, widget) def __tk_table_m1ef1meg(self, parent): # 表头字段 表头宽度 columns = {"序列": 79, "姓名": 239, "民族": 79, "年龄": 79, "简介": 319} tk_table = Treeview(parent, show="headings", columns=list(columns), ) for text, width in columns.items(): # 批量设置列属性 tk_table.heading(text, text=text, anchor='center') tk_table.column(text, anchor='center', width=width, stretch=False) # stretch 不自动拉伸 tk_table.place(relx=0.0000, rely=0.1817, relwidth=0.9988, relheight=0.7500) return tk_table def __tk_button_next_page(self, parent): btn = Button(parent, text="下一页", takefocus=False, ) btn.place(relx=0.8313, rely=0.9500, relwidth=0.0625, relheight=0.0500) return btn def __tk_button_previous__page(self, parent): btn = Button(parent, text="上一页", takefocus=False, ) btn.place(relx=0.7462, rely=0.9500, relwidth=0.0625, relheight=0.0500) return btn def __tk_label_m1ef4id7(self, parent): label = Label(parent, text="当前第3页/总130页", anchor="center", ) label.place(relx=0.4625, rely=0.9500, relwidth=0.1938, relheight=0.0500) return label def __tk_input_query(self, parent): ipt = Entry(parent, ) ipt.place(relx=0.0000, rely=0.1183, relwidth=0.2500, relheight=0.0500) return ipt def __tk_button_query(self, parent): btn = Button(parent, text="搜索", takefocus=False, ) btn.place(relx=0.2612, rely=0.1183, relwidth=0.0788, relheight=0.0500) return btn def __tk_button_head(self, parent): btn = Button(parent, text="首页", takefocus=False, ) btn.place(relx=0.6675, rely=0.9500, relwidth=0.0625, relheight=0.0500) return btn def __tk_button_last(self, parent): btn = Button(parent, text="尾页", takefocus=False, ) btn.place(relx=0.9187, rely=0.9500, relwidth=0.0625, relheight=0.0500) return btn def page_add_index(self): # 下一页 if self.index < self.total: self.index += 1 return self.index def page_sub_index(self): # 上一页 if self.index > 1: self.index -= 1 return self.index def update_table(self): if self.data and len(self.data) > 0: if self.tk_table_m1ef1meg.get_children(): for get_child in self.tk_table_m1ef1meg.get_children(): self.tk_table_m1ef1meg.delete(get_child) for index, datum in enumerate(self.data): self.tk_table_m1ef1meg.insert("", "end", text='', values=( index + 1, datum.get('name', ''), datum.get('nation', ''), datum.get('age', ''), datum.get('info', ''))) class Win(WinGUI): def __init__(self, controller): self.ctl = controller super().__init__() self.__event_bind() self.__style_config() self.config(menu=self.create_menu()) self.ctl.init(self) def create_menu(self): """ 创建自定义的菜单,可以不要 """ menu = Menu(self, tearoff=False) menu.add_cascade(label="设置", menu=self.menu_m1ecaoyu(menu)) menu.add_cascade(label="帮助", menu=self.menu_m1eccdqt(menu)) return menu def menu_m1ecaoyu(self, parent): menu = Menu(parent, tearoff=False) menu.add_command(label="关闭界面", command=self.ctl.close_windows) return menu def menu_m1eccdqt(self, parent): menu = Menu(parent, tearoff=False) menu.add_command(label="版本信息", command=self.ctl.version) return menu def __event_bind(self): """ 此处对组件绑定事件 """ # 搜索框绑定回车事件,回车直接搜索 self.tk_input_query.bind('<Return>', self.ctl.select_member_data) # 搜索按钮绑定点击事件 self.tk_button_query.bind('<Button-1>', self.ctl.select_member_data) # 首页事件 self.tk_button_head.bind('<Button-1>', lambda event: self.ctl.load_member_data_next(self.index)) self.tk_button_last.bind('<Button-1>', lambda event: self.ctl.load_member_data_next(self.total)) self.tk_button_previous__page.bind('<Button-1>', lambda event: self.ctl.load_member_data_next(self.page_sub_index())) self.tk_button_next_page.bind('<Button-1>', lambda event: self.ctl.load_member_data_next(self.page_add_index())) pass def __style_config(self): pass
3. 打包build.py(与main.py同级目录)
import subprocess
def main_windows_package():
# 定义 PyInstaller 命令
pyinstaller_command = [
'pyinstaller', '--onefile', '-w',
'--icon', 'favicon.ico', 'main.py']
try:
subprocess.run(pyinstaller_command, check=True)
except subprocess.CalledProcessError as e:
print(f"An error occurred while running PyInstaller: {e}")
if __name__ == '__main__':
main_windows_package()
4. 打包安装包
各位可以自行选择工具,由于此处项目过于简单,即开即用没有做打包的脚本,后续做到需要打包时会在后面写出来,当然有需要的也可以留言,看到后可以义务帮助。
执行了 build.py 后同级会出现一个dist目录,里边会有main.exe可执行文件,这个就是程序打包后的啦,好啦,到此结束。