使用Python3自带GUI tkinter 做的图形化操作SQLite3数据库的工具 v1.12 修修补补将就用
#_*_ coding:utf8 _*_
## Python3 GUI DB SQLite3 图形化 操作 SQLite3 数据库
## Python 自带 GUI tkinter 使用示例
## V1.12
## BUG 修改数据时,如果主键有值和自定义空值表示相同,则无法修改,需设置新字符串表示空值后才能修改
## 修复一些大小BUG,修修补补继续将就着用吧!import sqlite3 # Python 自带 SQLite3 模块
import re
from tkinter import *
from tkinter import filedialog # 选择文件用
from tkinter import ttk # 下拉菜单控件在ttk中
import tkinter.messagebox # 弹出提示对话框
import tkinter.simpledialog # 弹出对话框,获取用户输入
import os # 导出文件要用到
import time # 导出文件要用到
import csv # CSV文件操作模块,用于导出数据
#from openpyxl import Workbook # Excel文件操作模块,用于导出数据(第三方模块,在需要时加载)import logging # 日志模块
Log = logging.getLogger('__name__') # 获取实例
formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s') # 指定logger输出格式
file_handler = logging.FileHandler('PY3_SQLite3.log') # 日志文件路径
file_handler.setFormatter(formatter) # 可以通过setFormatter指定输出格式
Log.addHandler(file_handler) # 为logger添加的日志处理器## 同时在终端显示
console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
Log.addHandler(console)# 设置记录的日志级别
Log.setLevel(logging.DEBUG) # 记录所有操作过程信息
#Log.setLevel(logging.INFO) # 记录重点操作过程信息
#Log.setLevel(logging.WARNING) # 记录改动数据库的信息
#Log.setLevel(logging.ERROR) # 记录已知错误信息
#Log.setLevel(logging.CRITICAL) # 严重错误#####################
## Python 全局变量 ##
####################### 存储数据库信息
DB_INFO = {}
DB_INFO['数据库文件'] = '' # 记录操作的数据库文件路径
DB_INFO['数据库连接'] = '' # 保存打开的数据库连接对象,方便后续直接调用
DB_INFO['数据库游标'] = '' # 保存当前的数据库游标对象,方便往下翻页
DB_INFO['字段名列表'] = [] # 保持上一次成功查询的字段名
DB_INFO['LAST_SELECT'] = '' # 记录上次成功执行查询语句,方便在修改数据表后能自动刷新到相同页面
字典_查询字段_坐标_对象 = {} # KEY=控件坐标 || VAULE=控件对象 || {(控件行号,控件列号):控件对象} || { (0,0):obj }
字典_查询字段_坐标_初值 = {} # KEY=控件坐标 || VAULE=初始值 || {(控件行号,控件列号):初始值} || { (0,0):123 }
字典_查询结果_坐标_对象 = {}
字典_查询结果_坐标_初值 = {}
字典_添加记录_坐标_对象 = {}
字典_添加记录_坐标_初值 = {}
字典_创建表_字段信息 = {}
字典_新加字段信息 = {}
字典_对象存储 = {} # {'文本编辑对象':''} 大文本编辑框用##########
## 函数 ##
############ 通过外置字体计算字符串像素宽返回Entry标准宽
try:from PIL import ImageFontfont_Arial_8 = ImageFont.truetype("Arial.ttf", 8, encoding="utf-8")
except Exception as e:print(e)加载外置字体失败 = 0
else:加载外置字体失败 = 1def 计算字符串像素宽返回Entry标准宽(字符串, 外置字体=加载外置字体失败):if 字符串 == None: # 空值return(4)if 外置字体 == 1:字符串_w, 字符串_h = font_Arial_8.getsize(字符串)#print(f"字符串 {字符串} 像素宽({字符串_w}) 像素高({字符串_h})")return(字符串_w//4+2) # 像素级精确控制else:return(len(字符串)*2) # 统一给2倍的量## 打开SQLite3数据库文件,不存在会自动创建
def DEV_SQLite3_OPEN(DB_File):try:conn = sqlite3.connect(DB_File) # 尝试打开数据库文件except Exception as e:E = '打开数据库文件失败 ' + str(e)return(1, E) # 返回错误代码1和失败原因else:return(0, conn) # 返回成功代码0和数据库连接对象## 执行SQL查询语句(DQL - 数据查询语言 SELECT 或 SQLite3 PRAGMA 命令),返回执行状态和执行结果(数据列表)
def DEF_SQL_查询和返回(SQL_CMD):数据库连接对象 = DB_INFO['数据库连接']try:游标对象 = 数据库连接对象.cursor() # 创建一个游标except Exception as e:CRITICAL = f'创建游标失败 {e}'Log.critical(CRITICAL)return(1, CRITICAL)else:try:游标对象.execute(SQL_CMD)except Exception as e:ERROR = f'执行SQL语句 {SQL_CMD} 失败 {e}'Log.error(ERROR)TEXT_数据库操作日志.insert(0.0, ERROR+'\n', 'tag_e')Log.debug(f'游标对象={游标对象} 保持游标')return(1, ERROR)else:全部记录 = 游标对象.fetchall()游标对象.close()DB_INFO['数据库游标'] = ''Log.debug(f'游标对象={游标对象} 关闭游标')INFO = f'执行SQL语句 {SQL_CMD} 成功 返回 {len(全部记录)} 条记录'TEXT_数据库操作日志.insert(0.0, INFO+'\n', 'tag_i')return(0, 全部记录)## 执行SQL查询语句,返回执行状态和执行结果(数据列表,字段列表)导出使用
def DEF_SQL_查询和返回_数据列表_字段列表(SQL_CMD):数据库连接对象 = DB_INFO['数据库连接']try:游标对象 = 数据库连接对象.cursor()except Exception as e:CRITICAL = f'创建游标失败 {e}'Log.critical(CRITICAL)return(1, CRITICAL)else:try:游标对象.execute(SQL_CMD)except Exception as e:游标对象.close()ERROR = f'执行SQL语句 {SQL_CMD} 失败 {e}'Log.error(ERROR)TEXT_数据库操作日志.insert(0.0, ERROR+'\n', 'tag_e')Log.debug(f'游标对象={游标对象} 关闭游标')return(1, ERROR)else:全部记录 = 游标对象.fetchall() # 获取全部查询数据记录游标对象_字段名列表 = 游标对象.description # 获取查询结果的字段信息字段名列表 = [i[0] for i in 游标对象_字段名列表] # 整理成字段名列表游标对象.close()Log.debug(f'游标对象={游标对象} 关闭游标')INFO = f'执行SQL语句 {SQL_CMD} 成功 返回 {len(全部记录)} 条记录'TEXT_数据库操作日志.insert(0.0, INFO+'\n', 'tag_i')return(0, 全部记录, 字段名列表)## SQLite3 查找主键,为避免错误修改,数据表必须有主键,根据主键修改对应行数据,没有主键的表只能通过命令框手写语句修改
def 查数据表主键信息(数据表名):当前字段列表 = DB_INFO['字段名列表']SQLite3_CMD_PRAGMA = f'PRAGMA table_info({数据表名})' # SQLite3 获取的表字段信息命令R = DEF_SQL_查询和返回(SQLite3_CMD_PRAGMA)if R[0] == 0:字段信息 = R[1] # 示例 [(0, 'ID', 'INTEGER', 1, None, 1), (1, 'A', 'TEXT', 0, None, 0)]L_主键 = [i[1] for i in 字段信息 if i[5]!=0] # 字段名在第2列,第6列为非0表示是主键if L_主键 == []:Log.debug(f'查数据表主键信息 没有主键 数据表名={数据表名} 字段信息={字段信息}')return(1, f'数据表名={数据表名} 没有主键,为防止自动操作出错,请通过手工执行SQL语句操作')else:P_未出现的主键 = set(L_主键) - set(当前字段列表) # 集合计算,找出未出现在显示字段的主键if len(P_未出现的主键) != 0:Log.debug(f'查数据表主键信息 数据表名={数据表名} 字段信息={字段信息} L_主键={L_主键} 当前字段列表={当前字段列表} P_未出现的主键={P_未出现的主键}')return(1, f'数据表名={数据表名} 主键={L_主键} 未显示的主键={P_未出现的主键},为防止自动操作出错,请通过手工执行SQL语句操作')else:L_主键信息 = []for 主键名 in L_主键:列号 = 当前字段列表.index(主键名)主键信息 = (列号,主键名)L_主键信息.append(主键信息)Log.debug(f'查数据表主键信息 数据表名={数据表名} 字段信息={字段信息} L_主键={L_主键} 当前字段列表={当前字段列表} L_主键信息={L_主键信息} 主键全部在场')return(0, L_主键信息)else:ERROR = f'查数据表主键信息 {R[1]}'Log.error(ERROR)return(1, ERROR)## 执行SQL查询语句,直接显示在界面,不返回
## SQL_CMD:查询语句
## 显示开始行定位:从指定行后开始显示,默认值为0,表示从头全部显示
def DEF_SQL_查询和显示(SQL_CMD, 显示开始行定位=0):数据库连接对象 = DB_INFO['数据库连接']try:游标对象 = 数据库连接对象.cursor() # 创建一个游标except Exception as e:CRITICAL = '创建游标失败' + str(e)Log.critical(CRITICAL)tkinter.messagebox.showerror(title='CRITICAL', message=CRITICAL)else:try:游标对象.execute(SQL_CMD)except Exception as e:ERROR = f'执行SQL语句 {SQL_CMD} 失败 {e}'Log.error(ERROR)TEXT_数据库操作日志.insert(0.0, ERROR+'\n', 'tag_e')tkinter.messagebox.showerror(title='ERROR', message=ERROR)else:DEBUG = f'执行SQL语句 {SQL_CMD} 成功'Log.debug(DEBUG)TEXT_数据库操作日志.insert(0.0, DEBUG+'\n', 'tag_i')DB_INFO['LAST_SELECT'] = SQL_CMD # 保存成功执行的SQL查询语句SV_最后查询语句.set(SQL_CMD) # 显示成功执行的SQL查询语句游标对象_字段名列表 = 游标对象.description # 获取字段信息字段名列表 = [i[0] for i in 游标对象_字段名列表]DB_INFO['字段名列表'] = 字段名列表 # 保存字段名查询结果SV_查询字段列表.set(字段名列表) # 显示字段名查询结果if 显示开始行定位 > 0:丢弃记录 = 游标对象.fetchmany(显示开始行定位) # 先从SQL查询结果中取编辑位置前面的内容部分,丢弃IV_已显示记录数.set(显示开始行定位)else:IV_已显示记录数.set(0) ### 用于修改数据库后,再次显示在修改位置IV_上次分页行数.set(0) ### 用于修改数据库后,再次显示在修改位置## 分页控制## 游标对象.fetchmany(<=0) 和 游标对象.fetchall() 效果一样,为读取全部数据分页限制行数 = 分页行数.get()if 分页限制行数 > 0: # 分页限制行数 > 0 读取部分记录,可分页显示部分记录 = 游标对象.fetchmany(分页限制行数) # 从SQL查询结果中取指定行数的记录,如果查询结果为空则返回空列表实际读取记录行数 = len(部分记录)IV_已显示记录数.set(IV_已显示记录数.get() + 实际读取记录行数) ### 用于修改数据库后,再次显示在修改位置IV_上次分页行数.set(实际读取记录行数) ### 用于修改数据库后,再次显示在修改位置if 部分记录 != []:字段和数据的存储和展示(字段名列表, 部分记录) # 在显编框展示结果,保存结果到全局变量,可以进行修改操作if 实际读取记录行数 < 分页限制行数: # 已经全部显示,无法分页游标对象.close() # 关闭游标对象DB_INFO['数据库游标'] = '' # 清空数据库游标对象按钮_显编框下一页['state'] = 'disabled' # 禁止下一页按钮(第一次显示就全部显示完整,不需要下一页按钮)DEBUG = f"游标对象={游标对象} 关闭游标(数据<分页第一页) 设置 DB_INFO['数据库游标']='' 设置下一页按钮禁止使用"Log.debug(DEBUG)else:DB_INFO['数据库游标'] = 游标对象 # 缓存数据库游标对象按钮_显编框下一页['state'] = 'normal' # 启用下一页按钮DEBUG = f"游标对象={游标对象} 缓存游标(数据>=分页第一页)(用于继续读取显示下一页) 设置 DB_INFO['数据库游标']=游标对象 设置下一页按钮可以使用"Log.debug(DEBUG)else:字段和数据的存储和展示(字段名列表, [])游标对象.close() # 关闭游标对象DB_INFO['数据库游标'] = '' # 清空数据库游标对象按钮_显编框下一页['state'] = 'disabled' # 禁止下一页按钮DEBUG = f"游标对象={游标对象} 关闭游标(数据=空,不用分页) 设置 DB_INFO['数据库游标']='' 设置下一页按钮禁止使用"Log.debug(DEBUG)else: # 分页限制行数 <= 0 读取全部记录,不分页全部记录 = 游标对象.fetchall() # 从SQL查询结果中取出全部的记录字段和数据的存储和展示(字段名列表, 全部记录)游标对象.close() # 关闭游标对象DB_INFO['数据库游标'] = '' # 清空数据库游标对象按钮_显编框下一页['state'] = 'disabled' # 禁止下一页按钮DEBUG = f"游标对象={游标对象} 关闭游标(用户通过设置分页行数<=0一次性读取全部数据) 设置 DB_INFO['数据库游标']='' 设置下一页按钮禁止使用"Log.debug(DEBUG)实际读取记录行数 = len(全部记录) ### 用于修改数据库后,再次显示在修改位置IV_已显示记录数.set(IV_已显示记录数.get() + 实际读取记录行数) ### 用于修改数据库后,再次显示在修改位置IV_上次分页行数.set(实际读取记录行数) ### 用于修改数据库后,再次显示在修改位置def 执行上条查询语句_刷新显示_从头开始():Log.debug(f"执行上条查询语句_刷新显示_从头开始() DB_INFO['LAST_SELECT']={DB_INFO['LAST_SELECT']}")LAST_SELECT = DB_INFO['LAST_SELECT']if LAST_SELECT != '':DEF_SQL_查询和显示(LAST_SELECT)## 用于修改数据库后,再次显示在修改位置
def 执行上条查询语句_刷新显示_从指定行开始():Log.debug(f"执行上条查询语句_刷新显示_从指定行开始() DB_INFO['LAST_SELECT']={DB_INFO['LAST_SELECT']}")LAST_SELECT = DB_INFO['LAST_SELECT']if LAST_SELECT != '':显示开始行定位 = IV_已显示记录数.get() - IV_上次分页行数.get()DEF_SQL_查询和显示(LAST_SELECT, 显示开始行定位)## 分页操作:显示下一页
def DEF_SQL_查询和显示_下一页():游标对象 = DB_INFO['数据库游标'] # 获取缓存的数据库游标对象if 游标对象 == '':CRITICAL = "DEF_SQL_查询和显示_下一页 DB_INFO['数据库游标']='' 保存的游标对象不存在,无法继续读取"Log.critical(CRITICAL)tkinter.messagebox.showerror(title='CRITICAL', message=CRITICAL)else:Log.debug(f"DEF_SQL_查询和显示_下一页 使用保存的游标对象 DB_INFO['数据库游标']={游标对象} 继续读取后续数据")字段名列表 = DB_INFO['字段名列表'] # 从全局变量中取出保存的字段名信息分页限制行数 = 分页行数.get()if 分页限制行数 > 0:部分记录 = 游标对象.fetchmany(分页限制行数)实际读取记录行数 = len(部分记录)if 实际读取记录行数 == 0:游标对象.close() ## 关闭游标对象DB_INFO['数据库游标'] = '' ## 清空数据库游标对象按钮_显编框下一页['state'] = 'disabled' ## 禁止下一页按钮INFO = '已经到底,当前页已经是全部记录'tkinter.messagebox.showinfo(title='INFO', message=INFO)Log.debug(f"游标对象={游标对象} 关闭游标(数据=空,当前页已经是最后)")else:if 实际读取记录行数 < 分页限制行数: # 已经全部显示,没有后续分页游标对象.close() ## 关闭游标对象DB_INFO['数据库游标'] = '' ## 清空数据库游标对象按钮_显编框下一页['state'] = 'disabled' ## 禁止下一页按钮字段和数据的存储和展示(字段名列表, 部分记录)Log.debug(f"游标对象={游标对象} 关闭游标(数据<分页显示行数)")else: # 实际读取和限制显示相等,可能后面还有。可能刚刚读完字段和数据的存储和展示(字段名列表, 部分记录)Log.debug(f"游标对象={游标对象} 保持游标(数据>=分页显示行数)")IV_已显示记录数.set(IV_已显示记录数.get() + 实际读取记录行数) ### 用于修改数据库后,再次显示在修改位置IV_上次分页行数.set(实际读取记录行数) ### 用于修改数据库后,再次显示在修改位置else:全部记录 = 游标对象.fetchall() # 从SQL查询结果中取出全部的记录游标对象.close() ## 关闭游标对象Log.debug(f"游标对象={游标对象} 关闭游标(用户通过设置分页行数<=0一次性读取全部余下数据)")DB_INFO['数据库游标'] = '' ## 清空数据库游标对象按钮_显编框下一页['state'] = 'disabled' ## 禁止下一页按钮if 全部记录 == []:INFO = '已经到底,当前页已经是全部记录'tkinter.messagebox.showinfo(title='INFO', message=INFO)else:字段和数据的存储和展示(字段名列表, 全部记录)实际读取记录行数 = len(全部记录) ### 用于修改数据库后,再次显示在修改位置IV_已显示记录数.set(IV_已显示记录数.get() + 实际读取记录行数) ### 用于修改数据库后,再次显示在修改位置IV_上次分页行数.set(实际读取记录行数) ### 用于修改数据库后,再次显示在修改位置## 非查询的SQL语句(执行一条SQL语句)(每次执行都要打开关闭游标)
def DEF_SQL_执行(SQL_CMD):数据库连接对象 = DB_INFO['数据库连接']try:游标对象 = 数据库连接对象.cursor() # 创建一个游标except Exception as e:CRITICAL = f'创建游标失败 {e}'Log.critical(CRITICAL)return(1, CRITICAL)else:try:游标对象.execute(SQL_CMD)except Exception as e:##失败情况不关闭游标,以免点击下一页失效ERROR = f'执行SQL语句 {SQL_CMD} 失败 {e}'Log.error(ERROR)TEXT_数据库操作日志.insert(0.0, ERROR+'\n', 'tag_e')Log.debug(f'游标对象={游标对象} 保持游标')return(1, ERROR)else:数据库连接对象.commit() # 提交更改游标对象.close()DB_INFO['数据库游标'] = ''Log.debug(f'游标对象={游标对象} 关闭游标')INFO = f'执行SQL语句 {SQL_CMD} 成功'TEXT_数据库操作日志.insert(0.0, INFO+'\n', 'tag_w')return(0, INFO)## 非查询的SQL语句(执行多条SQL语句)(成功的提交更改,跳过错误的语句)
## SQLite3 不能使用事件,无法回退
def DEF_SQL_执行多条_忽略错误语句(L_SQL_CMD):L_执行信息 = []失败标记 = 0for SQL_CMD in L_SQL_CMD:R = DEF_SQL_执行(SQL_CMD)L_执行信息.append(R[1])if R[0] != 0:失败标记 = 1if 失败标记 == 1:return(1, L_执行信息)else:return(0, L_执行信息)## 执行SQL脚本
def DEF_SQL_执行脚本(SQL_SCRIPT):数据库连接对象 = DB_INFO['数据库连接']if 数据库连接对象 == '':CRITICAL = '数据库没有打开'Log.critical(CRITICAL)return(1, CRITICAL)else:try:游标对象 = 数据库连接对象.cursor()except Exception as e:CRITICAL = f'创建游标失败 {e}'Log.critical(CRITICAL)return(1, CRITICAL)else:try:游标对象.executescript(SQL_SCRIPT) # 执行SQL脚本except Exception as e:##失败情况不关闭游标,以免点击下一页失效ERROR = f'执行SQL脚本 {SQL_SCRIPT} 失败 {e}'Log.error(ERROR)TEXT_数据库操作日志.insert(0.0, ERROR+'\n', 'tag_e')Log.debug(f'游标对象={游标对象} 保持游标')return(1, ERROR)else:数据库连接对象.commit() # 提交更改游标对象.close()DB_INFO['数据库游标'] = ''Log.debug(f'游标对象={游标对象} 关闭游标')INFO = f'执行SQL脚本 {SQL_SCRIPT} 成功'TEXT_数据库操作日志.insert(0.0, INFO+'\n', 'tag_w')return(0, INFO)## 查询数据库表,只为更新数据表框内数据表列表
def DEF_查询数据库表():SQL_CMD = 'SELECT NAME FROM sqlite_master' # SQLite3查表命令R = DEF_SQL_查询和返回(SQL_CMD)if R[0] == 0:查询结果 = R[1]if 查询结果 == []:STR_数据表列表内容.set('<空>')else:数据表列表 = [i[0] for i in 查询结果] # [('sqlite_sequence',), ('t0',), ('t1',)]STR_数据表列表内容.set(数据表列表)else:tkinter.messagebox.showerror(title='ERROR', message=R[1])## 清空框内控件
def FRAME_CLEAR(FRAME_NAME):for X in FRAME_NAME.winfo_children():X.destroy()## 在显编框展示结果,保存结果到全局变量,可以进行修改操作
def 字段和数据的存储和展示(L_字段名, LL_数据值):列数 = len(L_字段名)行数 = len(LL_数据值)单元格宽度限制 = IV_单元格限宽.get() # 单元格宽度限制字符数## 记录字段中每个字段需要的Entry宽值L_字段需要宽值 = [计算字符串像素宽返回Entry标准宽(i) for i in L_字段名]Log.debug(f"L_字段名={L_字段名} L_字段需要宽值={L_字段需要宽值}")## 遍历每一列,计算每列可用的最大宽值(取最大值,超过限制的用限制值)L_每列最大宽度 = []for 列号 in range(0, 列数):if L_字段需要宽值[列号] > 单元格宽度限制: # 字段名已经超过最大限制值L_每列最大宽度.append(单元格宽度限制) # 本列采用最大限制值#Log.debug(f"列号={列号} (字段名宽){L_字段需要宽值[列号]} > {单元格宽度限制}(单元格宽度限制) 采用最大限制值")else:## 遍历每行指定列元素本列最大宽度 = L_字段需要宽值[列号] # 字段名的宽设置为本列最大宽的初始值for 行号 in range(0, 行数):字符串_值 = str(LL_数据值[行号][列号])字符串宽值 = 计算字符串像素宽返回Entry标准宽(字符串_值)if 字符串宽值 > 单元格宽度限制: # 已经超过最大限制值本列最大宽度 = 单元格宽度限制 # 本列采用最大限制值#Log.debug(f"列号={列号} 行号={行号} 字符串_值={字符串_值} 字符串宽值={字符串宽值} > {单元格宽度限制}(单元格宽度限制) 采用最大限制值 并终止循环")break # 终止对本列的循环else:if 字符串宽值 > 本列最大宽度:本列最大宽度 = 字符串宽值#Log.debug(f"列号={列号} 行号={行号} 字符串_值={字符串_值} 字符串宽值={字符串宽值} 为最新最大宽值")L_每列最大宽度.append(本列最大宽度)Log.debug(f"计算完成 L_每列最大宽度={L_每列最大宽度}")T0 = time.time()FRAME_CLEAR(LabelFrame_显编框) # 清空框内控件Log.debug(f"清空框内控件 用时={time.time()-T0:.3f}秒")## 创建画布画布 = Canvas(LabelFrame_显编框, bg='#00CED1') # 创建画布## 在画布里创建 Frame画布Frame框 = Frame(画布)字段框 = Frame(画布Frame框)字段框.grid(row=0,column=0,sticky='NW')数据框 = Frame(画布Frame框)数据框.grid(row=1,column=0,sticky='NW')## 动态设置画布窗口宽高:根据主主窗口的参数设置限宽限高主窗口大小和位置 = top.geometry()主窗口宽, 主窗口高, 主窗口X, 主窗口Y = re.findall('[0-9]+', 主窗口大小和位置)画图限高 = int(主窗口高) -500## 设置画布参数总行数 = 行数 + 1 # 数据行数n + 字段行数1## 画布可滚动显示的最大宽和高(要刚好能放下画布里的Frame里的全部控件)## 一个单元格(默认宽为144像素,20字符+4像素打底)## Entry 字符宽度与像素关系:# Entry 最小宽度字符为1(设置为0也会自动设置为1)# 宽度 = 1字符 = 11像素(1*7 +4)# 宽度 = 2字符 = 18像素(2*7 +4)画布滚动最右边 = sum(L_每列最大宽度)*7 + 列数*4 # 总字符数量x9像素+每个单元格需要初始4像素画布滚动最下边 = 21*总行数 # Entry 默认高度为 21像素(20像素+1分隔像素)## 滚动能到的宽高必须>=元素总和宽高像素,不然会显示不出画布['scrollregion'] = (0,0,画布滚动最右边,画布滚动最下边) # 一个元组 tuple (w, n, e, s) ,定义了画布可滚动的最大区域,w 为左边,n 为头部,e 为右边,s 为底部## 动态设置显示画布固定显示宽和高(要和主显示框的大小匹配)画布['width'] = 画布滚动最右边 # 使用实际需要的宽度即可画布['height'] = min(画图限高, 画布滚动最下边) # 取最小值,防止纵向撑爆Scrollbar_画布_竖 = Scrollbar(LabelFrame_显编框, command=画布.yview) # 创建竖滚动条Scrollbar_画布_竖.grid(row=0,column=1,sticky=S+W+E+N) # 竖滚动条定位Scrollbar_画布_横 = Scrollbar(LabelFrame_显编框, command=画布.xview, orient=HORIZONTAL) # 创建横滚动条Scrollbar_画布_横.grid(row=1,column=0,sticky=S+W+E+N) # 横滚动条定位画布.config(xscrollcommand=Scrollbar_画布_横.set, yscrollcommand=Scrollbar_画布_竖.set) # 自动设置滚动幅度画布.create_window((0,0), window=画布Frame框, anchor='nw') # 显示画布内 画布Frame框 控件画布.grid(row=0,column=0,sticky='nw') # 显示画布,不显示画布多余部分#画布.grid(row=0,column=0,sticky='nsew') # 显示画布,画布填满空间## 清除全局字典的内容字典_查询字段_坐标_对象.clear()字典_查询字段_坐标_初值.clear()字典_查询结果_坐标_对象.clear()字典_查询结果_坐标_初值.clear()## 字段名for 列 in range(0, 列数):初始值 = str(L_字段名[列]) # 转成字符串字典_查询字段_坐标_对象[(0,列)] = Entry(字段框, width=L_每列最大宽度[列], bg='#00BFFF') # 控件对象放到指定框内,并保存对象到对象字典中,只读后颜色失效字典_查询字段_坐标_初值[(0,列)] = 初始值 # 保存初始值字典_查询字段_坐标_对象[(0,列)].insert(0, 初始值) # 写入Entry作为初始值(从标签内开头开始填充新值,因为是全新标签,所有不用先删除里面内容)字典_查询字段_坐标_对象[(0,列)].grid(row=0,column=列,sticky='W') # Entry排放到指定位置字典_查询字段_坐标_对象[(0,列)].bind("<Button-3>", DEF_弹出_字段框_右键菜单) # 每个控件对象都绑定右键菜单事件## 数据值for 行 in range(0, 行数):for 列 in range(0, 列数):初始值 = LL_数据值[行][列]if 初始值 == None:初始值 = SV_自定义空值字符串表示.get() # 设置空值的字符串表示 '(NULL)'else:初始值 = str(LL_数据值[行][列]) # 转成字符串字典_查询结果_坐标_对象[(行,列)] = Entry(数据框, width=L_每列最大宽度[列]) # 控件对象放到指定框内,并保存对象到对象字典中字典_查询结果_坐标_初值[(行,列)] = 初始值 # 保存初始值字典_查询结果_坐标_对象[(行,列)].insert(0, 初始值) # 写入Entry作为初始值(从标签内开头开始填充新值,因为是全新标签,所有不用先删除里面内容)字典_查询结果_坐标_对象[(行,列)].grid(row=行,column=列,sticky='W') # Entry排放到指定位置字典_查询结果_坐标_对象[(行,列)].bind("<Button-1>", 左键单击) # 每个控件对象都绑定左键单击事件字典_查询结果_坐标_对象[(行,列)].bind("<Button-3>", DEF_弹出_数据框_右键菜单) # 每个控件对象都绑定右键菜单事件字典_查询结果_坐标_对象[(行,列)].bind('<KeyRelease>', 键盘任意输入事件) # 每当输入内容时执行一次函数##############
## 按钮函数 ##
################ 数据库文件操作 选择
def DEF_按钮_选择数据库文件():数据库文件 = filedialog.askopenfilename()DB_INFO['数据库文件'] = 数据库文件 # 同步全局字典DB_FULL_NAME.set(数据库文件) # 实时更新显示Log.debug(f"DEF_按钮_选择数据库文件 DB_INFO={DB_INFO}")## 数据库文件操作 打开
def DEF_按钮_打开数据库():FRAME_CLEAR(LabelFrame_显编框) # 先清空显编框内控件DB_File = DB_FULL_NAME.get() # 方便手动输入数据库名if DB_File.strip() == '': # 截掉头尾的空格后为空ERROR = '无效数据库名'tkinter.messagebox.showerror(title='ERROR', message=ERROR)else:R = DEV_SQLite3_OPEN(DB_File)if R[0] == 0:DB_INFO['数据库连接'] = R[1] # 同步全局字典:保存数据库连接对象DEF_查询数据库表()Frame_数据表列表显示框.grid(row=1,column=0,sticky='NW',rowspan=2) # 显示else:ERROR = f'打开数据库文件"{DB_File}"失败,错误信息{R[1]}'tkinter.messagebox.showerror(title='ERROR', message=ERROR)## 关闭数据库
def DEF_按钮_关闭数据库():数据库连接对象 = DB_INFO['数据库连接']if 数据库连接对象 == '':Log.debug("DEF_按钮_关闭数据库 数据库未打开,无需关闭数据库连接对象")else:数据库连接对象.close()Frame_数据表列表显示框.grid_forget()FRAME_CLEAR(LabelFrame_显编框) # 清空框内控件## 清空全局变量字典_查询字段_坐标_对象 = {}字典_查询字段_坐标_初值 = {}字典_查询结果_坐标_对象 = {}字典_查询结果_坐标_初值 = {}字典_添加记录_坐标_对象 = {}字典_添加记录_坐标_初值 = {}字典_创建表_字段信息 = {}字典_新加字段信息 = {}字典_对象存储 = {}## 事件函数:双击列表中的数据表名查询表内全部记录
def DEF_双击表名(event):当前选择 = Listbox_数据表列表.curselection() # 列表数据定位 (序号数,)当前选择值 = Listbox_数据表列表.get(当前选择) # 对应的值Log.debug(f"DEF_双击表名 当前选择={当前选择} 当前选择值={当前选择值}") # 如:当前选择 (0,) 当前选择值 001if 当前选择值 != '<空>':DB_TABLE_NAME.set(当前选择值) # 更新表名变量## 打开数据表(查询表内容)SQL_CMD = f'SELECT * FROM {当前选择值}'DEF_SQL_查询和显示(SQL_CMD)else:ERROR = "<空> 是数据库内无数据表的提示,请新建表后再打开"tkinter.messagebox.showerror(title='ERROR', message=ERROR)## 返回起始页
def DEF_按钮_显编框起始页():Log.debug('DEF_按钮_显编框起始页')执行上条查询语句_刷新显示_从头开始()## 上一页
def DEF_按钮_显编框上一页():Log.debug("DEF_按钮_显编框上一页")分页限制行数 = 分页行数.get()已显示记录数 = IV_已显示记录数.get()当前显示行数 = IV_上次分页行数.get()上一页可显示最大行数 = 已显示记录数-当前显示行数if 上一页可显示最大行数 > 0: # 上一页有内容if 分页限制行数 >= 上一页可显示最大行数: # 上一页内容不足一个分页,全部显示到一个分页Log.debug(f"从头显示 (分页限制行数){分页限制行数}>={上一页可显示最大行数}(上一页可显示最大行数)")执行上条查询语句_刷新显示_从头开始()tkinter.messagebox.showinfo(title='INFO', message='已经到最前面')else:定位显示开始位置行号 = 已显示记录数-当前显示行数-分页限制行数Log.debug(f"往前显示{分页限制行数}(分页限制行数)行数据(定位显示开始位置行号={定位显示开始位置行号}) (已显示记录数){已显示记录数}>{分页限制行数}(分页限制行数)")LAST_SELECT = DB_INFO['LAST_SELECT']if LAST_SELECT != '':DEF_SQL_查询和显示(LAST_SELECT, 定位显示开始位置行号)else:ERROR = "找不到上次执行的查询语句 DB_INFO['LAST_SELECT']=''"Log.error(ERROR)tkinter.messagebox.showerror(title='ERROR', message=ERROR)else:ERROR = f'(已显示记录数){已显示记录数}-{当前显示行数}(当前显示行数)={上一页可显示最大行数}(上一页可显示最大行数) 判断<=0 无上一页内容'Log.error(ERROR)tkinter.messagebox.showerror(title='ERROR', message=ERROR)## 下一页
def DEF_按钮_显编框下一页():Log.debug("DEF_按钮_显编框下一页")DEF_SQL_查询和显示_下一页()## 选择本地SQL脚本文件
def DEF_按钮_选择SQL脚本文本():本地SQL脚本文本 = filedialog.askopenfilename()DB_SQL_SCRIPT_FILE.set(本地SQL脚本文本) # 实时更新显示## 执行本地SQL脚本文件
def DEF_按钮_执行SQL脚本文件():脚本文件 = DB_SQL_SCRIPT_FILE.get().strip()if 脚本文件 == '':ERROR = '没有脚本文件可以执行'tkinter.messagebox.showerror(title='错误', message=ERROR)else:try:f = open(脚本文件, 'r')except Exception as e:ERROR = str(e)tkinter.messagebox.showerror(title='错误', message=ERROR)else:脚本文件内容 = f.read()f.close()if 脚本文件内容 != '':R = DEF_SQL_执行脚本(脚本文件内容)if R[0] == 0:执行上条查询语句_刷新显示_从头开始()tkinter.messagebox.showinfo(title='成功', message=R[1])else:tkinter.messagebox.showerror(title='失败', message=R[1])else:ERROR = 'SQL脚本文件无内容'tkinter.messagebox.showerror(title='错误', message=ERROR)## 修改记录:遍历全部显示内容,找出被修改的部分,组成SQL语句并执行
## 为避免错误修改,显示行必须有主键,根据主键修改对应行数据
def DEF_按钮_确认修改数据库():数据表名 = DB_TABLE_NAME.get()R = 查数据表主键信息(数据表名)if R[0] == 0:L_主键信息 = R[1] # [(列号,主键名), (列号,主键名)]Log.debug(f'确认修改数据库 L_主键信息={L_主键信息}')## 遍历数据行,找出被修改的行,生成以WHERE限定部分语句为Key的修改字典信息D_修改信息 = {} # {'WHERE 主键1=主键1值 AND 主键2=主键2值':[(字段名,新值),(字段名,新值)]}for i in 字典_查询结果_坐标_对象: # 遍历编辑框(查询结果框)中的全部控件控件旧值 = 字典_查询结果_坐标_初值[i]控件新值 = 字典_查询结果_坐标_对象[i].get()if 控件旧值 != 控件新值: # 当原值和当前值不一致,说明此控件值被修改行号,列号 = i # 提取当前控件的坐标字段名 = 字典_查询字段_坐标_初值[(0,列号)] # 字段名存储在字段全局变量中字段值 = 控件新值if 字段值 == SV_自定义空值字符串表示.get():VALUE_修改部分语句 = f'{字段名}=NULL'else:VALUE_修改部分语句 = f'{字段名}="{字段值}"'#L_修改限定部分 = []#Log.debug(f"发现 被改值控件 (行号,列号)={行号,列号}")#for 列号,主键名 in L_主键信息:# 此行主键值 = 字典_查询结果_坐标_初值[(行号,列号)]# print(f"控件 (行号,列号)={行号,列号} (列号,主键名)={列号,主键名} 此行主键值={此行主键值} type={type(此行主键值)}")# if 此行主键值 == SV_自定义空值字符串表示.get():# 修改限定部分 = f'{主键名} IS NULL'# else:# 修改限定部分 = f'{主键名} = "{此行主键值}"'# L_修改限定部分.append(修改限定部分)L_修改限定部分 = [f'{主键名} IS NULL' if 字典_查询结果_坐标_初值[(行号,列号)]==SV_自定义空值字符串表示.get() else f'{主键名}="{字典_查询结果_坐标_初值[(行号,列号)]}"' for 列号,主键名 in L_主键信息]Log.debug(f"L_修改限定部分={L_修改限定部分}")KEY_修改限定部分语句 = 'WHERE ' + ' AND '.join(L_修改限定部分)## 把同行的修改信息合并在一起,方便整合成一条修改语句if KEY_修改限定部分语句 not in D_修改信息:D_修改信息[KEY_修改限定部分语句] = [VALUE_修改部分语句]else:D_修改信息[KEY_修改限定部分语句].append(VALUE_修改部分语句)Log.debug(f"DEF_按钮_确认修改数据库 D_修改信息={D_修改信息}")## 根据 D_修改信息 制作数据库语句L_SQL_CMD = []for KEY_SQL_WHERE in D_修改信息:合并修改部分语句 = ', '.join(D_修改信息[KEY_SQL_WHERE])SQL_CMD = f'UPDATE {数据表名} SET {合并修改部分语句} {KEY_SQL_WHERE}'L_SQL_CMD.append(SQL_CMD)Log.debug(f"DEF_按钮_确认修改数据库 L_SQL_CMD={L_SQL_CMD}")if L_SQL_CMD == []:DEBUG = '内容没有改变,不操作数据库'Log.debug(DEBUG)TEXT_数据库操作日志.insert(0.0, DEBUG+'\n', 'tag_i')else:## 依次执行SQL语句成功列表 = []失败列表 = []for i in L_SQL_CMD:RR = DEF_SQL_执行(i)if RR[0] == 0:成功列表.append(RR[1])else:失败列表.append(RR[1])## 失败任意一条SQL语句就弹框提示if 失败列表 != []:SHOW_STR = ''for i in 成功列表:SHOW_STR += i + '\n'for i in 失败列表:SHOW_STR += i + '\n'tkinter.messagebox.showerror(title='ERROR', message=SHOW_STR)## 成功任意一条语句后,更新显示表格if 成功列表 != []:执行上条查询语句_刷新显示_从指定行开始()else:tkinter.messagebox.showerror(title='ERROR', message=R[1])## 按钮执行后隐藏按钮_确认修改数据库.grid_forget() # 隐藏 按钮_确认修改数据库## 执行用户输入的SQL语句
def DEF_按钮_执行SQL语句():SQL_CMD = 文本框_命令行.get(1.0, END).rstrip('\n') # 获取编写的SQL语句,去掉后面的回车符号if SQL_CMD.strip() != '':## 区别处理查询语句和其他语句if SQL_CMD.lstrip()[0:6].lower() in ('select', 'pragma'):DEF_SQL_查询和显示(SQL_CMD) # 调用查询语句专用函数else:R = DEF_SQL_执行(SQL_CMD) # 调用非查询语句函数R_TEXT = R[1]if R[0] == 0:## 操作成功后更新一下显示/编辑框执行上条查询语句_刷新显示_从头开始()tkinter.messagebox.showinfo(title='成功', message=R_TEXT)else:tkinter.messagebox.showerror(title='失败', message=R_TEXT)if SQL_CMD.replace(' ', '')[0:11].lower() == 'createtable':DEF_查询数据库表() # 更新一下数据库表显示else:ERROR = '没有输入SQL语句'tkinter.messagebox.showerror(title='错误', message=ERROR)## 执行用户输入的SQL脚本
def DEF_按钮_执行SQL脚本():SQL_CMD = 文本框_命令行.get(1.0, END).rstrip('\n') # 获取编写的SQL语句,去掉后面的回车符号if SQL_CMD.strip() != '':R = DEF_SQL_执行脚本(SQL_CMD) # 调用非查询语句函数if R[0] == 0:## 操作成功后更新一下显示/编辑框执行上条查询语句_刷新显示_从头开始()tkinter.messagebox.showinfo(title='成功', message=R[1])else:tkinter.messagebox.showerror(title='失败', message=R[1])else:ERROR = 'SQL脚本无内容'tkinter.messagebox.showerror(title='错误', message=ERROR)## 清除命令框里的内容
def DEF_按钮_清屏():文本框_命令行.delete(0.0, END)文本框_命令行.focus_set()###########################
## 创建新窗口 添加新记录 ##
def DEF_新增记录():数据表名 = DB_TABLE_NAME.get()SQLite3_CMD = f'PRAGMA table_info({数据表名})'R = DEF_SQL_查询和返回(SQLite3_CMD)查询结果 = R[1] # 数据或报错信息if R[0] == 0:字段列表 = [i[1] for i in 查询结果]DEF_弹出新加记录窗口(字段列表)else:tkinter.messagebox.showerror(title='ERROR', message=查询结果)
def 新加记录窗口_确定(窗口对象):Log.debug("新加记录窗口_确定")数据表名 = DB_TABLE_NAME.get()## 查找有值的字段,拼成 INSERT SQL 语句L_插入字段名 = []L_插入字段值 = []字段数量 = len(字典_添加记录_坐标_初值)//2 # 添加记录框总是显示2行,第一行为字段名,第二行初始全为空,字段数量=2行总格子数的一半for i in range(0, 字段数量): # 按序号遍历每一列字段名 = 字典_添加记录_坐标_对象[(0,i)].get() # 字段名都在第一行字段值 = 字典_添加记录_坐标_对象[(1,i)].get() # 获取用户输入的值if 字段值 != '': # 如果用户设置的值不是空的L_插入字段名.append(字段名)L_插入字段值.append(字段值)if L_插入字段名 != []: # 不为 [] 说明有插入信息SQL_字段名 = ','.join(L_插入字段名) # ['1', '2'] -> '1,2'SQL_字段值 = '","'.join(L_插入字段值) # ['1', '2'] -> '1","2'SQL_CMD = f'INSERT INTO {数据表名} ({SQL_字段名}) VALUES ("{SQL_字段值}")' # 拼成 INSERT SQL 语句R = DEF_SQL_执行(SQL_CMD)if R[0] == 0:## 成功后,更新显示表格执行上条查询语句_刷新显示_从头开始()窗口对象.withdraw() # 关闭新窗口else:tkinter.messagebox.showerror(title='ERROR', message=R[1])else:tkinter.messagebox.showerror(title='ERROR', message='请填入数据')
def 新加记录窗口_取消(窗口对象):Log.debug("新加记录窗口_取消")窗口对象.withdraw() # 关闭新窗口
def DEF_弹出新加记录窗口(字段名列表):新窗口 = Toplevel()新窗口.title('添加新记录')## 新窗口布局显编框 = Frame(新窗口)按钮框 = Frame(新窗口)显编框.grid(row=0,column=0,sticky='NW')按钮框.grid(row=1,column=0)## 宽高参数行数 = 2列数 = len(字段名列表)## 记录字段中每个字段需要的Entry宽值L_字段需要宽值 = [计算字符串像素宽返回Entry标准宽(i) for i in 字段名列表]Log.debug(f"字段名列表={字段名列表} L_字段需要宽值={L_字段需要宽值}")## 创建画布画布 = Canvas(显编框, bg='#00CED1') # 创建画布画布.grid(row=0,column=0) # 显示画布## 在画布里创建 Frame画布Frame框 = Frame(画布)字段框 = Frame(画布Frame框)字段框.grid(row=0,column=0,sticky='NW')数据框 = Frame(画布Frame框)数据框.grid(row=1,column=0,sticky='NW')## 动态设置画布窗口宽高:根据屏幕分辨率参数设置限宽限高画布限宽 = 屏幕宽 -500 # 比屏幕宽小一点print("画布限宽", 画布限宽)if 画布限宽 < 600:画布限宽 = 600 # 保障最小宽度画布限高 = 屏幕高 -100 # 比屏幕高小一点print("画布限高", 画布限高)if 画布限高 < 250:画布限高 = 250 # 保障最小高度## 设置画布参数总行数 = 行数## 画布可滚动显示的最大宽和高(要刚好能放下画布里的Frame里的全部控件)画布滚动最右边 = sum(L_字段需要宽值)*7 + 列数*4 # 总字符数量x9像素+每个单元格需要初始4像素画布滚动最下边 = 21*总行数 # 20*行数 + 行数*1## 动态设置显示画布固定显示宽和高(要和主显示框的大小匹配)if 画布限宽 > 画布滚动最右边:画布['width'] = 画布滚动最右边else:画布['width'] = 画布限宽 - 30if 画布限高 > 画布滚动最下边:画布['height'] = 画布滚动最下边else:画布['height'] = 画布限高画布['scrollregion'] = (0,0,画布滚动最右边,画布滚动最下边) # 一个元组 tuple (w, n, e, s) ,定义了画布可滚动的最大区域,w 为左边,n 为头部,e 为右边,s 为底部# 竖滚动条Scrollbar_画布_竖 = Scrollbar(显编框, command=画布.yview)Scrollbar_画布_竖.grid(row=0,column=1,sticky=S+W+E+N)# 横滚动条Scrollbar_画布_横 = Scrollbar(显编框, command=画布.xview, orient=HORIZONTAL)Scrollbar_画布_横.grid(row=1,column=0,sticky=S+W+E+N)画布.config(xscrollcommand=Scrollbar_画布_横.set, yscrollcommand=Scrollbar_画布_竖.set) # 自动设置滚动幅度画布.create_window((0,0), window=画布Frame框, anchor='nw')## 在 画布里的Frame里创建控件字典_添加记录_坐标_对象.clear()字典_添加记录_坐标_初值.clear()行 = 0 # 第1行是字段行,序号为0for 列 in range(0, 列数):初始值 = str(字段名列表[列])字典_添加记录_坐标_初值[(行,列)] = 初始值字典_添加记录_坐标_对象[(行,列)] = Entry(字段框, width=L_字段需要宽值[列])字典_添加记录_坐标_对象[(行,列)].insert(0, 初始值)字典_添加记录_坐标_对象[(行,列)].grid(row=行,column=列,sticky='W')字典_添加记录_坐标_对象[(行,列)]['state'] = 'readonly' # 设置为只读,用户不能修改行 = 1 # 第2行是数据行,序号为1for 列 in range(0, 列数):字典_添加记录_坐标_初值[(行,列)] = ''字典_添加记录_坐标_对象[(行,列)] = Entry(数据框, width=L_字段需要宽值[列])字典_添加记录_坐标_对象[(行,列)].grid(row=行,column=列,sticky='W')## 按钮框确定按钮 = Button(按钮框, text='确定', command=lambda 窗口对象=新窗口:新加记录窗口_确定(窗口对象))确定按钮.grid(row=1,column=0)取消按钮 = Button(按钮框, text='取消', command=lambda 窗口对象=新窗口:新加记录窗口_取消(窗口对象))取消按钮.grid(row=1,column=1)显示坐标 = f'+{屏幕宽//2-(画布限宽//2)}+{屏幕高//2}'新窗口.geometry(显示坐标)画布Frame框.grid_columnconfigure(0, weight=1)新窗口.grid_columnconfigure(0, weight=1)
## 创建新窗口 添加新记录 ##
###############################################
## TKinter 主窗口 ##
####################
top = Tk() # 初始化Tk()
top.title('Python3 tkinter GUI 图形化操作 SQLite3 工具') # 设置标题
窗口宽 = 900
窗口高 = 800
# 获取屏幕尺寸以计算布局参数,使窗口居屏幕中央
屏幕宽 = top.winfo_screenwidth()
屏幕高 = top.winfo_screenheight()
主窗口显示位置 = '%dx%d+%d+%d' % (窗口宽, 窗口高, (屏幕宽-窗口宽)/2, (屏幕高-窗口高)/2)
top.geometry(主窗口显示位置)
top.resizable(width=True, height=True) # 设置窗口是否可变长、宽(True:可变,False:不可变)################################
## TKinter 实时更新的全局变量 ##
################################
DB_FULL_NAME = StringVar() # 当前操作的数据库文件名
DB_TABLE_NAME = StringVar() # 当前操作的数据库数据表名
SV_最后查询语句 = StringVar() # 记录上一次的查询语句,用于在修改后刷新显示编辑框内容
SV_查询字段列表 = StringVar() # 查询语句查询结果的字段信息
字段框_定位列 = IntVar()
数据框_定位行 = IntVar()
数据框_定位列 = IntVar()
新建数据表名 = StringVar()
SV_自定义空值字符串表示 = StringVar() # 自定义空值字符串表示
SV_自定义空值字符串表示.set('(NULL)') # 遇到数据库内空值时用'(NULL)'表示,方便操作## 分页
分页行数 = IntVar() # 设置要读取数据的行数
分页行数.set(5) # 设置以5条分页
IV_已显示记录数 = IntVar() ### 用于修改数据库后,再次显示在修改位置
IV_上次分页行数 = IntVar() ### 用于修改数据库后,再次显示在修改位置## 选择本地SQL脚本文件
DB_SQL_SCRIPT_FILE = StringVar() # 选择本地SQL脚本文件## 光标位置记录(当前就是看看,没有实际用途)
IV_光标X轴 = IntVar()
IV_光标Y轴 = IntVar()## 设置单元格宽度自动适应时的最大宽度
IV_单元格限宽 = IntVar() # 自动设置单元格宽度时的最大宽度(字符数)
IV_单元格限宽.set(20) # 设置默认最大20字符宽度## 创建表/复制表提示信息
SV_提示信息_建表 = StringVar()###########################
## 右键菜单 数据表列表框 ##
############################# 事件函数:右键弹出菜单
def 弹出_数据库表_右键菜单(event):光标Y轴值 = event.yprint("光标Y轴值", 光标Y轴值)光标最近项 = Listbox_数据表列表.nearest(光标Y轴值)if 光标最近项 != -1:#根据光标Y轴位置自动选择print("光标最近项", 光标最近项)选项_xoffset, 选项_yoffset, 选项_width, 选项_height = Listbox_数据表列表.bbox(光标最近项)if 选项_yoffset <= 光标Y轴值 <= 选项_yoffset + 选项_height: # 光标落在最近项范围内Listbox_数据表列表.selection_set(光标最近项) # 自动选择光标最近项当前选择值 = Listbox_数据表列表.get(光标最近项)print("当前选择值", 当前选择值)if 当前选择值 != '<空>':DB_TABLE_NAME.set(当前选择值)数据库表_右键菜单_表名处.post(event.x_root, event.y_root) # 光标位置显示菜单Listbox_数据表列表.selection_clear(光标最近项)else:数据库表_右键菜单_空白处.post(event.x_root, event.y_root) # 光标在<空>处显示 数据库表_右键菜单_空白处else: # 光标落在最近项范围外print("光标不在选项上,打开空白处用的右键菜单")当前选择 = Listbox_数据表列表.curselection() # 列表数据定位 (序号数,)if 当前选择 != ():Listbox_数据表列表.selection_clear(当前选择)数据库表_右键菜单_空白处.post(event.x_root, event.y_root) # 光标位置显示空白处菜单else:print("无选择项")
def DEF_右键菜单_打开表():数据库表名 = DB_TABLE_NAME.get()## 打开数据表(查询表内容)SQL_CMD = f'SELECT * FROM {数据库表名}'DEF_SQL_查询和显示(SQL_CMD)
def DEF_右键菜单_删除表():数据库表名 = DB_TABLE_NAME.get()用户决定 = tkinter.messagebox.askquestion(title='请三思...', message='是否确定删除数据表: '+数据库表名) # 返回值为:yes/noif 用户决定 == 'yes':SQL_CMD = f'DROP TABLE {数据库表名}' # 删除表的SQL语句R = DEF_SQL_执行(SQL_CMD)if R[0] == 0:DEF_查询数据库表() # 重新查询数据库表else:tkinter.messagebox.showerror(title='ERROR', message=R[1])else:Log.debug(f"用户决定={用户决定} 取消删除表")
def DEF_右键菜单_编辑表():tkinter.messagebox.showinfo(title='提示', message='SQLite3 没有这个功能,请使用【新建表】或【复制表】生成新表替换旧表')
def DEF_右键菜单_复制表():DEF_弹出创建数据表窗口('复制')
def DEF_右键菜单_新建表():DEF_弹出创建数据表窗口('新建')
def DEF_右键菜单_查看表结构():显示字段信息()###########################
## 创建新窗口 新建数据表 ##(施工中...)
def 键盘任意输入事件_判断表名(event):当前控件 = event.widget现值 = 当前控件.get()if 现值 == '':SV_提示信息_建表.set('请输入表名')else:SV_提示信息_建表.set('')
def 创建数据表窗口_确定(窗口对象):新表名 = 新建数据表名.get()if 新表名 == '':SV_提示信息_建表.set('!!!请输入表名!!!')Label_提示信息_建表['fg'] = 'red'else:自增主键标记 = 0L_字段信息 = []L_主键 = []for 字段编号 in 字典_创建表_字段信息:字段对象列表 = 字典_创建表_字段信息[字段编号]字段名 = 字段对象列表[2].get()字段类型 = 字段对象列表[3].get()是否可空 = 字段对象列表[4].get()默认值 = 字段对象列表[5].get()是否主键 = 字段对象列表[6].get()if 是否主键 == '是(自增数)':自增主键标记 = 1自增主键固定格式 = f'{字段名} INTEGER PRIMARY KEY AUTOINCREMENT'L_主键.append(自增主键固定格式)L_字段信息.append(自增主键固定格式)else:if 是否主键 == '是':L_主键.append(字段名)if 默认值 != '':默认值 = f'default {默认值}'字段信息 = f'{字段名} {字段类型} {是否可空} {默认值}'L_字段信息.append(字段信息)Log.debug(f'新建表 L_字段信息={L_字段信息}')if len(L_主键) == 0 or (len(L_主键) == 1 and 自增主键标记 == 1):SQL_CMD = f"CREATE TABLE {新表名} ({','.join(L_字段信息)});"elif len(L_主键) > 0 and 自增主键标记 == 0:SQL_CMD = f"CREATE TABLE {新表名} ({','.join(L_字段信息)}, primary key ({','.join(L_主键)}));"else:SQL_CMD = ''SV_提示信息_建表.set('!!!(多主键且含自增) 我拼不出来,自己写吧!!!')Log.debug(f'新建表 SQL_CMD={SQL_CMD}')if SQL_CMD != '':R = DEF_SQL_执行(SQL_CMD)if R[0] == 0: # 创建新表成功窗口对象.withdraw() # 关闭编辑窗口DEF_查询数据库表() # 重新查询数据库表else:tkinter.messagebox.showerror(title='ERROR', message=R[1])
def 创建数据表窗口_取消(窗口对象):print("取消")窗口对象.withdraw()
def 创建数据表窗口_增加字段(显示框):print("增加字段")行号列表 = [i for i in 字典_创建表_字段信息]print("行号列表", 行号列表)if 行号列表 == []:新行号 = 0else:最大行号 = max(行号列表)print("最大行号", 最大行号)新行号 = 最大行号 + 1# 字段信息编号和删除字段信息按钮字段编号 = Entry(显示框, width=2)字段编号.insert(0, 新行号)字段编号['state'] = 'readonly'字段编号.grid(row=新行号,column=1,sticky='NW')Button_删除本行字段信息 = Button(显示框, bitmap='error', height=15, width=15, command=lambda STR_字段编号=字段编号.get():DEF_删除字段按钮(STR_字段编号))Button_删除本行字段信息.grid(row=新行号,column=0,sticky='NW')# 创建5个字段属性设置对象Entry_字段名 = Entry(显示框)Combobox_字段类型 = ttk.Combobox(显示框, width=15)Combobox_字段类型['value'] = ('INTEGER', 'FLOAT', 'CHAR(255)', 'VARCHAR(255)', 'TEXT', 'DATE', 'timestamp', 'BLOB')Combobox_是否可空 = ttk.Combobox(显示框, width=10)Combobox_是否可空['value'] = ('NULL', 'NOT NULL')Combobox_是否可空.current(0) # 默认值中的内容为索引,从0开始Combobox_默认值 = ttk.Combobox(显示框)Combobox_默认值['value'] = ('datetime("now","localtime")')Combobox_是否主键 = ttk.Combobox(显示框, width=15)Combobox_是否主键['value'] = ('是(自增数)', '是', '否')Combobox_是否主键.current(2)列表_字段属性对象 = [Button_删除本行字段信息, 字段编号, Entry_字段名, Combobox_字段类型, Combobox_是否可空, Combobox_默认值, Combobox_是否主键]字典_创建表_字段信息[新行号] = 列表_字段属性对象 # 添加到全局字典变量,KEY为行号,VALUE为组成列表依次存放的Entry对象# 给创建的7个控件对象设置位置for 列号 in range(0,7):列表_字段属性对象[列号].grid(row=新行号, column=列号, sticky='NW')
def 复制数据表窗口_填入字段(显示框):print("填入字段")数据表名 = DB_TABLE_NAME.get()SQLite3_CMD_PRAGMA = f'PRAGMA table_info({数据表名})' # SQLite3 获取的表字段信息命令R = DEF_SQL_查询和返回(SQLite3_CMD_PRAGMA)if R[0] == 0:LL_字段信息 = R[1] # 示例 [(0, 'ID', 'INTEGER', 1, None, 1), (1, 'A', 'TEXT', 0, None, 0)]for 编号,字段名,类型,是否空,默认值,主键 in LL_字段信息:# 字段信息编号和删除字段信息按钮字段编号 = Entry(显示框, width=2)字段编号.insert(0, 编号)字段编号['state'] = 'readonly'字段编号.grid(row=编号,column=1,sticky='NW')Button_删除本行字段信息 = Button(显示框, bitmap='error', height=15, width=15, command=lambda STR_字段编号=字段编号.get():DEF_删除字段按钮(STR_字段编号))Button_删除本行字段信息.grid(row=编号,column=0,sticky='NW')# 创建5个字段属性设置对象Entry_字段名 = Entry(显示框)Entry_字段名.insert(0, 字段名)Combobox_字段类型 = ttk.Combobox(显示框, width=15)Combobox_字段类型['value'] = (类型, 'INTEGER', 'FLOAT', 'CHAR(255)', 'VARCHAR(255)', 'TEXT', 'DATE', 'timestamp', 'BLOB')Combobox_字段类型.current(0)Combobox_是否可空 = ttk.Combobox(显示框, width=10)Combobox_是否可空['value'] = ('NULL', 'NOT NULL') # 刚好对应 0 NULL,1 NOT NULLCombobox_是否可空.current(是否空) # 默认值中的内容为索引,从0开始Combobox_默认值 = ttk.Combobox(显示框)Combobox_默认值['value'] = (默认值, 'datetime("now","localtime")')Combobox_默认值.current(0)Combobox_是否主键 = ttk.Combobox(显示框, width=15)Combobox_是否主键['value'] = ('是(自增数)', '是', '否')if 主键>0:Combobox_是否主键.current(1)else:Combobox_是否主键.current(2)列表_字段属性对象 = [Button_删除本行字段信息, 字段编号, Entry_字段名, Combobox_字段类型, Combobox_是否可空, Combobox_默认值, Combobox_是否主键]字典_创建表_字段信息[编号] = 列表_字段属性对象 # 添加到全局字典变量,KEY为行号,VALUE为组成列表依次存放的Entry对象# 给创建的7个控件对象设置位置for 列号 in range(0,7):列表_字段属性对象[列号].grid(row=编号, column=列号, sticky='NW')else:tkinter.messagebox.showerror(title='ERROR', message=R[1])
def DEF_删除字段按钮(STR_字段编号):print("DEF_删除字段按钮")print("STR_字段编号", STR_字段编号, type(STR_字段编号))KEY = int(STR_字段编号)for i in 字典_创建表_字段信息[KEY]:i.grid_forget() # 隐藏del 字典_创建表_字段信息[KEY] # 删除字段信息
def DEF_弹出创建数据表窗口(新建or复制):字典_创建表_字段信息.clear() # 先清空存储新建表信息的字典新窗口 = Toplevel()新窗口.title('创建数据表窗口')显示坐标 = f'+{屏幕宽//2-300}+{屏幕高//2-100}'新窗口.geometry(显示坐标)表名框 = Frame(新窗口)标题框 = Frame(新窗口)数据框 = Frame(新窗口)按钮框1 = Frame(新窗口)按钮框2 = Frame(新窗口)表名框.grid(row=0,column=0,sticky='NW')标题框.grid(row=1,column=0,sticky='NW')数据框.grid(row=2,column=0,sticky='NW')按钮框1.grid(row=3,column=0,sticky='NW')按钮框2.grid(row=4,column=0)## 表名框:用户输入新建的表名Label(表名框, text='[新建数据表名]').grid(row=0,column=0,sticky='NW')Entry_新表名 = Entry(表名框, textvariable=新建数据表名)Entry_新表名.bind('<KeyRelease>', 键盘任意输入事件_判断表名) # 每当输入内容时执行一次函数Entry_新表名.grid(row=0, column=1, sticky='NW')SV_提示信息_建表.set('请输入表名')Label_提示信息_建表 = Label(表名框, textvariable=SV_提示信息_建表)Label_提示信息_建表['fg'] = 'red'Label_提示信息_建表.grid(row=0,column=2,sticky='NW')## 标题框:固定不变的5个Entry,提示每列含义删除位 = Entry(标题框, width=2)删除位.grid(row=0,column=0,sticky='NW') #00删除位.insert(0, '删')序号位 = Entry(标题框, width=2)序号位.grid(row=0,column=1,sticky='NW') #01序号位.insert(0, '序')字段 = Entry(标题框)字段.grid(row=0,column=2,sticky='NW') #02字段.insert(0, '列名(字段名)')字段['state'] = 'readonly'类型 = Entry(标题框, width=18)类型.grid(row=0,column=3,sticky='NW') #03类型.insert(0, '类型')类型['state'] = 'readonly'空 = Entry(标题框, width=12)空.grid(row=0,column=4,sticky='NW') #04空.insert(0, '是否允许空')空['state'] = 'readonly'默认值 = Entry(标题框, width=23)默认值.grid(row=0,column=5,sticky='NW') #05默认值.insert(0, '默认值')默认值['state'] = 'readonly'主键 = Entry(标题框, width=18)主键.grid(row=0,column=6,sticky='NW') #06主键.insert(0, '主键标识')主键['state'] = 'readonly'## 数据框:编辑填入原值,新建填入空白if 新建or复制 == '新建':创建数据表窗口_增加字段(数据框)else:复制数据表窗口_填入字段(数据框)## 按钮框增加按钮 = Button(按钮框1, text='增加字段', command=lambda Frame_控件对象=数据框:创建数据表窗口_增加字段(Frame_控件对象))增加按钮.grid(row=0,column=0,sticky='NW',columnspan=2)确定按钮 = Button(按钮框2, text='确定', command=lambda 窗口对象=新窗口:创建数据表窗口_确定(窗口对象))确定按钮.grid(row=1,column=0)取消按钮 = Button(按钮框2, text='取消', command=lambda 窗口对象=新窗口:创建数据表窗口_取消(窗口对象))取消按钮.grid(row=1,column=1)
## 创建新窗口 新建数据表 ##
############################# 函数:导出CSV文件
def CSV导出一个表(导出文件名, 导出数据库名, 导出数据表名):try:F = open(导出文件名, 'a', newline='') ## newline='' 防止出现每行多一行空行except Exception as e:ERROR = f'导出数据库"{导出数据库名}"中的数据表"{导出数据表名}"失败\n错误信息:{e}\n请检查文件名或写入权限'tkinter.messagebox.showerror(title='ERROR', message=ERROR)else:F_CSV = csv.writer(F)## 查询数据库表提取字段名和数据记录SQL_CMD = f'SELECT * FROM {导出数据表名}'R = DEF_SQL_查询和返回_数据列表_字段列表(SQL_CMD)if R[0] == 0:数据记录 = R[1]字段信息 = R[2]F_CSV.writerow(字段信息)for 记录 in 数据记录:F_CSV.writerow(记录)F.close()INFO = f'导出数据库"{导出数据库名}"中的数据表"{导出数据表名}"成功\n导出文件为"{导出文件名}"'tkinter.messagebox.showinfo(title='INFO', message=INFO)else:F.close()ERROR = R[1]Log.error(ERROR)tkinter.messagebox.showerror(title='ERROR', message=ERROR)
def 数据表导出CSV():print("数据表导出CSV")DB_File = DB_FULL_NAME.get()数据库名 = os.path.basename(DB_File) # 提取文件名数据表名 = DB_TABLE_NAME.get()默认导出文件名 = 数据库名 +'_'+ 数据表名 +' ['+ time.strftime('%Y%m%d') + '].csv'导出文件名 = tkinter.simpledialog.askstring(title='导出文件名', prompt='请输入导出文件名:', initialvalue=默认导出文件名)print(导出文件名) # 确定为输入内容,取消为Noneif 导出文件名 == None:print("取消导出")else:## 检查文件名是否可用if os.path.exists(导出文件名): # 判断 目录、文件 是否存在数据表导出CSV() # 文件名已经被使用,循环操作,直到用户输入不重复的文件名或取消else:CSV导出一个表(导出文件名, 数据库名, 数据表名)
def 数据库导出CSV():print("数据库导出CSV")用户选择结果 = tkinter.messagebox.askyesno(title='数据库导出(csv)', message='导出数据库内全部数据表\n一个CSV只能存储一张表,是否分成多个文件存储') # 返回值为:True或者Falseprint(用户选择结果)if 用户选择结果 == True:DB_File = DB_FULL_NAME.get()数据库名 = os.path.basename(DB_File)数据表列表 = eval(STR_数据表列表内容.get()) # "('表名1', '表名2')" 字符串转成Python数据类型for 数据表名 in 数据表列表:导出文件名 = 数据库名 +'_'+ 数据表名 +' ['+ time.strftime('%Y%m%d') + '].csv'CSV导出一个表(导出文件名, 数据库名, 数据表名)
def DEF_按钮_显编框数据导出为CSV文件():if LabelFrame_显编框.winfo_children() == []: # 当显示编辑框内组件被销毁后ERROR = '无法导出数据:显编框内无数据'tkinter.messagebox.showerror(title='ERROR', message=ERROR)else:默认导出文件名 = '导出部分数据 ['+ time.strftime('%Y%m%d') + '].csv'导出文件名 = tkinter.simpledialog.askstring(title='导出显编框内数据(csv)', prompt='所见即所得\n请输入导出文件名:', initialvalue=默认导出文件名)print(导出文件名) # 确定为输入内容,取消为Noneif 导出文件名 == None:print("取消导出")else:## 检查文件名是否可用if os.path.exists(导出文件名): # 判断 目录、文件 是否存在DEF_按钮_显编框数据导出为CSV文件() # 文件名已经被使用,循环操作,直到用户输入不重复的文件名或取消else:try:F = open(导出文件名, 'a', newline='') ## newline='' 防止出现每行多一行空行except Exception as e:ERROR = f'导出数据"{导出文件名}"失败\n错误信息:{e}\n请检查文件名或写入权限'tkinter.messagebox.showerror(title='ERROR', message=ERROR)else:F_CSV = csv.writer(F)## 使用显示编辑框内实时数据,用户可以修改而不改动数据库,直接导出数据,所见即所得列表_字段信息 = []for K in 字典_查询字段_坐标_对象:列表_字段信息.append(字典_查询字段_坐标_对象[K].get())F_CSV.writerow(列表_字段信息)列数 = len(列表_字段信息)N = 0列表_数据信息 = []for K in 字典_查询结果_坐标_对象:N += 1列表_数据信息.append(字典_查询结果_坐标_对象[K].get())if N%列数==0:F_CSV.writerow(列表_数据信息)列表_数据信息 = []N = 0F.close()INFO = f'导出数据"{导出文件名}"成功'tkinter.messagebox.showinfo(title='INFO', message=INFO)
def DEF_按钮_显编框数据导出为Excel文件():if LabelFrame_显编框.winfo_children() == []: # 当显示编辑框内组件被销毁后ERROR = '无法导出数据:显编框内无数据'tkinter.messagebox.showerror(title='ERROR', message=ERROR)else:默认导出文件名 = '导出部分数据 ['+ time.strftime('%Y%m%d') + '].xlsx'导出文件名 = tkinter.simpledialog.askstring(title='导出显编框内数据(xlsx)', prompt='所见即所得\n请输入导出文件名:', initialvalue=默认导出文件名)# 确定为输入内容,取消为Noneif 导出文件名 == None:print("取消导出")else:## 检查文件名是否可用if os.path.exists(导出文件名): # 判断 目录、文件 是否存在DEF_按钮_显编框数据导出为Excel文件() # 文件名已经被使用,循环操作,直到用户输入不重复的文件名或取消else:try:from openpyxl import Workbookexcept Exception as e:ERROR = f'导出数据"{导出文件名}"失败\n错误信息:{e}\n请检查写入权限或openpyxl模块的安装和加载'tkinter.messagebox.showerror(title='ERROR', message=ERROR)else:EXCEL文件 = Workbook()工作表 = EXCEL文件.active # 获得激活的worksheet,默认有一张名为Sheet的工作表## 使用显示编辑框内实时数据,用户可以修改而不改动数据库,直接导出数据,所见即所得列表_字段信息 = []for K in 字典_查询字段_坐标_对象:列表_字段信息.append(字典_查询字段_坐标_对象[K].get())工作表.append(列表_字段信息)列数 = len(列表_字段信息)N = 0列表_数据信息 = []for K in 字典_查询结果_坐标_对象:N += 1列表_数据信息.append(字典_查询结果_坐标_对象[K].get())if N%列数==0:工作表.append(列表_数据信息)列表_数据信息 = []N = 0EXCEL文件.save(导出文件名)INFO = f'导出数据"{导出文件名}"成功'tkinter.messagebox.showinfo(title='INFO', message=INFO)
def 数据表导出EXCEL():DB_File = DB_FULL_NAME.get()数据库名 = os.path.basename(DB_File) # 提取文件名数据表名 = DB_TABLE_NAME.get()默认导出文件名 = 数据库名 +'_'+ 数据表名 +' ['+ time.strftime('%Y%m%d_%H%M%S') + '].xlsx'导出文件名 = tkinter.simpledialog.askstring(title='导出数据表(xlsx)', prompt='导出数据表\n最多导出1048575行数据\n请输入导出文件名:', initialvalue=默认导出文件名)## 确定为输入内容,取消为Noneif 导出文件名 == None:Log.debug(f'数据表导出EXCEL 数据表名={数据表名} 取消导出')else:## 检查文件名是否可用if os.path.exists(导出文件名): # 判断 目录、文件 是否存在数据表导出EXCEL() # 文件名已经被使用,循环操作,直到用户输入不重复的文件名或取消else:try:from openpyxl import Workbookexcept Exception as e:ERROR = f'导出数据库"{数据库名}"中的数据表"{数据表名}"失败\n错误信息:{e}\n请检查写入权限或openpyxl模块的安装和加载'tkinter.messagebox.showerror(title='ERROR', message=ERROR)Log.debug(f'数据表导出EXCEL 数据表名={数据表名} 导出失败 {e}')else:## 查询数据库表提取字段名和数据记录SQL_CMD = f'SELECT * FROM {数据表名}'R = DEF_SQL_查询和返回_数据列表_字段列表(SQL_CMD)if R[0] == 0:数据记录 = R[1]字段信息 = R[2]EXCEL文件 = Workbook()工作表 = EXCEL文件.active # 获得激活的worksheet,默认有一张名为Sheet的工作表工作表.title = 数据表名 # 重命名当前工作表工作表.append(字段信息) # 字段行放第一行作为标题for i in 数据记录:工作表.append(i) # 写入数据行EXCEL文件.save(导出文件名) # 保存INFO = f'导出数据库"{数据库名}"中的数据表"{数据表名}"成功\n导出文件为"{导出文件名}"'tkinter.messagebox.showinfo(title='INFO', message=INFO)Log.debug(f'数据表导出EXCEL 数据表名={数据表名} 导出成功 {导出文件名}')else:ERROR = R[1]tkinter.messagebox.showerror(title='ERROR', message=ERROR)Log.debug(f'数据表导出EXCEL 数据表名={数据表名} 导出失败 {R[1]}')
def 数据库导出EXCEL():print("数据库导出EXCEL")DB_File = DB_FULL_NAME.get()数据库名 = os.path.basename(DB_File) # 提取文件名数据表名 = DB_TABLE_NAME.get()默认导出文件名 = 数据库名 +' ['+ time.strftime('%Y%m%d') + '].xlsx'导出文件名 = tkinter.simpledialog.askstring(title='导出数据库内全部数据表(xlsx)', prompt='导出数据库内全部表\n最多导出1048575行数据\n请输入导出文件名:', initialvalue=默认导出文件名)## 确定为输入内容,取消为Noneif 导出文件名 == None:print("取消导出")else:## 检查文件名是否可用if os.path.exists(导出文件名): # 判断 目录、文件 是否存在数据库导出EXCEL() # 文件名已经被使用,循环操作,直到用户输入不重复的文件名或取消else:try:from openpyxl import Workbookexcept Exception as e:ERROR = f'导出数据库"{数据库名}"中的数据表"{数据表名}"失败\n错误信息:{e}\n请检查写入权限或openpyxl模块的安装和加载'tkinter.messagebox.showerror(title='ERROR', message=ERROR)else:数据表列表 = eval(STR_数据表列表内容.get()) # "('表名1', '表名2')" 字符串转成Python数据类型EXCEL文件 = Workbook() # 创建Excel文件for 数据表名 in 数据表列表:## 查询数据库表提取字段名和数据记录SQL_CMD = f'SELECT * FROM {数据表名}'R = DEF_SQL_查询和返回_数据列表_字段列表(SQL_CMD)if R[0] == 0:数据记录 = R[1]字段信息 = R[2]工作表 = EXCEL文件.create_sheet(数据表名, 0) # 创建工作表并插入到最前的位置工作表.append(字段信息) # 字段行放第一行作为标题for i in 数据记录:工作表.append(i) # 写入数据行INFO = f'导出数据库"{数据库名}"中的数据表"{数据表名}"成功\n导出文件为"{导出文件名}"'tkinter.messagebox.showinfo(title='INFO', message=INFO)else:ERROR = R[1]tkinter.messagebox.showerror(title='ERROR', message=ERROR)EXCEL文件.save(导出文件名) # 保存## 创建数据表列表框-表名处右键菜单
数据库表_右键菜单_表名处 = Menu()
数据库表_右键菜单_表名处.add_command(label='打开表', command=DEF_右键菜单_打开表) ## 菜单按钮函数:打开数据库表
数据库表_右键菜单_表名处.add_command(label='删除表', command=DEF_右键菜单_删除表) ## 菜单按钮函数:删除数据库表
数据库表_右键菜单_表名处.add_command(label='新建表', command=DEF_右键菜单_新建表) ## 菜单按钮函数:新建数据库表
数据库表_右键菜单_表名处.add_command(label='复制表', command=DEF_右键菜单_复制表) ## 菜单按钮函数:复制数据库表
数据库表_右键菜单_表名处.add_command(label='查看表结构', command=DEF_右键菜单_查看表结构) ## 菜单按钮函数:查看表结构
数据库表_右键菜单_表名处.add_separator()
数据库表_右键菜单_表名处.add_command(label='编辑表', command=DEF_右键菜单_编辑表) ## 菜单按钮函数:编辑数据库表
数据库表_右键菜单_表名处.add_separator()
数据库表_右键菜单_表名处.add_command(label='导出此表(CSV)', command=数据表导出CSV) ## 菜单按钮函数:导出选中的数据库表为CSV格式文件
数据库表_右键菜单_表名处.add_command(label='导出全库(CSV)', command=数据库导出CSV) ## 菜单按钮函数:导出数据库中每个表为CSV格式文件
数据库表_右键菜单_表名处.add_command(label='导出此表(Excel)最大1048575行记录', command=数据表导出EXCEL) ## 菜单按钮函数:导出选中的数据库表为EXCEL格式文件
数据库表_右键菜单_表名处.add_command(label='导出全库(Excel)每个表最大1048575行记录', command=数据库导出EXCEL) ## 菜单按钮函数:导出数据库中每个表为一个工作表的Excel格式文件## 创建数据表列表框-空白处右键菜单
数据库表_右键菜单_空白处 = Menu()
数据库表_右键菜单_空白处.add_command(label='空白处右键菜单')#####################
## 右键菜单 字段框 ##
####################### 事件函数:右键菜单
def DEF_弹出_字段框_右键菜单(event):选中控件 = event.widget行 = 0 # 字段名只有1行,恒等于0列 = 选中控件.grid_info()['column']字段框_定位列.set(列)## 弹出菜单字段框_右键菜单.post(event.x_root, event.y_root) # 光标位置显示菜单
def DEL_COL():print("删除列(删除字段)")INFO = 'SQLite3 没有这个功能,请使用新建表替换旧表'tkinter.messagebox.showinfo(title='提示', message=INFO)
def EDIT_COL():print("编辑列(修改字段)")INFO = 'SQLite3 没有这个功能,请使用新建表替换旧表'tkinter.messagebox.showinfo(title='提示', message=INFO)
def DEF_右键菜单_列值精确匹配():数据表名 = DB_TABLE_NAME.get()字段列号 = 字段框_定位列.get()字段名 = 字典_查询字段_坐标_初值[(0,字段列号)]SQL = f'SELECT * FROM {数据表名} WHERE {字段名} IS "匹配内容"'文本框_命令行.insert(0.0, SQL+'\n')
def DEF_右键菜单_列值模糊匹配():数据表名 = DB_TABLE_NAME.get()字段列号 = 字段框_定位列.get()字段名 = 字典_查询字段_坐标_初值[(0,字段列号)]SQL = f'SELECT * FROM {数据表名} WHERE {字段名} LIKE "%匹配内容%"'文本框_命令行.insert(0.0, SQL+'\n')
def DEF_右键菜单_列值替换():数据表名 = DB_TABLE_NAME.get()字段列号 = 字段框_定位列.get()字段名 = 字典_查询字段_坐标_初值[(0,字段列号)]SQL = f'UPDATE {数据表名} SET {字段名}="新值" WHERE {字段名}="原值";'文本框_命令行.insert(0.0, SQL+'\n')
def DEF_右键菜单_统计本列出现次数():数据表名 = DB_TABLE_NAME.get()字段列号 = 字段框_定位列.get()字段名 = 字典_查询字段_坐标_初值[(0,字段列号)]SQL = f'SELECT {字段名},COUNT({字段名}) AS 出现次数 FROM {数据表名} GROUP BY {字段名} ORDER BY 出现次数 DESC'DEF_SQL_查询和显示(SQL)
def 显示字段信息():数据表名 = DB_TABLE_NAME.get()SQL_CMD_PRAGMA = f'PRAGMA table_info ({数据表名})'DEF_SQL_查询和显示(SQL_CMD_PRAGMA)#########################################
## 右键菜单 字段框 新加字段 创建新窗口 ##
def 新加字段窗口_确定(窗口对象):print("新加字段窗口_确定")表名 = DB_TABLE_NAME.get()if 表名 == '':print("没有表名")else:## 字段名不能为空,不能重复错误标记 = 0测试字段名列表 = []for K in 字典_新加字段信息:测试字段名 = 字典_新加字段信息[K][2].get().strip()if 测试字段名 == '':错误标记 = 1print("含有空字段名")breakelse:测试字段名列表.append(测试字段名)if len(测试字段名列表) != len(set(测试字段名列表)):错误标记 = 1print("有重复字段名")if 错误标记 == 0:L_SQL_新增字段 = []for 字段编号 in 字典_新加字段信息:字段对象列表 = 字典_新加字段信息[字段编号]字段名 = 字段对象列表[2].get()字段类型 = 字段对象列表[3].get()是否可空 = 字段对象列表[4].get()默认值 = 字段对象列表[5].get()是否主键 = 字段对象列表[6].get()if 是否主键 == '是(自增数)':主键标识 = 'PRIMARY KEY AUTOINCREMENT'elif 是否主键 == '是':主键标识 = 'PRIMARY KEY'else:主键标识 = ''if 默认值 != '':默认值 = f'default {默认值}'else:默认值 == ''SQL_新增字段 = f'ALTER TABLE {表名} ADD {字段名} {字段类型} {是否可空} {默认值} {主键标识}'L_SQL_新增字段.append(SQL_新增字段)R = DEF_SQL_执行多条_忽略错误语句(L_SQL_新增字段)if R[0] == 0: # 创建新字段全部成功窗口对象.withdraw() # 关闭编辑窗口## 成功后,更新显示表格执行上条查询语句_刷新显示_从头开始()else:ERROR = '执行信息\n'for i in R[1]:ERROR += i+'\n'tkinter.messagebox.showerror(title='ERROR', message=ERROR)
def 新加字段窗口_取消(窗口对象):print("取消")窗口对象.withdraw()
def 新加字段窗口_增加字段(显示框):print("增加字段")行号列表 = [i for i in 字典_新加字段信息]print("行号列表", 行号列表)if 行号列表 == []:新行号 = 0else:最大行号 = max(行号列表)print("最大行号", 最大行号)新行号 = 最大行号 + 1# 字段信息编号和删除字段信息按钮字段编号 = Entry(显示框, width=2)字段编号.insert(0, 新行号)字段编号['state'] = 'readonly'字段编号.grid(row=新行号,column=1,sticky='NW')Button_删除本行字段信息 = Button(显示框, bitmap='error', height=15, width=15, command=lambda STR_字段编号=字段编号.get():DEF_删除新加字段按钮(STR_字段编号))Button_删除本行字段信息.grid(row=新行号,column=0,sticky='NW')# 创建5个字段属性设置对象Entry_字段名 = Entry(显示框)Combobox_字段类型 = ttk.Combobox(显示框, width=15)Combobox_字段类型['value'] = ('INTEGER', 'FLOAT', 'CHAR(255)', 'VARCHAR(255)', 'TEXT', 'DATE', 'BLOB')Combobox_是否可空 = ttk.Combobox(显示框, width=10)Combobox_是否可空['value'] = ('NULL', 'NOT NULL')Combobox_是否可空.current(0) # 默认值中的内容为索引,从0开始Combobox_默认值 = ttk.Combobox(显示框)Combobox_默认值['value'] = ('datetime("now","localtime")')Combobox_是否主键 = ttk.Combobox(显示框, width=15)Combobox_是否主键['value'] = ('是(自增数)', '是', '否')Combobox_是否主键.current(2)列表_字段属性对象 = [Button_删除本行字段信息, 字段编号, Entry_字段名, Combobox_字段类型, Combobox_是否可空, Combobox_默认值, Combobox_是否主键]字典_新加字段信息[新行号] = 列表_字段属性对象 # 添加到全局字典变量,KEY为行号,VALUE为组成列表依次存放的Entry对象# 给创建的7个控件对象设置位置for 列号 in range(0,7):列表_字段属性对象[列号].grid(row=新行号, column=列号, sticky='NW')
def DEF_删除新加字段按钮(STR_字段编号):print("DEF_删除新加字段按钮")print("STR_字段编号", STR_字段编号, type(STR_字段编号))KEY = int(STR_字段编号)for i in 字典_新加字段信息[KEY]:i.grid_forget() # 隐藏del 字典_新加字段信息[KEY] # 删除字段信息
def DEF_弹出新加字段窗口():字典_新加字段信息.clear() # 先清空存储新建表信息的字典新窗口 = Toplevel()新窗口.title('新增字段')显示坐标 = f'+{屏幕宽//2-300}+{屏幕高//2-100}'新窗口.geometry(显示坐标)表名框 = Frame(新窗口)标题框 = Frame(新窗口)数据框 = Frame(新窗口)按钮框1 = Frame(新窗口)按钮框2 = Frame(新窗口)表名框.grid(row=0,column=0,sticky='NW')标题框.grid(row=1,column=0,sticky='NW')数据框.grid(row=2,column=0,sticky='NW')按钮框1.grid(row=3,column=0,sticky='NW')按钮框2.grid(row=4,column=0)Label(表名框, text='[数据表名]').grid(row=0,column=0,sticky='NW')Label(表名框, text=DB_TABLE_NAME.get()).grid(row=0, column=1, sticky='NW')## 标题框:固定不变的7个Entry,提示每列含义删除位 = Entry(标题框, width=2)删除位.grid(row=0,column=0,sticky='NW') #00删除位.insert(0, '删')序号位 = Entry(标题框, width=2)序号位.grid(row=0,column=1,sticky='NW') #01序号位.insert(0, '序')字段 = Entry(标题框)字段.grid(row=0,column=2,sticky='NW') #02字段.insert(0, '列名(字段名)')字段['state'] = 'readonly'类型 = Entry(标题框, width=18)类型.grid(row=0,column=3,sticky='NW') #03类型.insert(0, '类型')类型['state'] = 'readonly'空 = Entry(标题框, width=12)空.grid(row=0,column=4,sticky='NW') #04空.insert(0, '是否允许空')空['state'] = 'readonly'默认值 = Entry(标题框, width=23)默认值.grid(row=0,column=5,sticky='NW') #05默认值.insert(0, '默认值')默认值['state'] = 'readonly'主键 = Entry(标题框, width=18)主键.grid(row=0,column=6,sticky='NW') #06主键.insert(0, '主键标识')主键['state'] = 'readonly'## 数据框:编辑填入原值,新建填入空白新加字段窗口_增加字段(数据框)## 按钮框确定按钮 = Button(按钮框2, text='确定', command=lambda 窗口对象=新窗口:新加字段窗口_确定(窗口对象))确定按钮.grid(row=1,column=0)取消按钮 = Button(按钮框2, text='取消', command=lambda 窗口对象=新窗口:新加字段窗口_取消(窗口对象))取消按钮.grid(row=1,column=1)增加按钮 = Button(按钮框1, text='增加字段', command=lambda Frame_控件对象=数据框:新加字段窗口_增加字段(Frame_控件对象))增加按钮.grid(row=1,column=2,sticky='NW')
## 右键菜单 字段框 新加字段 创建新窗口 ##
########################################### 创建字段框右键菜单
字段框_右键菜单 = Menu()
字段框_右键菜单.add_separator()
字段框_右键菜单.add_command(label='添加数据记录', command=DEF_新增记录) ## 菜单按钮函数:新增行(新增记录)
字段框_右键菜单.add_separator()
字段框_右键菜单.add_command(label='显示字段信息', command=显示字段信息) ## 菜单按钮函数:查询数据表全部字段属性并显示
字段框_右键菜单.add_command(label='统计本列出现次数', command=DEF_右键菜单_统计本列出现次数) ## 菜单按钮函数:统计本列出现次数
字段框_右键菜单.add_separator()
字段框_右键菜单.add_command(label='sql: 列值替换', command=DEF_右键菜单_列值替换) ## 生成替换字段内容的SQL语句
字段框_右键菜单.add_command(label='sql: 列值精确匹配 IS', command=DEF_右键菜单_列值精确匹配) ## 生成精确匹配此列的SQL语句
字段框_右键菜单.add_command(label='sql: 列值模糊匹配 LIKE', command=DEF_右键菜单_列值模糊匹配) ## 生成模糊匹配此列的SQL语句
字段框_右键菜单.add_separator()
字段框_右键菜单.add_command(label='本页数据导出(CSV)', command=DEF_按钮_显编框数据导出为CSV文件)
字段框_右键菜单.add_command(label='本页数据导出(Excel)', command=DEF_按钮_显编框数据导出为Excel文件)
字段框_右键菜单.add_separator()
字段框_右键菜单.add_command(label='添加列(添加字段)', command=DEF_弹出新加字段窗口) ## 菜单按钮函数:添加列(添加字段)
字段框_右键菜单.add_command(label='删除列(删除字段)', command=DEL_COL) ## 菜单按钮函数:删除列(删除字段)
字段框_右键菜单.add_command(label='编辑列(修改字段)', command=EDIT_COL) ## 菜单按钮函数:编辑列(修改字段)
字段框_右键菜单.add_separator()#####################
## 右键菜单 数据框 ##
################################################
## 创建新窗口 大文本编辑 ##
def 大文本窗口_确定(窗口对象):print("大文本编辑_确认")## 获取源控件定位行 = 数据框_定位行.get()列 = 数据框_定位列.get()## 提取源控件原值原值 = 字典_查询结果_坐标_初值[(行,列)]print("原值", 原值)## 提取编辑后的新值新值 = 字典_对象存储['文本编辑对象'].get(0.0, END).rstrip('\n') # insert 时候会多个换行,麻烦,直接删除现值 = 字典_查询结果_坐标_对象[(行,列)].get() # Entry控件是可以输入的,此处用于处理从其他值改回原值的情况print("用户编辑后新值", 新值)if 新值 != 原值:print("有变化")字典_查询结果_坐标_对象[(行,列)].delete(0, END) # 删除原内容字典_查询结果_坐标_对象[(行,列)].insert(0, 新值) # 写入新内容## 改变颜色,有变化用绿色字典_查询结果_坐标_对象[(行,列)]['bg'] = '#7FFF00'## 显示修改数据库的按钮按钮_确认修改数据库.grid()else:if 现值 != 原值:print("无变化,改回原值")字典_查询结果_坐标_对象[(行,列)].delete(0, END) # 删除原内容字典_查询结果_坐标_对象[(行,列)].insert(0, 原值) # 改回原值else:print("无变化,没有改动")## 改变颜色,无变化还原白色字典_查询结果_坐标_对象[(行,列)]['bg'] = '#FFFFFF'窗口对象.withdraw() # 关闭编辑窗口
def 大文本窗口_取消(窗口对象):print("取消")窗口对象.withdraw()
def DEF_弹出大文本窗口():#编辑时禁止使用分页按钮按钮_显编框下一页['state'] = 'disabled' # 禁止下一页按钮行 = 数据框_定位行.get()列 = 数据框_定位列.get()单元格 = 字典_查询结果_坐标_对象[(行,列)]单元格现值 = 单元格.get()单元格原值 = 字典_查询结果_坐标_初值[(行,列)]新窗口 = Toplevel()新窗口.title('大段文本显示/编辑窗口')显示坐标 = f'+{屏幕宽//2-300}+{屏幕高//2-100}'新窗口.geometry(显示坐标)小文本框 = Frame(新窗口)大文本框 = Frame(新窗口)按钮框 = Frame(新窗口)小文本框.grid(row=0,column=0,sticky='NW')大文本框.grid(row=1,column=0,sticky='NW')按钮框.grid(row=2,column=0)## 小文本框Label(小文本框, text='[现值]').grid(row=0,column=0,sticky='W')Entry_原值 = Entry(小文本框, width=80)Entry_原值.grid(row=0,column=1,sticky='W')Entry_原值.insert(0, 单元格现值)Label(小文本框, text='[原值]').grid(row=1,column=0,sticky='W')Entry_原值 = Entry(小文本框, width=80)Entry_原值.grid(row=1,column=1,sticky='W')Entry_原值.insert(0, 单元格原值)## 大文本框Text_大文本 = Text(大文本框, height=20, width=100, wrap='none') # 不使用自动换行显示字典_对象存储['文本编辑对象'] = Text_大文本Text_大文本.insert(0.0, 单元格现值)Text_大文本.focus_set() # 焦点移到编辑子框Scrollbar_编辑子框_横 = Scrollbar(大文本框, command=Text_大文本.xview, orient=HORIZONTAL)Scrollbar_编辑子框_竖 = Scrollbar(大文本框, command=Text_大文本.yview)Text_大文本.config(xscrollcommand=Scrollbar_编辑子框_横.set, yscrollcommand=Scrollbar_编辑子框_竖.set) # 自动设置滚动条滑动幅度Text_大文本.grid(row=0,column=0)Scrollbar_编辑子框_竖.grid(row=0, column=1, sticky=S+W+E+N)Scrollbar_编辑子框_横.grid(row=1, column=0, sticky=S+W+E+N)## 按钮框确定按钮 = Button(按钮框, text='确定', command=lambda 窗口对象=新窗口:大文本窗口_确定(窗口对象))确定按钮.grid(row=1,column=0)取消按钮 = Button(按钮框, text='取消', command=lambda 窗口对象=新窗口:大文本窗口_取消(窗口对象))取消按钮.grid(row=1,column=1)
## 创建新窗口 大文本编辑 ##
############################# 事件函数:键盘任意输入事件
def 键盘任意输入事件(event):当前控件 = event.widget控件行号 = 当前控件.grid_info()['row']控件列号 = 当前控件.grid_info()['column']## 判断内容是否有变动新值 = 字典_查询结果_坐标_对象[(控件行号,控件列号)].get()旧值 = 字典_查询结果_坐标_初值[(控件行号,控件列号)]if 新值 == 旧值:Log.debug(f"触发控件键盘事件 {(控件行号,控件列号)}:无变化 旧值={旧值}")当前控件['bg'] = '#FFFFFF' # 无变化还原白底按钮_显编框下一页['state'] = 'normal' # 解禁下一页按钮else:Log.debug(f"触发控件键盘事件 {(控件行号,控件列号)}:有变化 旧值={旧值} -> 新值={新值}")当前控件['bg'] = '#7FFF00' # 有变化改成草绿按钮_确认修改数据库.grid() # 显示修改数据库的按钮按钮_显编框下一页['state'] = 'disabled' # 禁止下一页按钮## 事件函数:离开控件
def DEF_离开控件(event):离开控件 = event.widget离开行 = 离开控件.grid_info()['row']离开列 = 离开控件.grid_info()['column']## 判断内容是否有变动if 数据框_定位行.get() == 离开行 and 数据框_定位列.get() == 离开列: # 判断是触发离开事件的控件新值 = 字典_查询结果_坐标_对象[(离开行,离开列)].get()旧值 = 字典_查询结果_坐标_初值[(离开行,离开列)]if 新值 == 旧值:Log.debug(f"触发控件离开事件 {(离开行,离开列)}:无变化 旧值={旧值}")离开控件['bg'] = '#FFFFFF' # 无变化还原白底按钮_显编框下一页['state'] = 'normal' # 解禁下一页按钮else:Log.debug(f"触发控件离开事件 {(离开行,离开列)}:有变化 旧值={旧值} -> 新值={新值}")离开控件['bg'] = '#7FFF00' # 有变化改成草绿按钮_确认修改数据库.grid() # 显示修改数据库的按钮按钮_显编框下一页['state'] = 'disabled' # 禁止下一页按钮else:Log.debug(f"触发控件离开事件 {(离开行,离开列)}:从其他控件离开(忽略)")## 事件函数:左键单击
def 左键单击(event):选中控件 = event.widget行 = 选中控件.grid_info()['row']列 = 选中控件.grid_info()['column']#选中控件['bg'] = '#7FFF00'# 赋值数据框_定位行.set(行)数据框_定位列.set(列)字典_查询结果_坐标_对象[(行,列)].bind('<Leave>', DEF_离开控件) # 单击是进入编辑,给这个控件加个离开事件## 事件函数:右键菜单
def DEF_弹出_数据框_右键菜单(event):# 取值选中控件 = event.widget行 = 选中控件.grid_info()['row']列 = 选中控件.grid_info()['column']# 赋值数据框_定位行.set(行)数据框_定位列.set(列)## 右键选择的控件获得焦点单元格 = 字典_查询结果_坐标_对象[(行,列)]单元格.focus_set() # 焦点移到单元格## 弹出菜单光标X轴 = event.x_root光标Y轴 = event.y_rootIV_光标X轴.set(光标X轴) # (当前就是看看,没有实际用途)IV_光标Y轴.set(光标Y轴) # (当前就是看看,没有实际用途)数据框_右键菜单.post(光标X轴, 光标Y轴) # 光标位置显示菜单def DEF_删除记录():控件行号 = 数据框_定位行.get()数据表名 = DB_TABLE_NAME.get()R = 查数据表主键信息(数据表名)if R[0] == 0:L_主键信息 = R[1] # [(列号,主键名), (列号,主键名)]L_修改限定部分 = [f'{主键名} IS NULL' if 字典_查询结果_坐标_初值[(控件行号,列号)]==SV_自定义空值字符串表示.get() else f'{主键名}="{字典_查询结果_坐标_初值[(控件行号,列号)]}"' for 列号,主键名 in L_主键信息]SQL_WHERE_修改限定部分语句 = 'WHERE ' + ' AND '.join(L_修改限定部分)SQL_CMD_DELETE = f'DELETE FROM {数据表名} {SQL_WHERE_修改限定部分语句}'RR = DEF_SQL_执行(SQL_CMD_DELETE)if RR[0] == 0:## 操作成功后更新一下显示/编辑框执行上条查询语句_刷新显示_从指定行开始()## 删除成功后的行列号和当前行列号有差别,立刻设置为无效行列号,防止后面误删数据框_定位行.set(-1)数据框_定位列.set(-1)else:tkinter.messagebox.showerror(title='ERROR', message=RR[1])else:tkinter.messagebox.showerror(title='ERROR', message=R[1])
def DEF_按钮_还原旧值():行号 = 数据框_定位行.get()列号 = 数据框_定位列.get()单元格_初值 = 字典_查询结果_坐标_初值[(行号,列号)]单元格 = 字典_查询结果_坐标_对象[(行号,列号)]单元格_现值 = 单元格.get()if 单元格_现值 != 单元格_初值:单元格.delete(0, END)单元格.insert(0, 单元格_初值)单元格['bg'] = '#FFFFFF' # 无变化还原白底Log.debug(f"DEF_按钮_还原旧值 (单元格_现值){单元格_现值} 还原 {单元格_初值}(单元格_初值)")else:Log.debug(f"DEF_按钮_还原旧值 (单元格_现值){单元格_现值} 相同 {单元格_初值}(单元格_初值) 忽略操作")## 创建数据框右键菜单
数据框_右键菜单 = Menu()
数据框_右键菜单.add_command(label='添加新行', command=DEF_新增记录) ## 菜单按钮函数:添加新行
数据框_右键菜单.add_command(label='删除整行', command=DEF_删除记录) ## 菜单按钮函数:删除整行(提取控件行号信息)
数据框_右键菜单.add_separator() # 分隔线
数据框_右键菜单.add_command(label='大文本编辑', command=DEF_弹出大文本窗口)
数据框_右键菜单.add_separator()
数据框_右键菜单.add_command(label='还原旧值', command=DEF_按钮_还原旧值)################################
## TKinter 主窗口布局 - TOP框 ##
################################
定位框00 = Frame(top) # 0-0 存放 数据库文件操作框
日志框 = LabelFrame(top, text='数据库操作记录(倒序)') # 0-1 数据库操作记录
定位框20 = Frame(top) # 2-0 数据库表显示编辑框 + 常用功能框
LabelFrame_显编框 = LabelFrame(top, text='数据: 显示/编辑框', bg='#FFD700')
定位框30 = Frame(top) # 3-0 数据: 显示/编辑框
命令框 = LabelFrame(top, text='SQL语句/SQL脚本') # 4-0 SQL语句/SQL脚本
定位框50 = Frame(top) # 5-0 全局变量debug定位框00.grid(row=0,column=0,sticky='NW')
日志框.grid(row=1,column=0,sticky='NSEW') # 填满
日志框.grid_columnconfigure(0, weight=1) # 横向自动填充
定位框20.grid(row=3,column=0,sticky='NW')
LabelFrame_显编框.grid(row=4,column=0,sticky='NW')
LabelFrame_显编框.grid_columnconfigure(0, weight=1) # 横向自动填充
定位框30.grid(row=5,column=0,sticky='NW')
命令框.grid(row=6,column=0,sticky='NSEW')
命令框.grid_columnconfigure(0, weight=1) # 横向自动填充
定位框50.grid(row=7,column=0,sticky='NW')#########################################
## TKinter 主窗口布局 - TOP框 - 日志框 ##
#########################################
TEXT_数据库操作日志 = Text(日志框, height=3, wrap='none') # 显示改动了数据库的操作日志
Scrollbar_日志框_竖 = Scrollbar(日志框)
Scrollbar_日志框_竖['command'] = TEXT_数据库操作日志.yview
Scrollbar_日志框_横 = Scrollbar(日志框)
Scrollbar_日志框_横['command'] = TEXT_数据库操作日志.xview
Scrollbar_日志框_横['orient'] = HORIZONTAL
Scrollbar_日志框_竖.grid(row=0, column=1, sticky=S+W+E+N)
Scrollbar_日志框_横.grid(row=1, column=0, sticky=S+W+E+N)
TEXT_数据库操作日志.config(xscrollcommand=Scrollbar_日志框_横.set, yscrollcommand=Scrollbar_日志框_竖.set) # 自动设置滚动条滑动幅度
TEXT_数据库操作日志.grid(row=0, column=0, sticky='NSEW')
TEXT_数据库操作日志.tag_config('tag_i', foreground='green') # 自定义文本格式 绿字
TEXT_数据库操作日志.tag_config('tag_w', foreground='blue') # 自定义文本格式 蓝字
TEXT_数据库操作日志.tag_config('tag_e', backgroun='yellow', foreground='red') # 自定义文本格式 黄底红字###########################################
## TKinter 主窗口布局 - TOP框 - 定位框00 ##
###########################################
数据库文件操作框 = LabelFrame(定位框00, text='数据库文件操作框')
数据库文件操作框.grid(row=0,column=0,sticky='NW') #0-0##############################################################
## TKinter 主窗口布局 - TOP框 - 定位框00 - 数据库文件操作框 ##
##############################################################
Button(数据库文件操作框, text='选择数据库文件', command=DEF_按钮_选择数据库文件).grid(row=0,column=0,sticky='NW') #00
数据库路径 = Entry(数据库文件操作框, textvariable=DB_FULL_NAME, width=80).grid(row=0,column=1,sticky='NW') #01
按钮_打开数据库 = Button(数据库文件操作框, text='打开/新建数据库', command=DEF_按钮_打开数据库)
按钮_打开数据库.grid(row=0,column=2,sticky='E') #02
Button(数据库文件操作框, text='关闭数据库', command=DEF_按钮_关闭数据库).grid(row=0,column=3,sticky='E') #03###########################################
## TKinter 主窗口布局 - TOP框 - 定位框20 ##
###########################################
Frame_数据表列表显示框 = LabelFrame(定位框20, text='数据表: 显示/编辑框')
##Frame_数据表列表显示框.grid(row=1,column=0,sticky='NW',rowspan=2) # 由 DEF_按钮_打开数据库 控制显示####################################################################
## TKinter 主窗口布局 - TOP框 - 定位框20 - Frame_数据表列表显示框 ##
####################################################################
STR_数据表列表内容 = StringVar() # 实时更新变量## Listbox 列表控件
Listbox_数据表列表 = Listbox(Frame_数据表列表显示框, listvariable=STR_数据表列表内容, height=3, width=45) # height 行数(默认10行)## Scrollbar 滚动条控件
Scrollbar_数据表列表_横 = Scrollbar(Frame_数据表列表显示框, orient=HORIZONTAL, command=Listbox_数据表列表.xview) # HORIZONTAL 横向
Scrollbar_数据表列表_竖 = Scrollbar(Frame_数据表列表显示框, orient=VERTICAL, command=Listbox_数据表列表.yview) # VERTICAL 纵向(默认就是)## 列表控件 绑定事件、设置滚动条
Listbox_数据表列表.config(xscrollcommand=Scrollbar_数据表列表_横.set)
Listbox_数据表列表.config(yscrollcommand=Scrollbar_数据表列表_竖.set)
Listbox_数据表列表.bind('<Double-Button-1>', DEF_双击表名) # 绑定双击命令
Listbox_数据表列表.bind('<Button-3>', 弹出_数据库表_右键菜单) # 绑定右键菜单事件## 控件布局
Listbox_数据表列表.grid(row=0,column=0,sticky='NW') #00
Scrollbar_数据表列表_横.grid(row=1,column=0,sticky=S+W+E+N) #10
Scrollbar_数据表列表_竖.grid(row=0,column=1,sticky=S+W+E+N) #01########################################################
## TKinter 主窗口布局 - TOP框 - 定位框20 - 常用功能框 ##
########################################################
常用功能框_表内全字段搜索 = LabelFrame(定位框20, text='常用功能框:表内全字段搜索')
常用功能框_表内全字段搜索.grid(row=1,column=3,sticky='NW')## 常用功能:表内全字段搜索 SELECT * FROM 数据库表名 WHERE 列名1 LIKE "%查找内容%" or 列名2 LIKE "%查找内容%";
def DEF_按钮_表内全字段搜索():# 根据表名查全字段数据库表名 = DB_TABLE_NAME.get()if 数据库表名 == '':ERROR = '请先打开一个表'tkinter.messagebox.showerror(title='ERROR', message=ERROR)else:SQL_CMD_PRAGMA = f'PRAGMA table_info ({数据库表名})' # 获取表的全部字段信息R = DEF_SQL_查询和返回(SQL_CMD_PRAGMA)if R[0] == 0:查询结果 = R[1] # [(0, 'ID', 'INTEGER', 1, None, 1), (1, 'A', 'TEXT', 0, None, 0)]L_字段名 = [i[1] for i in 查询结果] # 提取字段名# 制作全表全字段搜索SQL语句:SELECT * FROM 数据库表名 WHERE 列名1 LIKE "%查找内容%" or 列名2 LIKE "%查找内容%";查找内容 = Entry_查找内容.get() # 获取查找内容值SELECT_LIKE_TEXT = ''for i in range(0, len(L_字段名)):if i == 0:SELECT_LIKE_TEXT += f'{L_字段名[i]} LIKE "%{查找内容}%"'else:SELECT_LIKE_TEXT += f' or {L_字段名[i]} LIKE "%{查找内容}%"'SQL_CMD_SELECT_LIKE = f'SELECT * FROM {数据库表名} WHERE {SELECT_LIKE_TEXT}'Log.debug(f'DEF_按钮_表内全字段搜索 查找内容={查找内容} 生成搜索SQL语句 {SQL_CMD_SELECT_LIKE}')DEF_SQL_查询和显示(SQL_CMD_SELECT_LIKE)else:tkinter.messagebox.showerror(title='ERROR', message=R[1])Label(常用功能框_表内全字段搜索, text='搜索内容:').grid(row=0, column=0)
Entry_查找内容 = Entry(常用功能框_表内全字段搜索)
Entry_查找内容.grid(row=0, column=1)
Button(常用功能框_表内全字段搜索, text='搜索', command=DEF_按钮_表内全字段搜索).grid(row=0, column=2)## 常用功能:SQL示例
常用功能框_SQL语句示例框 = LabelFrame(定位框20, text='常用功能框:SQL语句示例')
常用功能框_SQL语句示例框.grid(row=2,column=3,sticky='NW')SQL语句示例字典 = {'修改表':{'修改表名':'ALTER TABLE 旧表名 RENAME 新表名;','增加字段':'ALTER TABLE 表名 ADD 新列名 VARCHAR(10)','删除字段':'ALTER TABLE 表名 DROP 列名;','修改字段属性':'ALTER TABLE 表名 MODIFY 字段名 VARCHAR(200) NOT NULL DEFAULT "0000";','修改字段名':'ALTER TABLE 表名 CHANGE 原字段名 新字段名 字段类型 约束条件','修改字段位置':'ALTER TABLE user10 MODIFY card CHAR(10) AFTER test; -- 将card移到test后面'},'INSERT':{'插入一个字段':'INSERT INTO 表名 (列名) VALUES ("值");','插入多个字段':'INSERT INTO 表名 (列名1, 列名2) VALUES ("值1", "值2");'},'DELETE':{'删除':'DELETE FROM 表名 WHERE 列名 = "值";'},'UPDATE':{'更新一个字段':'UPDATE 表名 SET 列名 = "新值" WHERE 某列名 = "原值";','更新多个字段':'UPDATE 表名 SET 列名1 = "新值", 列名2 = "新值" WHERE 某列名 = "原值";'},'SELECT':{'全部数据':'SELECT * FROM 表名;','部分数据':'SELECT 字段1,字段2 FROM 表名 WHERE 字段=;','结果去重':'SELECT DISTINCT 列名 FROM 表名;','判断空值 IS NULL':'SELECT * FROM 表名 WHERE 列名 IS NULL;','判断非空值 IS NOT NULL':'SELECT * FROM 表名 WHERE 列名 IS NOT NULL;','模糊查询 拼接':'拼接','模糊查询':'SELECT * FROM 表名 WHERE 字段 LIKE "%查找内容%";','模糊查询 排除':'SELECT * FROM 表名 WHERE 列名 NOT LIKE "%查找内容%";','取反':'SELECT * FROM 表名 WHERE 列名 LIKE "[^12]0";','区间':'SELECT * FROM 表名 WHERE 列名 BETWEEN 1 AND 30; -- 查询 列名范围在1到30间的记录','判断 IN':'SELECT * FROM 表名 WHERE 字段 IN ("1","2"); -- 查询字段内容是 1 或 2 的记录','判断 OR':'SELECT * FROM 表名 WHERE 字段 = "1" OR 字段 = "2"; -- 查询字段内容是 1 或 2 的记录','判断 AND':'SELECT * FROM 表名 WHERE 字段 != "1" AND 字段 != "2"; -- 查询字段内容不是 1 或 2 的记录','排序 升序':'SELECT * FROM 表名 ORDER BY 列名 ASC; -- 升序(默认)','排序 降序':'SELECT * FROM 表名 ORDER BY 列名 DESC; -- 降序','筛选出重复记录':'SELECT * FROM 表名 where 列名 in (select 列名 from 表名 group by 列名 having count(列名) > 1);','聚集函数 计数 COUNT(*)':'SELECT COUNT(*) FROM 表名; -- 统计全记录数 ','聚集函数 总和 SUM()':'','聚集函数 平均 AVG()':'','聚集函数 最大 MAX()':'','聚集函数 最小 MIN()':'','分组 GROUP BY 子句':'SELECT name,COUNT(ID) FROM 表名 GROUP BY name; -- 同一个名字有多少个ID号(以名字分组,相同名字为一组)','GROUP BY 和 HAVING':'SELECT name,COUNT(ID) FROM 表名 GROUP BY name HAVING COUNT(ID)>3; -- 同一个名字有多少个ID号(以名字分组,相同名字为一组)再在结果中筛选出数量大于3的','模糊查询':'SELECT * FROM 表名 WHERE CONCAT(字段1,字段2) LIKE "%查找内容%";'}}def DEF_点击二级菜单(K1,K2):二级菜单选中值 = SQL语句示例字典[K1][K2]print("二级菜单选中值", 二级菜单选中值)文本框_命令行.delete(0.0, END)文本框_命令行.insert(0.0, 二级菜单选中值)文本框_命令行.focus_set()## 根据字典自动生成多级菜单
一级菜单 = Menu(tearoff=False) # 创建一级菜单对象
for K1 in SQL语句示例字典:二级菜单 = Menu(tearoff=False) # 创建二级菜单对象for K2 in SQL语句示例字典[K1]:二级菜单.add_command(label=K2, command=lambda STR1=K1,STR2=K2:DEF_点击二级菜单(STR1,STR2)) # 二级菜单添加'一级KEY'及'二级KEY'一级菜单.add_cascade(label=K1, menu=二级菜单) # 一级菜单添加'菜单项'并关联'二级菜单'def DEF_选择SQL示例语句():## 显示一级菜单一级菜单_X = Button_选择按钮.winfo_rootx() + Button_选择按钮.winfo_width()一级菜单_Y = Button_选择按钮.winfo_rooty() - Button_选择按钮.winfo_height()一级菜单.post(一级菜单_X, 一级菜单_Y)Button_选择按钮 = Button(常用功能框_SQL语句示例框, text='选择SQL示例语句', command=DEF_选择SQL示例语句)
Button_选择按钮.grid(row=0,column=0,sticky='NESW')########################################################
## TKinter 主窗口布局 - TOP框 - 定位框30 - 分页按钮框 ##
########################################################
分页按钮框 = Frame(定位框30)
修改确认框 = Frame(定位框30)
分页按钮框.grid(row=1,column=0,sticky='NW') #1-0
修改确认框.grid(row=2,column=0,sticky='NW') #2-0按钮_显编框起始页 = Button(分页按钮框, text='返回起始页/刷新', command=DEF_按钮_显编框起始页)
按钮_显编框上一页 = Button(分页按钮框, text='上一页', command=DEF_按钮_显编框上一页)
按钮_显编框下一页 = Button(分页按钮框, text='下一页', command=DEF_按钮_显编框下一页)
按钮_显编框起始页.grid(row=0,column=0, sticky='NW') # 显示起始页按钮
按钮_显编框上一页.grid(row=0,column=1, sticky='NW') # 显示上一页按钮
按钮_显编框下一页.grid(row=0,column=2, sticky='NW') # 显示下一页按钮
Label(分页按钮框, text='[分页显示行数]').grid(row=0,column=3,sticky='W')
Combobox_分页显示行数 = ttk.Combobox(分页按钮框, width=4)
Combobox_分页显示行数['value'] = (5, 10, 20, 50, 100, 200)
Combobox_分页显示行数.current(0) # 默认值中的内容为索引,从0开始
Combobox_分页显示行数.grid(row=0, column=4, sticky='W')
Label(分页按钮框, text='[单元格限宽]').grid(row=0,column=5,sticky='W')
Entry(分页按钮框, textvariable=IV_单元格限宽, width=4).grid(row=0,column=6,sticky='W')
Label(分页按钮框, text='[自定义空值表示]').grid(row=0,column=7,sticky='W')
Entry(分页按钮框, textvariable=SV_自定义空值字符串表示, width=8).grid(row=0,column=8,sticky='W')def 选择后执行函数(event):分页行数.set(Combobox_分页显示行数.get())Combobox_分页显示行数.bind('<<ComboboxSelected>>', 选择后执行函数)## 不用时禁止
#按钮_显编框起始页['state'] = 'disabled'
#按钮_显编框上一页['state'] = 'disabled'
按钮_显编框下一页['state'] = 'disabled'
## 需要时启用
#按钮_显编框起始页['state'] = 'normal'
#按钮_显编框上一页['state'] = 'normal'
#按钮_显编框下一页['state'] = 'normal'########################################################
## TKinter 主窗口布局 - TOP框 - 定位框30 - 修改确认框 ##
########################################################
按钮_确认修改数据库 = Button(修改确认框, text='确认修改', bg='#7FFF00', command=DEF_按钮_确认修改数据库)
#进行编辑后再出现
按钮_确认修改数据库.grid(row=0, column=0, sticky='NW')
按钮_确认修改数据库.grid_forget() # 隐藏
#按钮_确认修改数据库.grid() # 显示#########################################
## TKinter 主窗口布局 - TOP框 - 命令框 ##
#########################################
命令行_按钮框 = Text(命令框)
命令行_按钮框.grid(row=0, column=0, sticky='W')
Button(命令行_按钮框, text='执行SQL语句', command=DEF_按钮_执行SQL语句).grid(row=0, column=0)
Button(命令行_按钮框, text='执行SQL脚本', command=DEF_按钮_执行SQL脚本).grid(row=0, column=1)
Button(命令行_按钮框, text='清屏', command=DEF_按钮_清屏).grid(row=0, column=2)文本框_命令行 = Text(命令框, height=3, wrap='none')
文本框_命令行.grid(row=1,column=0,sticky='NESW')
Scrollbar_命令框_竖 = Scrollbar(命令框)
Scrollbar_命令框_竖['command'] = 文本框_命令行.yview
Scrollbar_命令框_横 = Scrollbar(命令框)
Scrollbar_命令框_横['command'] = 文本框_命令行.xview
Scrollbar_命令框_横['orient'] = HORIZONTAL
Scrollbar_命令框_竖.grid(row=1, column=1, sticky=S+W+E+N)
Scrollbar_命令框_横.grid(row=2, column=0, sticky=S+W+E+N)
文本框_命令行.config(xscrollcommand=Scrollbar_命令框_横.set, yscrollcommand=Scrollbar_命令框_竖.set) # 自动设置滚动条滑动幅度本地SQL脚本文件操作框 = Frame(命令框)
本地SQL脚本文件操作框.grid(row=3, column=0, sticky='W')
Button(本地SQL脚本文件操作框, text='选择脚本文本', command=DEF_按钮_选择SQL脚本文本).grid(row=0, column=0, sticky='NW')
Entry(本地SQL脚本文件操作框, textvariable=DB_SQL_SCRIPT_FILE, width=55).grid(row=0, column=1)
Button(本地SQL脚本文件操作框, text='执行脚本文件', command=DEF_按钮_执行SQL脚本文件).grid(row=0, column=2, sticky='E')###########################################
## TKinter 主窗口布局 - TOP框 - 定位框50 ##
###########################################
全局变量框 = LabelFrame(定位框50, text='全局变量框,实时更新,请勿修改')
全局变量框.grid(row=2,column=0,sticky='NW') #2-0########################################################
## TKinter 主窗口布局 - TOP框 - 定位框50 - 全局变量框 ##
########################################################
数据库信息框 = LabelFrame(全局变量框, text='数据库信息')
Label(数据库信息框, text='[数据库路径]').grid( row=0,column=0,sticky='W')
Entry(数据库信息框, textvariable=DB_FULL_NAME).grid( row=0,column=1,sticky='W')
Label(数据库信息框, text='[数据表名称]').grid( row=1,column=0,sticky='W')
Entry(数据库信息框, textvariable=DB_TABLE_NAME).grid( row=1,column=1,sticky='W')
Label(数据库信息框, text='[SV_最后查询语句]').grid( row=2,column=0,sticky='W')
Entry(数据库信息框, textvariable=SV_最后查询语句).grid(row=2,column=1,sticky='W')
Label(数据库信息框, text='[SV_查询字段列表]').grid( row=3,column=0,sticky='W')
Entry(数据库信息框, textvariable=SV_查询字段列表).grid(row=3,column=1,sticky='W')
Label(数据库信息框, text='[新建数据表名]').grid( row=4,column=0,sticky='W')
Entry(数据库信息框, textvariable=新建数据表名).grid( row=4,column=1,sticky='W')
数据库信息框.grid(row=0, column=0, sticky='N', rowspan=3)显编框_字段框 = LabelFrame(全局变量框, text='显编框.字段框定位')
Label(显编框_字段框, text='[列]').grid(row=0,column=0,sticky='W')
Entry(显编框_字段框, textvariable=字段框_定位列, width=3).grid(row=0,column=1,sticky='W')
显编框_字段框.grid(row=0,column=2,sticky='W')显编框_数据框 = LabelFrame(全局变量框, text='显编框.数据框定位')
Label(显编框_数据框, text='[行]').grid(row=0,column=0,sticky='W')
Entry(显编框_数据框, textvariable=数据框_定位行, width=3).grid(row=0,column=1,sticky='W')
Label(显编框_数据框, text='[列]').grid(row=0,column=2,sticky='W')
Entry(显编框_数据框, textvariable=数据框_定位列, width=3).grid(row=0,column=3, sticky='W')
显编框_数据框.grid(row=0,column=3,sticky='E')## 查看光标位置(当前就是看看,没有实际用途)
光标定位框 = LabelFrame(全局变量框, text='光标定位框')
Label(光标定位框, text='[IV_光标X轴]').grid(row=0,column=0,sticky='W')
Entry(光标定位框, textvariable=IV_光标X轴, width=6).grid(row=0, column=1, sticky='W',columnspan=3)
Label(光标定位框, text='[IV_光标Y轴]').grid(row=1,column=0,sticky='W')
Entry(光标定位框, textvariable=IV_光标Y轴, width=6).grid(row=1, column=1, sticky='W',columnspan=3)
光标定位框.grid(row=1,column=2,sticky='W')显编框_编辑后定位框 = LabelFrame(全局变量框, text='显编框_编辑后定位框')
Label(显编框_编辑后定位框, text='[IV_已显示记录数]').grid( row=0,column=0,sticky='W')
Entry(显编框_编辑后定位框, textvariable=IV_已显示记录数, width=3).grid(row=0,column=1, sticky='W')
Label(显编框_编辑后定位框, text='[IV_上次分页行数]').grid( row=1,column=0,sticky='W')
Entry(显编框_编辑后定位框, textvariable=IV_上次分页行数, width=3).grid(row=1,column=1, sticky='W')
显编框_编辑后定位框.grid(row=1,column=3,sticky='W')显编框_分页控制框 = LabelFrame(全局变量框, text='显编框_分页控制框')
Label(显编框_分页控制框, text='[分页行数]').grid( row=0,column=0,sticky='W')
Entry(显编框_分页控制框, textvariable=分页行数, width=6).grid(row=0,column=1, sticky='W')
显编框_分页控制框.grid(row=2,column=2,sticky='W')显编框_单元格限宽 = LabelFrame(全局变量框, text='显编框_单元格限宽')
Label(显编框_单元格限宽, text='[单元格限宽]').grid( row=0,column=0,sticky='W')
Entry(显编框_单元格限宽, textvariable=IV_单元格限宽, width=6).grid(row=0,column=1, sticky='W')
显编框_单元格限宽.grid(row=2,column=3,sticky='W')top.grid_columnconfigure(0, weight=1) # 自适应窗口宽
top.mainloop() ## 进入消息循环
## END ##
使用Python3自带GUI tkinter 做的图形化操作SQLite3数据库的工具 v1.12 修修补补将就用相关推荐
- 用python自带的tkinter做游戏(一)—— 贪吃蛇 篇
用python自带的tkinter做游戏(一)-- 贪吃蛇 篇 本人新手,刚自学python满半年,现分享下心得,希望各位老手能指点一二,也希望和我一样的新手能共勉,谢谢~ 大家都知道用python做 ...
- 用python自带的tkinter做游戏(二)—— 俄罗斯方块 篇
上回分享了博文 用python自带的tkinter做游戏(一)-- 贪吃蛇 篇 今天继续,尝试用tkinter来制作一个更经典的游戏 -- 俄罗斯方块. 俄罗斯方块相信大家都玩过,一共有七个方块组,每 ...
- python函数做菜单_PYTHON图形化操作界面的编程七__创建菜单
PYTHON图形化操作界面的编程七__创建菜单 十八.创建菜单 1.水平菜单的创建 创建菜单需要多条语句,所以这里通过实例来说明水平菜单的创建方法: 下面的语句可以在窗口中添加水平菜单,其中前四行语句 ...
- Python自动生成代码 - 通过tkinter图形化操作生成代码框架
Python自动生成代码 - 通过tkinter图形化操作生成代码框架 背景 脚本代码 Demo_CodeGenerator.py display.py FileHandler.py: 脚本运行结果: ...
- 利用 Tkinter 打造专属图形化界面:设计签名
大家好,今天本期带着大家制作一个属于自己的GUI图形化界面:用于设计签名的哦(效果如下图),是不是感觉很好玩,是不是很想学习呢? 限于篇幅,今天我们首先详细讲述一下Tkinter的使用方法.本来不准备 ...
- python tkinter计算器实例_使用Python自带GUI tkinter编写一个期权价格计算器
0 准备工作 首先,确认环境中有numpy.scipy.stats和tkinter三个功能包.前两个功能包可用于Python的数学计算,比如使用numpy来生成随机数用于Monte Carlo模拟,以 ...
- python 布莱克舒尔斯_使用Python自带GUI tkinter编写一个期权价格计算器
0 准备工作 首先,确认环境中有numpy.scipy.stats和tkinter三个功能包.前两个功能包可用于Python的数学计算,比如使用numpy来生成随机数用于Monte Carlo模拟,以 ...
- 端口映射+物理机、虚拟机互访+Pyqt5、Gui图形化界面+Mysql数据库
目录 一.背景 1.1.要求 1.2.说明 1.2.1.环境 二.步骤详解 2.1.在linux下安装mysql数据库,并能通过localhost/phpmyadmin登录,用户名密码均设为root ...
- 用python的tkinter做游戏(八)—— 实现图片在tkinter中自适应大小(自动匹配窗口)
用python的tkinter做游戏 系列: 用python自带的tkinter做游戏(一)-- 贪吃蛇 篇 用python自带的tkinter做游戏(二)-- 俄罗斯方块 篇 用python自带的t ...
- 用python的tkinter做游戏(七)—— 双人射击游戏Demo(类的应用) 篇
不知不觉这已经是第七篇文章了,今天来谈谈python中类(class)在游戏中的应用. 老规矩,先展现一下之前的几篇博文: 用python自带的tkinter做游戏(一)-- 贪吃蛇 篇 用pytho ...
最新文章
- go语言io和ioutil包的学习和使用
- 【Spring框架家族】Spring Cloud Eureka 之常用配置解析(转载)
- 企业经营私域运营的三大核心系列直播课
- 玻璃体混浊当前的治疗方案汇总
- 使用hotnode自动更新脚本(热发布)
- Kinect开发笔记之一Kinect详细介绍
- ASP.NET Core 项目简单实现身份验证及鉴权
- Mac远程连接服务器
- tomcat session失效时间
- QR code 二维码基础入门教程
- jquery的DOM节点操作(创建和插入元素节点)
- johnson算法 java_Johnson-trotter 算法,一种高效的全排序算法的java实现
- 常用的浏览器及其内核
- html中滚动字幕是什么属性,html中Marquee属性详解(滚动显示文本/图片)
- 怎么压缩word文档的大小?
- python 实现盒滤波boxfilter
- 实现Linux与windows文件互传
- 快速下载||AnotherRedisDesktopManagerMedis-Redis可视化工具
- 03【Verilog实战】UART通信协议,半双工通信方式(附源码)
- ​浙江省交通运输物流信息服务中心平台容灾设备采购二期项目