#_*_ 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 修修补补将就用相关推荐

  1. 用python自带的tkinter做游戏(一)—— 贪吃蛇 篇

    用python自带的tkinter做游戏(一)-- 贪吃蛇 篇 本人新手,刚自学python满半年,现分享下心得,希望各位老手能指点一二,也希望和我一样的新手能共勉,谢谢~ 大家都知道用python做 ...

  2. 用python自带的tkinter做游戏(二)—— 俄罗斯方块 篇

    上回分享了博文 用python自带的tkinter做游戏(一)-- 贪吃蛇 篇 今天继续,尝试用tkinter来制作一个更经典的游戏 -- 俄罗斯方块. 俄罗斯方块相信大家都玩过,一共有七个方块组,每 ...

  3. python函数做菜单_PYTHON图形化操作界面的编程七__创建菜单

    PYTHON图形化操作界面的编程七__创建菜单 十八.创建菜单 1.水平菜单的创建 创建菜单需要多条语句,所以这里通过实例来说明水平菜单的创建方法: 下面的语句可以在窗口中添加水平菜单,其中前四行语句 ...

  4. Python自动生成代码 - 通过tkinter图形化操作生成代码框架

    Python自动生成代码 - 通过tkinter图形化操作生成代码框架 背景 脚本代码 Demo_CodeGenerator.py display.py FileHandler.py: 脚本运行结果: ...

  5. 利用 Tkinter 打造专属图形化界面:设计签名

    大家好,今天本期带着大家制作一个属于自己的GUI图形化界面:用于设计签名的哦(效果如下图),是不是感觉很好玩,是不是很想学习呢? 限于篇幅,今天我们首先详细讲述一下Tkinter的使用方法.本来不准备 ...

  6. python tkinter计算器实例_使用Python自带GUI tkinter编写一个期权价格计算器

    0 准备工作 首先,确认环境中有numpy.scipy.stats和tkinter三个功能包.前两个功能包可用于Python的数学计算,比如使用numpy来生成随机数用于Monte Carlo模拟,以 ...

  7. python 布莱克舒尔斯_使用Python自带GUI tkinter编写一个期权价格计算器

    0 准备工作 首先,确认环境中有numpy.scipy.stats和tkinter三个功能包.前两个功能包可用于Python的数学计算,比如使用numpy来生成随机数用于Monte Carlo模拟,以 ...

  8. 端口映射+物理机、虚拟机互访+Pyqt5、Gui图形化界面+Mysql数据库

    目录 一.背景 1.1.要求 1.2.说明 1.2.1.环境 二.步骤详解 2.1.在linux下安装mysql数据库,并能通过localhost/phpmyadmin登录,用户名密码均设为root ...

  9. 用python的tkinter做游戏(八)—— 实现图片在tkinter中自适应大小(自动匹配窗口)

    用python的tkinter做游戏 系列: 用python自带的tkinter做游戏(一)-- 贪吃蛇 篇 用python自带的tkinter做游戏(二)-- 俄罗斯方块 篇 用python自带的t ...

  10. 用python的tkinter做游戏(七)—— 双人射击游戏Demo(类的应用) 篇

    不知不觉这已经是第七篇文章了,今天来谈谈python中类(class)在游戏中的应用. 老规矩,先展现一下之前的几篇博文: 用python自带的tkinter做游戏(一)-- 贪吃蛇 篇 用pytho ...

最新文章

  1. go语言io和ioutil包的学习和使用
  2. 【Spring框架家族】Spring Cloud Eureka 之常用配置解析(转载)
  3. 企业经营私域运营的三大核心系列直播课
  4. 玻璃体混浊当前的治疗方案汇总
  5. 使用hotnode自动更新脚本(热发布)
  6. Kinect开发笔记之一Kinect详细介绍
  7. ASP.NET Core 项目简单实现身份验证及鉴权
  8. Mac远程连接服务器
  9. tomcat session失效时间
  10. QR code 二维码基础入门教程
  11. jquery的DOM节点操作(创建和插入元素节点)
  12. johnson算法 java_Johnson-trotter 算法,一种高效的全排序算法的java实现
  13. 常用的浏览器及其内核
  14. html中滚动字幕是什么属性,html中Marquee属性详解(滚动显示文本/图片)
  15. 怎么压缩word文档的大小?
  16. python 实现盒滤波boxfilter
  17. 实现Linux与windows文件互传
  18. 快速下载||AnotherRedisDesktopManagerMedis-Redis可视化工具
  19. 03【Verilog实战】UART通信协议,半双工通信方式(附源码)
  20. ​浙江省交通运输物流信息服务中心平台容灾设备采购二期项目

热门文章

  1. HDU5773(The All-purpose Zero LIS变形)
  2. LDAP学习小结【仅原理和基础篇】
  3. 记录一下C#使用SmtpClient发送网易163、126邮件使用sll协议失败的坑
  4. adb 查看固件版本
  5. Adobe Flash Player 是什么
  6. 绝对干货的Twitter搜索技巧
  7. 令人头秃,SaaS部署和本地部署该怎么选?
  8. 网页制作全套视频教程下载(包括html,ASP,Flash,Photoshop)
  9. 计算机其它离的360云盘,360云盘资源转到百度云 360云盘搬家教程
  10. Mysql 最全教程