2022-8-4 合并SQLite和MYSQL操作界面
#_*_ coding:utf8 _*_
## Python3 GUI tkinter 数据库图形化操作工具
## Python 自带 GUI tkinter 使用示例
## V2.0.2 尝试汇总 SQLite3/MySQL/Oracle/SQLServer
## BUG 修改数据时,如果主键有值和自定义空值表示相同,则无法修改,需设置新字符串表示空值后才能修改
## BUG 对乱操作的限制没有做,对Treeview的控制能力不足,请不要重复打开数据库...
## 修复一些大小BUG,修修补补继续将就着用吧!
import sqlite3 # Python 自带 SQLite3 模块
import os,time,re
from tkinter import *
from tkinter import filedialog # 选择文件用
from tkinter import ttk # 下拉菜单控件在ttk中
import tkinter.messagebox # 弹出提示对话框
import tkinter.simpledialog # 弹出对话框,获取用户输入
import csv # CSV文件操作模块,用于导出数据
try:
import MySQLdb # MySQL 第三方模块,使用 pip install mysqlclient 安装
except Exception as e:
print(f"import MySQLdb 失败\n{e}\n要使用MySQL数据库先安装MySQLdb模块(pip install mysqlclient)")
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) # 严重错误
## 通过外置字体计算字符串像素宽返回Entry标准宽,PIL模块Python3安装 pip install pillow
try:
from PIL import ImageFont
字体文件路径 = 'C:\\Windows\\Fonts\\Arial.ttf' # Win10 字体文件位置
#字体文件路径 = 'Arial.ttf' # 复制到此脚本同目录
font_Arial_8 = ImageFont.truetype(字体文件路径, 8, encoding='utf-8')
except Exception as e:
Log.info(f"{e} 加载Windows字体Arial.ttf失败,无法精确计算字符宽度")
加载外置字体失败 = 0
else:
加载外置字体失败 = 1
def 计算字符串像素宽返回Entry标准宽(字符串, 外置字体=加载外置字体失败):
if 字符串 == None: # 空值
return(4) # 返回宽度4
if 外置字体 == 1:
字符串_w, 字符串_h = font_Arial_8.getsize(字符串)
return(字符串_w//4+2) # 像素级精确控制
else:
return(len(字符串)*2) # 统一给2倍的量
def DEV_SQLite3_OPEN(DB_File): # 打开SQLite3数据库文件,不存在会自动创建
try:
conn = sqlite3.connect(DB_File) # 尝试打开数据库文件
except Exception as e:
E = '打开数据库文件失败 ' + str(e)
return(1, E) # 返回错误代码1和失败原因
else:
return(0, conn) # 返回成功代码0和数据库连接对象
def DEV_MySQL_OPEN(登录地址, 登录帐号, 登录密码, 服务端口, 登录库名, 字符集): # 连接MySQL数据库
try:
数据库连接对象 = MySQLdb.connect(登录地址, 登录帐号, 登录密码, 登录库名, 服务端口, 字符集)
except Exception as e:
E = '连接数据库失败 ' + str(e)
return(1, E) # 返回错误代码1和失败原因
else:
return(0, 数据库连接对象) # 返回成功代码0和数据库连接对象
def DEF_SQL_查询和返回(SQL_CMD): # 执行SQL查询语句(DQL - 数据查询语言 SELECT 或 SQLite3 pragma 命令),返回执行状态和执行结果(数据列表)
数据库连接对象 = 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['数据库游标对象'] = ''
SV_数据库游标对象.set('')
Log.debug(f'游标对象={游标对象} 关闭游标')
INFO = f'执行SQL语句 {SQL_CMD} 成功 返回 {len(全部记录)} 条记录'
TEXT_数据库操作日志.insert(0.0, INFO+'\n', 'tag_i')
return(0, 全部记录)
def DEF_SQL_查询和返回_数据列表_字段列表(SQL_CMD): # 执行SQL查询语句,返回执行状态和执行结果(数据列表,字段列表)导出使用
数据库连接对象 = 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, 全部记录, 字段名列表)
def DEF_查表字段属性返回字段名及是否主键():
L_字段名 = []
L_主键 = []
数据表名 = SV_数据库树_选中表名.get()
当前数据库类型 = SV_数据库类型.get()
if 当前数据库类型 == 'SQLite3':
SQL_CMD = f'PRAGMA table_info({数据表名})' # 获取字段属性命令
R = DEF_SQL_查询和返回(SQL_CMD)
if R[0] == 0:
字段信息 = R[1] # 示例 [(0, 'ID', 'INTEGER', 1, None, 1), (1, 'A', 'TEXT', 0, None, 0)]
Log.debug(f'{当前数据库类型} 数据表名={数据表名} 字段信息={字段信息}')
L_字段名 = [i[1] for i in 字段信息] # 字段名在第2列
L_主键 = [i[1] for i in 字段信息 if i[5]!=0] # 第6列为非0表示是主键
else:
TEXT_数据库操作日志.insert(0.0, R[1]+'\n', 'tag_e')
elif 当前数据库类型 == 'MySQL':
SQL_CMD = f'SELECT COLUMN_NAME,COLUMN_KEY FROM information_schema.columns WHERE table_name="{数据表名}"'
R = DEF_SQL_查询和返回(SQL_CMD)
if R[0] == 0:
字段信息 = R[1]
Log.debug(f'{当前数据库类型} 数据表名={数据表名} 字段信息={字段信息}')
L_字段名 = [i[0] for i in 字段信息] # 字段名在第1列
L_主键 = [i[0] for i in 字段信息 if i[1] == 'PRI'] # 第2列为PRI表示是主键
else:
TEXT_数据库操作日志.insert(0.0, R[1]+'\n', 'tag_e')
else:
ERROR = f'未知{当前数据库类型}类型数据库的查询表字段的SQL语句'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
return(L_字段名,L_主键)
def 判断主键是否可用(): # 查找主键,为避免错误修改,数据表必须有主键,根据主键修改对应行数据,没有主键的表只能通过命令框手写语句修改
当前字段列表 = DB_INFO['字段名列表']
L_字段名, L_主键 = DEF_查表字段属性返回字段名及是否主键()
if L_主键 == []:
Log.debug('没有主键')
return(1, f'没有主键,为防止自动操作出错,请通过手工执行SQL语句操作')
else:
P_未出现的主键 = set(L_主键) - set(当前字段列表) # 集合计算,找出未出现在显示字段的主键
if len(P_未出现的主键) != 0:
Log.debug(f'L_主键={L_主键} 当前字段列表={当前字段列表} P_未出现的主键={P_未出现的主键}')
return(1, f'L_主键={L_主键} 未显示的主键={P_未出现的主键},为防止自动操作出错,请通过手工执行SQL语句操作')
else:
L_主键信息 = []
for 主键名 in L_主键:
列号 = 当前字段列表.index(主键名)
主键信息 = (列号,主键名)
L_主键信息.append(主键信息)
Log.debug(f'L_主键={L_主键} 当前字段列表={当前字段列表} L_主键信息={L_主键信息} 主键全部在场')
return(0, L_主键信息)
def DEF_SQL_查询和显示(SQL_CMD, 显示开始行定位=0): # 执行SQL查询语句,直接显示在界面,不返回。参数:显示开始行定位,从指定行后开始显示,默认值为0,表示从头全部显示
## 是否应该关一下不用的游标对象 if DB_INFO['数据库游标对象'] != ''
数据库连接对象 = 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')
游标对象_字段名列表 = 游标对象.description # 获取字段信息
字段名列表 = [i[0] for i in 游标对象_字段名列表]
DB_INFO['字段名列表'] = 字段名列表 # 保存字段名查询结果
SV_查询字段列表.set(str(字段名列表)) # 显示字段名查询结果
DB_INFO['最后查询语句'] = SQL_CMD # 保存成功执行的SQL查询语句
SV_最后查询语句.set(SQL_CMD) # 显示成功执行的SQL查询语句
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['数据库游标对象'] = '' # 清空数据库游标对象
SV_数据库游标对象.set('')
按钮_显编框下一页['state'] = 'disabled' # 禁止下一页按钮(第一次显示就全部显示完整,不需要下一页按钮)
DEBUG = f"游标对象={游标对象} 关闭游标(数据<分页第一页) 设置 DB_INFO['数据库游标对象']='' 设置下一页按钮禁止使用"
Log.debug(DEBUG)
else:
DB_INFO['数据库游标对象'] = 游标对象 # 缓存数据库游标对象
SV_数据库游标对象.set(str(游标对象))
按钮_显编框下一页['state'] = 'normal' # 启用下一页按钮
DEBUG = f"游标对象={游标对象} 缓存游标(数据>=分页第一页)(用于继续读取显示下一页) 设置 DB_INFO['数据库游标对象']=游标对象 设置下一页按钮可以使用"
Log.debug(DEBUG)
else:
字段和数据的存储和展示(字段名列表, [])
游标对象.close() # 关闭游标对象
DB_INFO['数据库游标对象'] = '' # 清空数据库游标对象
SV_数据库游标对象.set('')
按钮_显编框下一页['state'] = 'disabled' # 禁止下一页按钮
DEBUG = f"游标对象={游标对象} 关闭游标(数据=空,不用分页) 设置 DB_INFO['数据库游标对象']='' 设置下一页按钮禁止使用"
Log.debug(DEBUG)
else: # 分页限制行数 <= 0 读取全部记录,不分页
全部记录 = 游标对象.fetchall() # 从SQL查询结果中取出全部的记录
字段和数据的存储和展示(字段名列表, 全部记录)
游标对象.close() # 关闭游标对象
DB_INFO['数据库游标对象'] = '' # 清空数据库游标对象
SV_数据库游标对象.set('')
按钮_显编框下一页['state'] = 'disabled' # 禁止下一页按钮
DEBUG = f"游标对象={游标对象} 关闭游标(用户通过设置分页行数<=0一次性读取全部数据) 设置 DB_INFO['数据库游标对象']='' 设置下一页按钮禁止使用"
Log.debug(DEBUG)
实际读取记录行数 = len(全部记录) ### 用于修改数据库后,再次显示在修改位置
IV_已显示记录数.set(IV_已显示记录数.get() + 实际读取记录行数) ### 用于修改数据库后,再次显示在修改位置
IV_上次分页行数.set(实际读取记录行数) ### 用于修改数据库后,再次显示在修改位置
def 执行上条查询语句_刷新显示(重新定位要求):
最后查询语句 = DB_INFO['最后查询语句']
Log.debug(f"执行上条查询语句 DB_INFO['最后查询语句']={最后查询语句}")
if 最后查询语句 != '':
if 重新定位要求 == '从头开始':
DEF_SQL_查询和显示(最后查询语句)
elif 重新定位要求 == '已翻页位置开始':
显示开始行定位 = IV_已显示记录数.get() - IV_上次分页行数.get()
DEF_SQL_查询和显示(最后查询语句, 显示开始行定位)
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['数据库游标对象'] = '' ## 清空数据库游标对象
SV_数据库游标对象.set('')
按钮_显编框下一页['state'] = 'disabled' ## 禁止下一页按钮
INFO = '已经到底,当前页已经是全部记录'
tkinter.messagebox.showinfo(title='INFO', message=INFO)
Log.debug(f"游标对象={游标对象} 关闭游标(数据=空,当前页已经是最后)")
else:
if 实际读取记录行数 < 分页限制行数: # 已经全部显示,没有后续分页
游标对象.close() ## 关闭游标对象
DB_INFO['数据库游标对象'] = '' ## 清空数据库游标对象
SV_数据库游标对象.set('')
按钮_显编框下一页['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['数据库游标对象'] = '' ## 清空数据库游标对象
SV_数据库游标对象.set('')
按钮_显编框下一页['state'] = 'disabled' ## 禁止下一页按钮
if 全部记录 == []:
INFO = '已经到底,当前页已经是全部记录'
tkinter.messagebox.showinfo(title='INFO', message=INFO)
else:
字段和数据的存储和展示(字段名列表, 全部记录)
实际读取记录行数 = len(全部记录) ### 用于修改数据库后,再次显示在修改位置
IV_已显示记录数.set(IV_已显示记录数.get() + 实际读取记录行数) ### 用于修改数据库后,再次显示在修改位置
IV_上次分页行数.set(实际读取记录行数) ### 用于修改数据库后,再次显示在修改位置
def DEF_SQL_执行(SQL_CMD): # 非查询的SQL语句(执行一条SQL语句)(每次执行都要打开关闭新的游标)
if SV_安全模式状态.get() == '关闭':
数据库连接对象 = 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() # 提交更改
受影响行数 = '未知'
if SV_数据库类型.get() == 'MySQL':
受影响行数 = 游标对象.rowcount # 获取受影响行数
游标对象.close()
DB_INFO['数据库游标对象'] = ''
SV_数据库游标对象.set('')
Log.debug(f'游标对象={游标对象} 关闭游标')
INFO = f'执行SQL语句 {SQL_CMD} 成功 受影响行数={受影响行数}'
TEXT_数据库操作日志.insert(0.0, INFO+'\n', 'tag_w')
return(0, INFO)
else:
安全模式处理SQL操作([SQL_CMD]) ## 安全模式,任何修改数据库的命令不直接运行,都放入命令行文本框,由人工审核后再手动执行
return(0, '未实际执行,仅写到SQL命令行中,执行需要设置安全模式=关闭')
def DEF_SQL_执行多条_忽略错误语句(L_SQL_CMD): ## 非查询的SQL语句(执行多条SQL语句)(成功的提交更改,跳过错误的语句)## SQLite3 不能使用事件,无法回退
L_成功信息 = []
L_失败信息 = []
if SV_安全模式状态.get() == '关闭':
失败标记 = 0
for SQL_CMD in L_SQL_CMD:
R = DEF_SQL_执行(SQL_CMD)
if R[0] == 0:
L_成功信息.append(R[1])
else:
L_失败信息.append(R[1])
return(L_成功信息, L_失败信息)
else:
安全模式处理SQL操作(L_SQL_CMD) ## 安全模式,任何修改数据库的命令不直接运行,都放入命令行文本框,由人工审核后再手动执行
return(L_成功信息, L_失败信息)
def 安全模式处理SQL操作(L_SQL_CMD): # 安全模式,任何修改数据库的命令不直接运行,都放入命令行文本框,由人工审核后再手动执行
文本框_命令行.delete(0.0, END)
TEXT = ';\n'.join(L_SQL_CMD)
文本框_命令行.insert(0.0, TEXT+'\n')
文本框_命令行.focus_set()
def DEF_SQL_执行脚本(SQL_SCRIPT): # 执行SQL脚本
数据库连接对象 = 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['数据库游标对象'] = ''
SV_数据库游标对象.set('')
Log.debug(f'游标对象={游标对象} 关闭游标')
INFO = f'执行SQL脚本 {SQL_SCRIPT} 成功'
TEXT_数据库操作日志.insert(0.0, INFO+'\n', 'tag_w')
return(0, INFO)
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_每列最大宽度}")
DB_INFO['字段宽度记录'] = dict(zip(L_字段名,L_每列最大宽度)) # 保存结果后续新增数据窗口可用
SV_字段宽度记录.set(str(DB_INFO['字段宽度记录'])) # DEBUG查看
T0 = time.time()
FRAME_CLEAR(LF_显示编辑框) # 清空框内控件
Log.debug(f"清空框内控件 用时={time.time()-T0:.3f}秒")
## 创建画布
画布 = Canvas(LF_显示编辑框, 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(主窗口高) -450 # 预留下边控件空间
## 设置画布参数
总行数 = 行数 + 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(LF_显示编辑框, command=画布.yview) # 创建竖滚动条
Scrollbar_画布_竖.grid(row=0,column=1,sticky=S+W+E+N) # 竖滚动条定位
Scrollbar_画布_横 = Scrollbar(LF_显示编辑框, 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("", 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("", DEF_鼠标事件_左键单击) # 每个控件对象都绑定左键单击事件(不用了)
#字典_查询结果_坐标_对象[(行,列)].bind('', DEF_鼠标事件_光标悬停) # 每个控件对象都绑定光标悬停事件(尝试中)
字典_查询结果_坐标_对象[(行,列)].bind("", DEF_鼠标事件_右键菜单_数据单元) # 每个控件对象都绑定右键菜单事件
字典_查询结果_坐标_对象[(行,列)].bind('', DEF_键盘事件_任意输入_数据单元) # 每当输入内容时执行一次函数
def 生成_库名表名限定SQL语句部分(): # 数据库名.数据表名
数据库名 = SV_数据库树_选中库名.get()
数据表名 = SV_数据库树_选中表名.get()
当前数据库类型 = SV_数据库类型.get()
if 当前数据库类型 == 'SQLite3':
SQL_库名表名 = 数据表名
elif 当前数据库类型 == 'MySQL':
SQL_库名表名 = f'{数据库名}.{数据表名}'
else:
SQL_库名表名 = ''
ERROR = f'未知{当前数据库类型}类型数据库 {数据库名}.{数据表名}'
TEXT_数据库操作日志.insert(0.0, ERROR+'\n', 'tag_e')
return(SQL_库名表名)
def 生成_查表SQL语句():
数据库名 = SV_数据库树_选中库名.get()
当前数据库类型 = SV_数据库类型.get()
if 当前数据库类型 == 'SQLite3':
SQL_CMD = 'SELECT NAME FROM sqlite_master' # SQLite3查表命令,返回数据结构 [('sqlite_sequence',), ('t0',), ('t1',)]
elif 当前数据库类型 == 'MySQL':
SQL_CMD = f'SHOW TABLES FROM {数据库名};' # MySQL查表命令,返回数据结构 (('t',), ('t2',))
else:
SQL_CMD = ''
ERROR = f'未知{当前数据库类型}类型数据库的查询表名的SQL语句'
TEXT_数据库操作日志.insert(0.0, ERROR+'\n', 'tag_e')
return(SQL_CMD)
def 生成_查表内容SQL语句():
数据库名 = SV_数据库树_选中库名.get()
数据表名 = SV_数据库树_选中表名.get()
当前数据库类型 = SV_数据库类型.get()
if 当前数据库类型 == 'SQLite3':
SQL_CMD = f'SELECT * FROM {数据表名}'
elif 当前数据库类型 == 'MySQL':
SQL_CMD = f'SELECT * FROM {数据库名}.{数据表名}'
else:
SQL_CMD = ''
ERROR = f'未知{当前数据库类型}类型数据库的查询表内容的SQL语句'
TEXT_数据库操作日志.insert(0.0, ERROR+'\n', 'tag_e')
return(SQL_CMD)
def 生成_查表字段属性SQL语句(备注):
数据表名 = SV_数据库树_选中表名.get()
当前数据库类型 = SV_数据库类型.get()
if 当前数据库类型 == 'SQLite3':
SQL_CMD = f'PRAGMA table_info({数据表名})' # SQLite3 查询字段属性的命令语句
elif 当前数据库类型 == 'MySQL':
if 备注 == '主要6项':
SQL_CMD = f'SELECT TABLE_SCHEMA, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_KEY FROM information_schema.columns WHERE TABLE_NAME="{数据表名}"'
else:
SQL_CMD = f'SELECT * FROM information_schema.columns WHERE table_name="{数据表名}"' # MySQL 查询字段属性的SQL语句
else:
SQL_CMD = ''
ERROR = f'未知{当前数据库类型}类型数据库的查询表字段属性的SQL语句'
TEXT_数据库操作日志.insert(0.0, ERROR+'\n', 'tag_e')
return(SQL_CMD)
####################
## TKinter 主窗口 ##
####################
top = Tk() # 初始化Tk()
top.title('Python3 tkinter GUI 图形化操作 SQLite3/MySQL 工具') # 设置标题
窗口宽 = 900
窗口高 = 600
# 获取屏幕尺寸以计算布局参数,使窗口居屏幕中央
屏幕宽 = top.winfo_screenwidth()
屏幕高 = top.winfo_screenheight()
主窗口显示位置 = '%dx%d+%d+%d' % (窗口宽, 窗口高, (屏幕宽-窗口宽)/2, (屏幕高-窗口高)/2)
top.geometry(主窗口显示位置)
top.resizable(width=True, height=True) # 设置窗口是否可变长、宽(True:可变,False:不可变)
top.grid_columnconfigure(1, weight=1) # (第1列, 自适应窗口宽)
##################################################################################
## TKinter 主窗口布局 - TOP框 ####################################################
顶级功能菜单框 = Frame(top)
LF_数据库信息框 = LabelFrame(top, text='数据库信息框')
LF_日志框 = LabelFrame(top, text='数据库操作记录(倒序)')
常用功能框 = Frame(top)
LF_显示编辑框 = LabelFrame(top, text='数据: 显示/编辑框', bg='PaleGreen')
控表按钮框 = Frame(top) # 分页按钮/确认修改按钮
命令框 = LabelFrame(top, text='SQL语句/SQL脚本')
顶级功能菜单框.grid( row=0,column=1,sticky='NW')
LF_数据库信息框.grid(row=1,column=1,sticky='NW')
LF_日志框.grid( row=2,column=1,sticky='NSEW') # NSEW填满(自动伸缩必要设置)
常用功能框.grid( row=3,column=1,sticky='NW')
LF_显示编辑框.grid( row=4,column=1,sticky='NW')
控表按钮框.grid( row=5,column=1,sticky='NW')
命令框.grid( row=6,column=1,sticky='NSEW')
LF_日志框.grid_columnconfigure(0, weight=1) # 0列横向自动填充
LF_显示编辑框.grid_columnconfigure(0, weight=1) # 0列横向自动填充
命令框.grid_columnconfigure(0, weight=1) # 0列横向自动填充
左框 = Frame(top)
左框.grid(row=1,column=0,rowspan=6,sticky='NSEW')
Frame_数据库和表显示框 = LabelFrame(左框, text='数据库/数据表')
Frame_数据库和表显示框.grid(row=0,column=0,sticky='NW')
################################################################################################################
## Python 全局变量 ##
DB_INFO = {} ## 存储数据库信息
DB_INFO['数据库连接对象'] = '' # 保存打开的数据库连接对象,方便后续直接调用
DB_INFO['数据库游标对象'] = '' # 保存当前的数据库游标对象,方便往下翻页
DB_INFO['字段名列表'] = [] # 保持上一次成功查询的字段名
DB_INFO['最后查询语句'] = '' # 记录上次成功执行查询语句,方便在修改数据表后能自动刷新到相同页面
DB_INFO['字段宽度记录'] = {} # 记录上次显示的字段宽度信息,方便添加数据时使用相同宽度
DB_INFO['数据库列表'] = [] # 记录数据库服务器上的所有数据库
字典_查询字段_坐标_对象 = {} # KEY=控件坐标 || VAULE=控件对象 || {(控件行号,控件列号):控件对象} || { (0,0):obj }
字典_查询字段_坐标_初值 = {} # KEY=控件坐标 || VAULE=初始值 || {(控件行号,控件列号):初始值} || { (0,0):123 }
字典_查询结果_坐标_对象 = {}
字典_查询结果_坐标_初值 = {}
字典_添加记录_坐标_对象 = {}
字典_创建表_字段信息 = {}
字典_新加字段信息 = {}
字典_对象存储 = {} # {'文本编辑对象':''} 大文本编辑框用
################################
## TKinter 实时更新的全局变量 ##
SV_数据库类型 = StringVar() # 当前操作的数据库类型:SQLite3/MySQL/Oracle/SQLServer/...
SV_数据库连接对象 = StringVar() # DEBUG查看数据库连接对象转文本
SV_数据库游标对象 = StringVar() # DEBUG查看数据库游标对象转文本
SV_SQLite3_数据库路径 = StringVar() # 当前操作的SQLite3数据库全路径
DB_MySQL_CONF_PATH = StringVar() # MySQL连接配置文件路径
SV_最后查询语句 = StringVar() # 记录上一次的查询语句,用于在修改后刷新显示编辑框内容
SV_查询字段列表 = StringVar() # 查询语句查询结果的字段信息
SV_数据库列表 = StringVar() # DEBUG查看数据库列表转文本
SV_数据表列表 = StringVar() # DEBUG查看数据表列表转文本
字段框_定位列 = IntVar()
数据框_定位行 = IntVar()
数据框_定位列 = IntVar()
SV_新建数据表_库名 = StringVar()
SV_新建数据表_表名 = StringVar()
SV_新建数据表_存储引擎 = StringVar()
SV_新建数据表_字符集 = StringVar()
SV_字段宽度记录 = StringVar() # DEBUG查看字段宽度记录字典转文本
SV_自定义空值字符串表示 = StringVar() # 自定义空值字符串表示
SV_自定义空值字符串表示.set('(NULL)') # 默认设置:遇到数据库内空值时用'(NULL)'表示,方便操作
分页行数 = IntVar() # 分页 设置要读取数据的行数
分页行数.set(5) # 分页 设置以5条分页
IV_已显示记录数 = IntVar() # 分页 用于修改数据库后,再次显示在修改位置
IV_上次分页行数 = IntVar() # 分页 用于修改数据库后,再次显示在修改位置
DB_SQL_SCRIPT_FILE = StringVar() # 选择本地SQL脚本文件
IV_光标X轴 = IntVar() # 光标位置记录(当前就是看看,没有实际用途)
IV_光标Y轴 = IntVar() # 光标位置记录(当前就是看看,没有实际用途)
IV_单元格限宽 = IntVar() # 自动设置单元格宽度时的最大宽度(字符数)
IV_单元格限宽.set(20) # 设置默认最大20字符宽度
SV_提示信息_建表 = StringVar() ## 创建表/复制表提示信息
SV_数据库树_选中库名 = StringVar() # 当前选择的库名
SV_数据库树_选中库名.set('无')
SV_数据库树_选中表名 = StringVar() # 当前选择的表名
SV_数据库树_选中表名.set('无')
SV_搜索内容 = StringVar()
SV_提示信息 = StringVar()
SV_登录帐号 = StringVar()
SV_登录密码 = StringVar()
SV_登录地址 = StringVar()
IV_服务端口 = IntVar()
SV_登录库名 = StringVar()
SV_字符集 = StringVar()
################################################################################################################
## 全局变量 DEBUG 显示
def DEF_显示DEBUG小窗():
新窗口 = Toplevel()
新窗口.title('DEBUG')
显示坐标 = '+600+300'
新窗口.geometry(显示坐标)
新窗口.grid_columnconfigure(0, weight=1) # (第0列, 自适应窗口宽)
新窗口.grid_rowconfigure(0, weight=1) # (第0列, 自适应窗口高)
LabelFrame_DEBUG框 = LabelFrame(新窗口, text='Debug 全局变量', bg='#FFD700')
LabelFrame_DEBUG框.grid(row=0,column=0,sticky='NSEW')
LabelFrame_DEBUG框.grid_columnconfigure(0, weight=1)
LabelFrame_DEBUG框.grid_rowconfigure(0, weight=1)
DEBUG画布 = Canvas(LabelFrame_DEBUG框, bg='#00CED1') # 创建画布
## 在画布里创建 Frame
DEBUG画布Frame框 = Frame(DEBUG画布)
全局变量显示宽 = 85
全局变量展示框 = Frame(DEBUG画布Frame框)
全局变量展示框.grid(row=0,column=0,sticky='NW')
Label(全局变量展示框, text='[数据库类型]').grid( row=0,column=0,sticky='W')
Label(全局变量展示框, text='[数据库连接对象]').grid( row=1,column=0,sticky='W')
Label(全局变量展示框, text='[数据库游标对象]').grid( row=2,column=0,sticky='W')
Label(全局变量展示框, text='[数据库树_选中库名]').grid( row=3,column=0,sticky='W')
Label(全局变量展示框, text='[数据库树_选中表名]').grid( row=4,column=0,sticky='W')
Label(全局变量展示框, text='[查询字段列表]').grid( row=5,column=0,sticky='W')
Label(全局变量展示框, text='[字段宽度记录]').grid( row=6,column=0,sticky='W')
Label(全局变量展示框, text='[最后查询语句]').grid( row=7,column=0,sticky='W')
Label(全局变量展示框, text='[数据库列表]').grid( row=8,column=0,sticky='W')
Label(全局变量展示框, text='[数据表列表]').grid( row=9,column=0,sticky='W')
Label(全局变量展示框, text='[新建数据表_库名]').grid( row=10,column=0,sticky='W')
Label(全局变量展示框, text='[新建数据表_表名]').grid( row=11,column=0,sticky='W')
Label(全局变量展示框, text='[新建数据表_存储引擎]').grid( row=12,column=0,sticky='W')
Label(全局变量展示框, text='[新建数据表_字符集]').grid( row=13,column=0,sticky='W')
Label(全局变量展示框, text='[显编框.字段框定位 列]').grid(row=15,column=0,sticky='W')
Label(全局变量展示框, text='[显编框.数据框定位 行]').grid(row=16,column=0,sticky='W')
Label(全局变量展示框, text='[显编框.数据框定位 列]').grid(row=17,column=0,sticky='W')
Label(全局变量展示框, text='[IV_光标X轴]').grid( row=18,column=0,sticky='W')
Label(全局变量展示框, text='[IV_光标Y轴]').grid( row=19,column=0,sticky='W')
Label(全局变量展示框, text='[IV_已显示记录数]').grid( row=20,column=0,sticky='W')
Label(全局变量展示框, text='[IV_上次分页行数]').grid( row=21,column=0,sticky='W')
Label(全局变量展示框, text='[分页行数]').grid( row=22,column=0,sticky='W')
Label(全局变量展示框, text='[单元格限宽]').grid( row=23,column=0,sticky='W')
Label(全局变量展示框, text='[SQLite3数据库路径]').grid( row=24,column=0,sticky='W')
Entry(全局变量展示框, textvariable=SV_数据库类型, width=全局变量显示宽).grid(row=0,column=1,sticky='W')
Entry(全局变量展示框, textvariable=SV_数据库连接对象,width=全局变量显示宽).grid(row=1,column=1,sticky='W')
Entry(全局变量展示框, textvariable=SV_数据库游标对象,width=全局变量显示宽).grid(row=2,column=1,sticky='W')
Entry(全局变量展示框, textvariable=SV_数据库树_选中库名, width=全局变量显示宽).grid(row=3,column=1,sticky='W')
Entry(全局变量展示框, textvariable=SV_数据库树_选中表名, width=全局变量显示宽).grid(row=4,column=1,sticky='W')
Entry(全局变量展示框, textvariable=SV_查询字段列表, width=全局变量显示宽).grid(row=5,column=1,sticky='W')
Entry(全局变量展示框, textvariable=SV_字段宽度记录, width=全局变量显示宽).grid(row=6,column=1,sticky='W')
Entry(全局变量展示框, textvariable=SV_最后查询语句, width=全局变量显示宽).grid(row=7,column=1,sticky='W')
Entry(全局变量展示框, textvariable=SV_数据库列表, width=全局变量显示宽).grid(row=8,column=1,sticky='W')
Entry(全局变量展示框, textvariable=SV_数据表列表, width=全局变量显示宽).grid(row=9,column=1,sticky='W')
Entry(全局变量展示框, textvariable=SV_新建数据表_库名, width=全局变量显示宽).grid(row=10,column=1,sticky='W')
Entry(全局变量展示框, textvariable=SV_新建数据表_表名, width=全局变量显示宽).grid(row=11,column=1,sticky='W')
Entry(全局变量展示框, textvariable=SV_新建数据表_存储引擎,width=全局变量显示宽).grid(row=12,column=1,sticky='W')
Entry(全局变量展示框, textvariable=SV_新建数据表_字符集, width=全局变量显示宽).grid(row=13,column=1,sticky='W')
Entry(全局变量展示框, textvariable=字段框_定位列, width=全局变量显示宽).grid(row=15,column=1,sticky='W')
Entry(全局变量展示框, textvariable=数据框_定位行, width=全局变量显示宽).grid(row=16,column=1,sticky='W')
Entry(全局变量展示框, textvariable=数据框_定位列, width=全局变量显示宽).grid(row=17,column=1,sticky='W')
Entry(全局变量展示框, textvariable=IV_光标X轴, width=全局变量显示宽).grid(row=18,column=1,sticky='W',columnspan=3)
Entry(全局变量展示框, textvariable=IV_光标Y轴, width=全局变量显示宽).grid(row=19,column=1,sticky='W',columnspan=3)
Entry(全局变量展示框, textvariable=IV_已显示记录数, width=全局变量显示宽).grid(row=20,column=1,sticky='W')
Entry(全局变量展示框, textvariable=IV_上次分页行数, width=全局变量显示宽).grid(row=21,column=1,sticky='W')
Entry(全局变量展示框, textvariable=分页行数, width=全局变量显示宽).grid(row=22,column=1,sticky='W')
Entry(全局变量展示框, textvariable=IV_单元格限宽, width=全局变量显示宽).grid(row=23,column=1,sticky='W')
Entry(全局变量展示框, textvariable=SV_SQLite3_数据库路径, width=全局变量显示宽).grid(row=24,column=1,sticky='W')
## 动态设置画布窗口宽高:根据主主窗口的参数设置限宽限高
##主窗口大小和位置 = top.geometry()
##主窗口宽, 主窗口高, 主窗口X, 主窗口Y = re.findall('[0-9]+', 主窗口大小和位置)
总行数 = 27 # 画布最大显示行数
画布滚动最右边 = 750
画布滚动最下边 = 21*总行数 # Entry 默认高度为 21像素(20像素+1分隔像素)
DEBUG画布['scrollregion'] = (0,0,画布滚动最右边,画布滚动最下边) # 一个元组 tuple (w, n, e, s) ,定义了画布可滚动的最大区域,w 为左边,n 为头部,e 为右边,s 为底部
DEBUG画布['width'] = 画布滚动最右边 # 使用实际需要的宽度即可
DEBUG画布['height'] = min(800, 画布滚动最下边) # 取最小值,防止纵向撑爆
Scrollbar_画布_竖 = Scrollbar(LabelFrame_DEBUG框, command=DEBUG画布.yview) # 创建竖滚动条
Scrollbar_画布_竖.grid(row=0,column=1,sticky=S+W+E+N) # 竖滚动条定位
Scrollbar_画布_横 = Scrollbar(LabelFrame_DEBUG框, command=DEBUG画布.xview, orient=HORIZONTAL) # 创建横滚动条
Scrollbar_画布_横.grid(row=1,column=0,sticky=S+W+E+N) # 横滚动条定位
DEBUG画布.config(xscrollcommand=Scrollbar_画布_横.set, yscrollcommand=Scrollbar_画布_竖.set) # 自动设置滚动幅度
DEBUG画布.create_window((0,0), window=DEBUG画布Frame框, anchor='nw') # 显示画布内 DEBUG画布Frame框 控件
#DEBUG画布.grid(row=0,column=0,sticky='nw') # 显示画布,不显示画布多余部分
DEBUG画布.grid(row=0,column=0,sticky='nsew') # 显示画布,画布填满空间
Button_DEBUG小窗 = Button(top, text='DEBUG小窗', width=30, command=DEF_显示DEBUG小窗)
Button_DEBUG小窗.grid(row=0,column=0,sticky='E')
########################################################################################################
## 右键菜单 ###########################################################################################
###########################
## 创建新窗口 添加新记录 ##
def DEF_新增记录():
L_字段名,L_主键 = DEF_查表字段属性返回字段名及是否主键()
if L_字段名 != []:
DEF_弹出新加记录窗口(L_字段名)
else:
tkinter.messagebox.showerror(title='ERROR', message=查询结果)
def DEF_新增记录_填入模板值():
L_字段名,L_主键 = DEF_查表字段属性返回字段名及是否主键()
if L_字段名 != []:
D_新增数据复制值模板 = {}
选择数据行号 = 数据框_定位行.get()
列数量 = len(字典_查询字段_坐标_初值)
for 列号 in range(0, 列数量):
D_新增数据复制值模板[字典_查询字段_坐标_初值[(0,列号)]] = 字典_查询结果_坐标_初值[(选择数据行号,列号)]
DEF_弹出新加记录窗口(L_字段名,D_新增数据复制值模板)
else:
tkinter.messagebox.showerror(title='ERROR', message=查询结果)
def 新加记录窗口_确定(窗口对象):
Log.debug("新加记录窗口_确定")
SQL_库名表名 = 生成_库名表名限定SQL语句部分()
if SQL_库名表名 != '':
## 查找有值的字段,拼成 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_库名表名} ({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_弹出新加记录窗口(字段名列表, D_新增数据复制值模板={}):
新窗口 = Toplevel()
新窗口.title('添加新记录')
## 新窗口布局
显编框 = Frame(新窗口)
按钮框 = Frame(新窗口)
显编框.grid(row=0,column=0,sticky='NWES')
按钮框.grid(row=1,column=0)
## 宽高参数
行数 = 2
列数 = len(字段名列表)
## 记录字段中每个字段需要的Entry宽值
D_已知字段宽度 = DB_INFO['字段宽度记录']
L_字段需要宽值 = [D_已知字段宽度[i] if i in D_已知字段宽度 else 计算字符串像素宽返回Entry标准宽(i) for i in 字段名列表]
Log.debug(f"字段名列表={字段名列表} D_已知字段宽度={D_已知字段宽度} 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')
## 设置画布参数
总行数 = 行数
## 画布可滚动显示的最大宽和高(要刚好能放下画布里的Frame里的全部控件)
画布滚动最右边 = sum(L_字段需要宽值)*7 + 列数*4 # 总字符数量x9像素+每个单元格需要初始4像素
画布滚动最下边 = 21*总行数 # 20*行数 + 行数*1
画布['scrollregion'] = (0,0,画布滚动最右边,画布滚动最下边) # 一个元组 tuple (w, n, e, s) ,定义了画布可滚动的最大区域,w 为左边,n 为头部,e 为右边,s 为底部
## 动态设置显示画布固定显示宽和高(要和主显示框的大小匹配)
画布['width'] = 画布滚动最右边 # 使用实际需要的宽度即可
画布['height'] = 画布滚动最下边
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()
# 第1行是字段行,行序号固定为0
for 列 in range(0, 列数):
初始值 = str(字段名列表[列])
字典_添加记录_坐标_对象[(0,列)] = Entry(字段框, width=L_字段需要宽值[列])
字典_添加记录_坐标_对象[(0,列)].insert(0, 初始值)
字典_添加记录_坐标_对象[(0,列)].grid(row=0,column=列,sticky='W')
字典_添加记录_坐标_对象[(0,列)]['state'] = 'readonly' # 设置为只读,用户不能修改
# 第2行是数据行,行序号固定为1
for 列 in range(0, 列数):
字典_添加记录_坐标_对象[(1,列)] = Entry(数据框, width=L_字段需要宽值[列])
字典_添加记录_坐标_对象[(1,列)].grid(row=1,column=列,sticky='W')
字段名 = 字段名列表[列]
if 字段名 in D_新增数据复制值模板:
初始值 = D_新增数据复制值模板[字段名]
字典_添加记录_坐标_对象[(1,列)].insert(0, 初始值)
## 按钮框
确定按钮 = Button(按钮框, text='确定', command=lambda 窗口对象=新窗口:新加记录窗口_确定(窗口对象))
确定按钮.grid(row=1,column=0)
取消按钮 = Button(按钮框, text='取消', command=lambda 窗口对象=新窗口:新加记录窗口_取消(窗口对象))
取消按钮.grid(row=1,column=1)
显示坐标 = '+300+200'
新窗口.geometry(显示坐标)
显编框.grid_columnconfigure(0, weight=1)
新窗口.grid_columnconfigure(0, weight=1)
## 创建新窗口 添加新记录 ##
###########################
## 数据导出为CSV文件
def CSV写入本地(文件名, L_字段名, LL_数据):
with open(文件名, mode='w', encoding='utf-8', newline='') as f: # 以写方式打开文件。注意添加 newline='',否则会在两行数据之间都插入一行空白。
CSV_W = csv.writer(f)
CSV_W.writerow(L_字段名) # 写入标题行,writerow() 一次只能写入一行
CSV_W.writerows(LL_数据) # 写入数据,writerows() 一次写入多行
INFO = f'导出数据"{文件名}"成功'
tkinter.messagebox.showinfo(title='INFO', message=INFO)
def 数据表导出CSV():
SQL_库名表名 = 生成_库名表名限定SQL语句部分()
if SQL_库名表名 != '':
默认导出文件名 = f'{SQL_库名表名}.csv'
导出文件名 = tkinter.simpledialog.askstring(title='导出文件名(重名会覆盖)', prompt='请输入导出文件名:', initialvalue=默认导出文件名)
## 确定为输入内容,取消为None
if 导出文件名 != None:
SQL_CMD = f'SELECT * FROM {SQL_库名表名}'
R = DEF_SQL_查询和返回_数据列表_字段列表(SQL_CMD)
if R[0] == 0:
LL_数据 = R[1]
L_字段名 = R[2]
CSV写入本地(导出文件名, L_字段名, LL_数据)
else:
tkinter.messagebox.showerror(title='ERROR', message=R[1])
def DEF_右键菜单_此页数据导出为CSV文件():
if LF_显示编辑框.winfo_children() == []: # 当显示编辑框内组件不存在
ERROR = '无法导出数据:显编框内无数据'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
默认导出文件名 = '导出部分数据.csv'
导出文件名 = tkinter.simpledialog.askstring(title='导出文件名(重名会覆盖)', prompt='请输入导出文件名:', initialvalue=默认导出文件名)
## 确定为输入内容,取消为None
if 导出文件名 != None:
L_字段名 = [字典_查询字段_坐标_初值[K] for K in 字典_查询字段_坐标_初值]
列数 = len(L_字段名)
N = 0
LL_数据 = []
列表_数据信息 = []
for K in 字典_查询结果_坐标_初值:
N += 1
列表_数据信息.append(字典_查询结果_坐标_初值[K])
if N%列数==0:
LL_数据.append(列表_数据信息)
列表_数据信息 = []
N = 0
CSV写入本地(导出文件名, L_字段名, LL_数据)
########################################################################################################
## 右键菜单 字段框 #####################################################################################
#########################
## 创建新窗口 修改字段 ##
字典_修改字段信息对象 = {} #
字典_原字段信息 = {} # {'COLUMN_DEFAULT':'', 'IS_NULLABLE':'', 'COLUMN_TYPE':'', 'COLUMN_KEY':''}
def 修改字段窗口_确定(窗口对象):
数据表名 = SV_数据库树_选中表名.get()
SQL_库名表名 = 生成_库名表名限定SQL语句部分()
if SQL_库名表名 != '':
if 数据表名 == '':
print("没有表名")
else:
## 提取缓存的字段原属性
原字段名 = 字典_原字段信息['COLUMN_NAME']
原默认值 = 字典_原字段信息['COLUMN_DEFAULT']
原是否可空 = 字典_原字段信息['IS_NULLABLE']
原字段类型 = 字典_原字段信息['COLUMN_TYPE']
原是否主键 = 字典_原字段信息['COLUMN_KEY']
字段对象列表 = 字典_修改字段信息对象['0']
新字段名 = 字段对象列表[0].get()
新字段类型 = 字段对象列表[1].get()
新是否可空 = 字段对象列表[2].get()
新默认值 = 字段对象列表[3].get()
新是否主键 = 字段对象列表[4].get()
if 原字段名==新字段名 and 原默认值==新默认值 and 原是否可空==新是否可空 and 原字段类型==新字段类型 and 原是否主键==新是否主键:
print("没有改动,忽略")
窗口对象.withdraw() # 关闭编辑窗口
else:
print("有改动")
if 新默认值 != '':
if 新默认值 == 'auto_increment':
新默认值 = 'auto_increment'
else:
新默认值 = f'DEFAULT {新默认值}'
## 判断主键情况
if 原是否主键 == '是':
if 新是否主键 == '否':
主键标识 = '不自动操作' # 原是新否,删除主键还是手动命令操作吧 ALTER TABLE 表名 DROP PRIMARY KEY
else:
主键标识 = '' # 原是新是,忽略
else:
if 新是否主键 == '否':
主键标识 = '' # 原否新否,忽略
else:
主键标识 = 'PRIMARY KEY' # 原否新是,尝试设置主键
if 主键标识 == '不自动操作':
WARNING = '删除主键还是手动命令操作吧'
tkinter.messagebox.showwarning(title='WARNING', message=WARNING)
else:
if 新字段名 == 原字段名:
## 修改字段属性的SQL语句
## ALTER TABLE 表名 MODIFY 字段名称 字段类型 [完整性约束条件]
SQL_CMD = f'ALTER TABLE {SQL_库名表名} MODIFY {原字段名} {新字段类型} {新是否可空} {新默认值} {主键标识}'
else:
## 修改字段名和字段属性
## ALTER TABLE 表名 CHANGE 原字段名 新字段名 字段类型 约束条件
SQL_CMD = f'ALTER TABLE {SQL_库名表名} CHANGE {原字段名} {新字段名} {新字段类型} {新是否可空} {新默认值} {主键标识}'
print("SQL_CMD", SQL_CMD)
R = DEF_SQL_执行(SQL_CMD)
print(R)
if R[0] == 0: # 创建新字段全部成功
窗口对象.withdraw() # 关闭编辑窗口
执行上条查询语句_刷新显示('已翻页位置开始') # 成功后,更新显示表格
else:
ERROR = R[1]
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
def 修改字段窗口_取消(窗口对象):
窗口对象.withdraw()
def DEF_弹出修改字段窗口():
字段索引 = 字段框_定位列.get()
原字段名 = 字典_查询字段_坐标_初值[(0,字段索引)]
## 查询数据库,取原字段信息
数据表名 = SV_数据库树_选中表名.get()
SQL_CMD = f'SELECT COLUMN_NAME, COLUMN_DEFAULT, IS_NULLABLE, COLUMN_TYPE, COLUMN_KEY FROM information_schema.columns WHERE table_name="{数据表名}" AND COLUMN_NAME="{原字段名}"'
R = DEF_SQL_查询和返回(SQL_CMD)
if R[0] == 0:
查询结果 = R[1]
print("查询成功,查询结果R[1]", 查询结果) # 有值 (('A', None, 'NO', 'int(11)', 'PRI'),) 无值 ()
if 查询结果 == ():
ERROR = '字段属性查询结果为空,无法编辑'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
## 获取字段原属性
COLUMN_NAME, COLUMN_DEFAULT, IS_NULLABLE, COLUMN_TYPE, COLUMN_KEY = 查询结果[0]
## 缓存字段原属性
字典_原字段信息['COLUMN_NAME'] = COLUMN_NAME
字典_原字段信息['COLUMN_DEFAULT'] = COLUMN_DEFAULT
if IS_NULLABLE == 'YES':
字典_原字段信息['IS_NULLABLE'] = 'NULL'
else:
字典_原字段信息['IS_NULLABLE'] = 'NOT NULL'
字典_原字段信息['COLUMN_TYPE'] = COLUMN_TYPE
if COLUMN_KEY == 'PRI':
字典_原字段信息['COLUMN_KEY'] = '是'
else:
字典_原字段信息['COLUMN_KEY'] = '否'
字典_修改字段信息对象.clear()
新窗口 = Toplevel()
新窗口.title('修改数据字段窗口')
显示坐标 = f'+{屏幕宽//2-300}+{屏幕高//2-100}'
新窗口.geometry(显示坐标)
表名框 = Frame(新窗口)
标题框 = Frame(新窗口)
数据框 = Frame(新窗口)
按钮框 = Frame(新窗口)
表名框.grid(row=0,column=0,sticky='NW')
标题框.grid(row=1,column=0,sticky='NW')
数据框.grid(row=2,column=0,sticky='NW')
按钮框.grid(row=3,column=0)
## 标题框:固定不变的5个Entry,提示每列含义
字段 = Entry(标题框)
字段.grid(row=0,column=0,sticky='NW') #00
字段.insert(0, '列名(字段名)')
字段['state'] = 'readonly'
类型 = Entry(标题框, width=18)
类型.grid(row=0,column=1,sticky='NW') #01
类型.insert(0, '类型')
类型['state'] = 'readonly'
空 = Entry(标题框, width=12)
空.grid(row=0,column=2,sticky='NW') #02
空.insert(0, '是否允许空')
空['state'] = 'readonly'
默认值 = Entry(标题框, width=23)
默认值.grid(row=0,column=3,sticky='NW') #03
默认值.insert(0, '默认值')
默认值['state'] = 'readonly'
主键 = Entry(标题框, width=18)
主键.grid(row=0,column=4,sticky='NW') #04
主键.insert(0, '主键标识')
主键['state'] = 'readonly'
## 数据框:查询数据库并填入原值,方便用户后续修改
Entry_字段名 = Entry(数据框)
Entry_字段名.insert(0, 原字段名)
Combobox_字段类型 = ttk.Combobox(数据框, width=15)
Combobox_字段类型['value'] = ('INTEGER','CHAR()','VARCHAR()','TEXT','DATE','TIME','TINYINT','SMALLINT','MEDIUMINT','BIGINT','FLOAT','DOUBLE','DECIMAL','YEAR','DATETIME','TIMESTAMP','TINYBLOB','TINYTEXT','BLOB','MEDIUMBLOB','MEDIUMTEXT','LONGBLOB','LONGTEXT')
Combobox_字段类型.set(COLUMN_TYPE)
Combobox_是否可空 = ttk.Combobox(数据框, width=10)
Combobox_是否可空['value'] = ('NULL', 'NOT NULL')
if IS_NULLABLE == 'YES':
Combobox_是否可空.current(0) # 设置默认选择值,默认值中的内容为索引,从0开始
else:
Combobox_是否可空.current(1)
Combobox_默认值 = ttk.Combobox(数据框)
Combobox_默认值['value'] = ('auto_increment', 'CURRENT_TIMESTAMP')
if COLUMN_DEFAULT == None:
pass
else:
Combobox_默认值.set(COLUMN_DEFAULT)
Combobox_是否主键 = ttk.Combobox(数据框, width=15)
Combobox_是否主键['value'] = ('是', '否')
if COLUMN_KEY == 'PRI':
Combobox_是否主键.current(0)
else:
Combobox_是否主键.current(1)
字典_修改字段信息对象['0'] = [Entry_字段名, Combobox_字段类型, Combobox_是否可空, Combobox_默认值, Combobox_是否主键]
Entry_字段名.grid(row=1,column=0,sticky='NW')
Combobox_字段类型.grid(row=1,column=1,sticky='NW')
Combobox_是否可空.grid(row=1,column=2,sticky='NW')
Combobox_默认值.grid(row=1,column=3,sticky='NW')
Combobox_是否主键.grid(row=1,column=4,sticky='NW')
## 按钮框
确定按钮 = Button(按钮框, text='确定', command=lambda 窗口对象=新窗口:修改字段窗口_确定(窗口对象))
确定按钮.grid(row=1,column=0)
取消按钮 = Button(按钮框, text='取消', command=lambda 窗口对象=新窗口:修改字段窗口_取消(窗口对象))
取消按钮.grid(row=1,column=1)
else:
ERROR = f'数据表"{数据表名}"的字段"{原字段名}"异常: {R[1]}'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
## 创建新窗口 修改字段 ##
#########################
def DEF_鼠标事件_右键菜单_字段单元(event):
选中控件 = event.widget
行 = 0 # 字段名只有1行,恒等于0
列 = 选中控件.grid_info()['column']
字段框_定位列.set(列)
## 弹出菜单
字段框_右键菜单.post(event.x_root, event.y_root) # 光标位置显示菜单
def DEF_右键菜单_删除列():
当前数据库类型 = SV_数据库类型.get()
if 当前数据库类型 == 'SQLite3':
tkinter.messagebox.showinfo(title='提示', message='SQLite3 没有这个功能,可使用新建表替换旧表')
else:
## ALTER TABLE 库名.表名 DROP COLUMN 列名
SQL_库名表名 = 生成_库名表名限定SQL语句部分()
if SQL_库名表名 != '':
字段显示列号 = 字段框_定位列.get()
字段名 = 字典_查询字段_坐标_初值[(0,字段显示列号)]
用户决定 = tkinter.messagebox.askquestion(title='请三思...', message='是否确定删除字段: '+字段名) # 返回值为:yes/no
if 用户决定 == 'yes':
SQL_CMD = f'ALTER TABLE {SQL_库名表名} DROP COLUMN {字段名}' # 删除字段的SQL语句
R = DEF_SQL_执行(SQL_CMD)
if R[0] == 0:
执行上条查询语句_刷新显示('已翻页位置开始') # 重新查询数据库表,更新显编框内容
else:
tkinter.messagebox.showerror(title='ERROR', message=R[1])
else:
Log.debug(f"取消删除字段 {字段名}")
def DEF_右键菜单_编辑列():
当前数据库类型 = SV_数据库类型.get()
if 当前数据库类型 == 'SQLite3':
tkinter.messagebox.showinfo(title='提示', message='SQLite3 没有这个功能,可使用新建表替换旧表')
elif 当前数据库类型 == 'MySQL':
DEF_弹出修改字段窗口()
else:
ERROR = f'{当前数据库类型} 还不会操作...'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
def DEF_右键菜单_列值精确匹配():
SQL_库名表名 = 生成_库名表名限定SQL语句部分()
if SQL_库名表名 != '':
字段列号 = 字段框_定位列.get()
字段名 = 字典_查询字段_坐标_初值[(0,字段列号)]
if SV_数据库类型 == 'SQLite3':
SQL = f'SELECT * FROM {SQL_库名表名} WHERE {字段名} IS "匹配内容"'
else:
SQL = f'SELECT * FROM {SQL_库名表名} WHERE {字段名} = "匹配内容"'
文本框_命令行.insert(0.0, SQL+'\n')
def DEF_右键菜单_列值模糊匹配():
SQL_库名表名 = 生成_库名表名限定SQL语句部分()
if SQL_库名表名 != '':
字段列号 = 字段框_定位列.get()
字段名 = 字典_查询字段_坐标_初值[(0,字段列号)]
SQL = f'SELECT * FROM {SQL_库名表名} WHERE {字段名} LIKE "%匹配内容%"'
文本框_命令行.insert(0.0, SQL+'\n')
def DEF_右键菜单_列值替换():
SQL_库名表名 = 生成_库名表名限定SQL语句部分()
if SQL_库名表名 != '':
字段列号 = 字段框_定位列.get()
字段名 = 字典_查询字段_坐标_初值[(0,字段列号)]
SQL = f'UPDATE {SQL_库名表名} SET {字段名}="新值" WHERE {字段名}="原值";'
文本框_命令行.insert(0.0, SQL+'\n')
def DEF_右键菜单_统计本列出现次数():
SQL_库名表名 = 生成_库名表名限定SQL语句部分()
if SQL_库名表名 != '':
字段列号 = 字段框_定位列.get()
字段名 = 字典_查询字段_坐标_初值[(0,字段列号)]
SQL = f'SELECT {字段名},COUNT({字段名}) AS 出现次数 FROM {SQL_库名表名} GROUP BY {字段名} ORDER BY 出现次数 DESC'
DEF_SQL_查询和显示(SQL)
def 显示字段信息():
SQL_CMD = 生成_查表字段属性SQL语句('全属性展示')
if SQL_CMD != '':
DEF_SQL_查询和显示(SQL_CMD)
#########################################
## 右键菜单 字段框 新加字段 创建新窗口 ##
def 新加字段窗口_确定(窗口对象):
数据表名 = SV_数据库树_选中表名.get()
if 数据表名 == '':
print("没有表名")
else:
SQL_库名表名 = 生成_库名表名限定SQL语句部分()
if SQL_库名表名 != '':
## 字段名不能为空,不能重复
错误标记 = 0
测试字段名列表 = []
for K in 字典_新加字段信息:
测试字段名 = 字典_新加字段信息[K][2].get().strip()
if 测试字段名 == '':
错误标记 = 1
print("含有空字段名")
break
else:
测试字段名列表.append(测试字段名)
if len(测试字段名列表) != len(set(测试字段名列表)):
错误标记 = 1
print("有重复字段名")
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 {SQL_库名表名} ADD {字段名} {字段类型} {是否可空} {默认值} {主键标识}'
L_SQL_新增字段.append(SQL_新增字段)
L_成功信息, L_失败信息 = DEF_SQL_执行多条_忽略错误语句(L_SQL_新增字段)
if L_成功信息 != []: # 创建新字段有成功的
窗口对象.withdraw() # 关闭编辑窗口
执行上条查询语句_刷新显示('从头开始')
elif L_失败信息 != []:
ERROR = '执行信息\n'
for i in L_成功信息 + L_失败信息:
ERROR += i+'\n'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
def 新加字段窗口_取消(窗口对象):
窗口对象.withdraw()
def 新加字段窗口_增加字段(显示框):
行号列表 = [i for i in 字典_新加字段信息]
if 行号列表 == []:
新行号 = 0
else:
最大行号 = max(行号列表)
新行号 = 最大行号 + 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=SV_数据库树_选中表名.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(tearoff=False)
字段框_右键菜单.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_separator()
字段框_右键菜单.add_command(label='添加列(添加字段)', command=DEF_弹出新加字段窗口) ## 菜单按钮函数:添加列(添加字段)
字段框_右键菜单.add_command(label='删除列(删除字段)', command=DEF_右键菜单_删除列) ## 菜单按钮函数:删除列(删除字段)
字段框_右键菜单.add_command(label='编辑列(修改字段)', command=DEF_右键菜单_编辑列) ## 菜单按钮函数:编辑列(修改字段)
########################################################################################################
## 右键菜单 数据框 #####################################################################################
###########################
## 创建新窗口 大文本编辑 ##
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 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 DEF_鼠标事件_左键单击(event):
选中控件 = event.widget
行 = 选中控件.grid_info()['row']
列 = 选中控件.grid_info()['column']
#选中控件['bg'] = '#7FFF00'
#字典_查询结果_坐标_对象[(行,列)].bind('', DEF_鼠标事件_离开) # 单击是进入编辑,给这个控件加个离开事件
数据框_定位行.set(行)
数据框_定位列.set(列)
def DEF_鼠标事件_右键菜单_数据单元(event):
选中控件 = event.widget
行 = 选中控件.grid_info()['row']
列 = 选中控件.grid_info()['column']
数据框_定位行.set(行)
数据框_定位列.set(列)
## 右键选择的控件获得焦点
单元格 = 字典_查询结果_坐标_对象[(行,列)]
单元格.focus_set() # 焦点移到单元格
## 弹出菜单
光标X轴 = event.x_root
光标Y轴 = event.y_root
IV_光标X轴.set(光标X轴) # (当前就是看看,没有实际用途)
IV_光标Y轴.set(光标Y轴) # (当前就是看看,没有实际用途)
数据框_右键菜单.post(光标X轴, 光标Y轴) # 光标位置显示菜单
def DEF_鼠标事件_光标悬停(event):
控件 = event.widget
行号 = 控件.grid_info()['row']
列号 = 控件.grid_info()['column']
print(f"DEF_鼠标事件_光标悬停 {行号,列号}") # 怎么实现显示提示?
def DEF_删除记录():
控件行号 = 数据框_定位行.get()
SQL_库名表名 = 生成_库名表名限定SQL语句部分()
if SQL_库名表名 != '':
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_库名表名} {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(tearoff=False)
数据框_右键菜单.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='还原旧值', command=DEF_按钮_还原旧值)
############################################
## TKinter 主窗口布局 - TOP框 - LF_日志框 ##
TEXT_数据库操作日志 = Text(LF_日志框, height=3, wrap='none') # 显示改动了数据库的操作日志
Scrollbar_日志框_竖 = Scrollbar(LF_日志框)
Scrollbar_日志框_竖['command'] = TEXT_数据库操作日志.yview
Scrollbar_日志框_横 = Scrollbar(LF_日志框)
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框 - 常用功能框 ##
def DEF_按钮_表内全字段搜索(): # 常用功能:表内全字段搜索 SELECT * FROM 库名.表名 WHERE 列名1 LIKE "%查找内容%" or 列名2 LIKE "%查找内容%";
数据表名 = SV_数据库树_选中表名.get()
if 数据表名 == '':
ERROR = '请先打开一个表'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
SQL_库名表名 = 生成_库名表名限定SQL语句部分()
if SQL_库名表名 != '':
L_字段名,L_主键 = DEF_查表字段属性返回字段名及是否主键()
if L_字段名 != []:
查找内容 = SV_搜索内容.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 {SQL_库名表名} WHERE {SELECT_LIKE_TEXT}'
DEF_SQL_查询和显示(SQL_CMD_SELECT_LIKE)
else:
tkinter.messagebox.showerror(title='ERROR', message='查询字段名返回空')
Label(常用功能框, text='[库名]:').grid(row=0, column=0)
Label(常用功能框, textvariable=SV_数据库树_选中库名, fg='DarkOrange').grid(row=0, column=1)
Label(常用功能框, text='[表名]:').grid(row=0, column=2)
Label(常用功能框, textvariable=SV_数据库树_选中表名, fg='DarkOrange').grid(row=0, column=3)
Label(常用功能框, text=' 搜索内容:').grid(row=0, column=4)
Entry(常用功能框, textvariable=SV_搜索内容, width=15).grid(row=0,column=5)
Button(常用功能框, text='搜索', command=DEF_按钮_表内全字段搜索).grid(row=0, column=6)
###########################
## 创建新窗口 新建数据表 ##
def 键盘任意输入事件_判断表名(event):
当前控件 = event.widget
现值 = 当前控件.get()
if 现值 == '':
SV_提示信息_建表.set('请输入表名')
else:
SV_提示信息_建表.set('')
def 创建数据表窗口_确定(窗口对象):
新建数据表_表名 = SV_新建数据表_表名.get()
if 新建数据表_表名 == '':
SV_提示信息_建表.set('!!!请输入表名!!!')
else:
if SV_数据库类型.get() == 'SQLite3':
SQL_库名表名 = 新建数据表_表名
自增主键标记 = 0
L_字段信息 = []
L_主键 = []
for 字段编号 in 字典_创建表_字段信息:
字段对象列表 = 字典_创建表_字段信息[字段编号]
字段名 = 字段对象列表[2].get()
字段类型 = 字段对象列表[3].get()
是否可空 = 字段对象列表[4].get()
默认值 = 字段对象列表[5].get()
是否主键 = 字段对象列表[6].get()
if 是否主键 == '是(SQLite3自增数)':
自增主键标记 = 1
SQLite3自增主键固定格式 = f'{字段名} INTEGER PRIMARY KEY AUTOINCREMENT'
L_主键.append(SQLite3自增主键固定格式)
L_字段信息.append(SQLite3自增主键固定格式)
else:
if 是否主键 == '是':
L_主键.append(字段名)
if 默认值 == '' or 默认值 == 'auto_increment':
pass # 不改变
else:
默认值 = 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 {SQL_库名表名} ({','.join(L_字段信息)});"
elif len(L_主键) > 0 and 自增主键标记 == 0:
SQL_CMD = f"CREATE TABLE {SQL_库名表名} ({','.join(L_字段信息)}, primary key ({','.join(L_主键)}));"
else:
SQL_CMD = ''
SV_提示信息_建表.set('!!!(多主键且含自增) 我拼不出来,自己写吧!!!')
elif SV_数据库类型.get() == 'MySQL':
SQL_库名表名 = f"{SV_新建数据表_库名.get()}.{新建数据表_表名}"
新建数据表_存储引擎 = SV_新建数据表_存储引擎.get()
新建数据表_字符集 = SV_新建数据表_字符集.get()
L_字段信息 = []
L_主键 = []
for 字段编号 in 字典_创建表_字段信息:
字段对象列表 = 字典_创建表_字段信息[字段编号]
字段名 = 字段对象列表[2].get()
字段类型 = 字段对象列表[3].get()
是否可空 = 字段对象列表[4].get()
默认值 = 字段对象列表[5].get()
是否主键 = 字段对象列表[6].get()
if 是否主键 == '是':
L_主键.append(字段名)
if 默认值 == '' or 默认值 == 'auto_increment':
pass # 不改变
else:
默认值 = f'DEFAULT {默认值}' # 加关键字
字段信息 = f'{字段名} {字段类型} {是否可空} {默认值}'
L_字段信息.append(字段信息)
Log.debug(f'新建表 L_字段信息={L_字段信息}')
if len(L_主键) == 0:
SQL_CMD = f"CREATE TABLE {SQL_库名表名} ({','.join(L_字段信息)}) ENGINE={新建数据表_存储引擎} DEFAULT CHARSET={新建数据表_字符集};"
else:
SQL_CMD = f"CREATE TABLE {SQL_库名表名} ({','.join(L_字段信息)}, primary key ({','.join(L_主键)})) ENGINE={新建数据表_存储引擎} DEFAULT CHARSET={新建数据表_字符集};"
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 创建数据表窗口_取消(窗口对象):
窗口对象.withdraw()
def 创建数据表窗口_增加字段(显示框):
print("增加字段")
行号列表 = [i for i in 字典_创建表_字段信息]
print("行号列表", 行号列表)
if 行号列表 == []:
新行号 = 0
else:
最大行号 = 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)
if SV_数据库类型.get() == 'SQLite3':
Combobox_字段类型['value'] = ('INTEGER', 'FLOAT', 'CHAR()', 'VARCHAR()', 'TEXT', 'DATE', 'timestamp', 'BLOB')
elif SV_数据库类型.get() == 'MySQL':
Combobox_字段类型['value'] = ('INT','CHAR()','VARCHAR()','TEXT','DATE','TIME','TINYINT','SMALLINT','MEDIUMINT','BIGINT','FLOAT','DOUBLE','DECIMAL','YEAR','DATETIME','TIMESTAMP','TINYBLOB','TINYTEXT','BLOB','MEDIUMBLOB','MEDIUMTEXT','LONGBLOB','LONGTEXT')
else:
Combobox_字段类型['value'] = ('INT', 'FLOAT', 'CHAR(N)', 'VARCHAR(N)', 'TEXT', 'DATE')
Combobox_是否可空 = ttk.Combobox(显示框, width=10)
Combobox_是否可空['value'] = ('NULL', 'NOT NULL')
Combobox_是否可空.current(0) # 默认值中的内容为索引,从0开始
Combobox_默认值 = ttk.Combobox(显示框)
Combobox_默认值['value'] = ('auto_increment', 'datetime("now","localtime")', 'CURRENT_TIMESTAMP')
Combobox_是否主键 = ttk.Combobox(显示框, width=15)
if SV_数据库类型.get() == 'SQLite3':
Combobox_是否主键['value'] = ('否', '是', '是(SQLite3自增数)')
else:
Combobox_是否主键['value'] = ('否', '是')
Combobox_是否主键.current(0)
列表_字段属性对象 = [Button_删除本行字段信息, 字段编号, Entry_字段名, Combobox_字段类型, Combobox_是否可空, Combobox_默认值, Combobox_是否主键]
字典_创建表_字段信息[新行号] = 列表_字段属性对象 # 添加到全局字典变量,KEY为行号,VALUE为组成列表依次存放的Entry对象
# 给创建的7个控件对象设置位置
for 列号 in range(0,7):
列表_字段属性对象[列号].grid(row=新行号, column=列号, sticky='NW')
def 复制数据表窗口_填入字段(显示框):
print("填入字段")
SQL_CMD = 生成_查表字段属性SQL语句('主要6项')
if SQL_CMD != '':
R = DEF_SQL_查询和返回(SQL_CMD)
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 NULL
Combobox_是否可空.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)
## 表名框:用户输入新建的表名
if SV_数据库类型.get() == 'MySQL':
def Combobox_数据库引擎选择后执行函数(event):
SV_新建数据表_存储引擎.set(Combobox_数据库引擎.get())
def Combobox_字符集选择后执行函数(event):
SV_新建数据表_字符集.set(Combobox_字符集.get())
Label(表名框, text='[库名]').grid(row=0,column=0,sticky='NW')
Entry(表名框, textvariable=SV_新建数据表_库名).grid(row=0,column=1,sticky='NW')
Label(表名框, text='[存储引擎]').grid(row=1,column=0,sticky='NW')
Combobox_数据库引擎 = ttk.Combobox(表名框, width=10)
Combobox_数据库引擎['value'] = ('InnoDB', 'MyISAM')
Combobox_数据库引擎.current(0) # 默认值中的内容为索引,从0开始
Combobox_数据库引擎.grid(row=1, column=1, sticky='NW')
SV_新建数据表_存储引擎.set(Combobox_数据库引擎.get()) # 设置全局变量为框内值
Combobox_数据库引擎.bind('<>', Combobox_数据库引擎选择后执行函数)
Label(表名框, text='[字符集]').grid(row=2,column=0,sticky='NW')
Combobox_字符集 = ttk.Combobox(表名框, width=10)
Combobox_字符集['value'] = ('UTF8', 'BIG5', 'GB2312', 'ASCII') # 查询支持的字符集 show character set;
Combobox_字符集.current(0)
Combobox_字符集.grid(row=2, column=1, sticky='NW')
SV_新建数据表_字符集.set(Combobox_字符集.get())
Combobox_字符集.bind('<>', Combobox_字符集选择后执行函数)
SV_新建数据表_库名.set(SV_数据库树_选中库名.get())
Label(表名框, text='[表名]').grid(row=0,column=2,sticky='NW')
Entry_新表名 = Entry(表名框, textvariable=SV_新建数据表_表名)
Entry_新表名.bind('', 键盘任意输入事件_判断表名) # 每当输入内容时执行一次函数
Entry_新表名.grid(row=0, column=3, sticky='NW')
SV_提示信息_建表.set('请输入新表名')
Label_提示信息_建表 = Label(表名框, textvariable=SV_提示信息_建表)
Label_提示信息_建表['fg'] = 'red'
Label_提示信息_建表.grid(row=0,column=4,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)
## 创建新窗口 新建数据表 ##
###########################
def DEF_数据库树_鼠标左键单击(event):
sels= event.widget.selection() # event.widget获取Treeview对象,调用selection获取选择对象名称
print(f"DEF_数据库树_鼠标左键单击【查看对象】sels={sels}")
for idx in sels:
值 = Treeview_数据库结构.item(idx)["text"]
print(f" idx={idx} Treeview_数据库结构={Treeview_数据库结构} Treeview_数据库结构.item(idx)={Treeview_数据库结构.item(idx)}")
选中名 = Treeview_数据库结构.item(idx)["text"]
库或表,上级名 = Treeview_数据库结构.item(idx)["values"] # ['库', 'TOP'] 或 ['表', '上级库名']
print(f"选中名={选中名} 库或表={库或表} 上级名={上级名}")
if 库或表 == '库':
SV_数据库树_选中库名.set(选中名)
elif 库或表 == '表':
SV_数据库树_选中表名.set(选中名)
SV_数据库树_选中库名.set(上级名)
数据库名 = SV_数据库树_选中库名.get()
if 数据库名 not in ('', '无'):
ID = D_数据库名_Treeview对象[数据库名]
print(f"数据库名={数据库名} ID={ID}")
def DEF_数据库树_鼠标右键(event):
iid = Treeview_数据库结构.identify_row(event.y)
print(f"DEF_数据库树_鼠标右键 iid={iid}")
if iid:
Treeview_数据库结构.selection_set(iid) # 鼠标左键点击
选中名 = Treeview_数据库结构.item(iid)["text"]
库或表,上级名 = Treeview_数据库结构.item(iid)["values"] # ['库', 'TOP'] 或 ['表', '上级库名']
print(f"选中名={选中名} 库或表={库或表} 上级名={上级名}")
if 库或表 == '库':
SV_数据库树_选中库名.set(选中名)
数据库_右键菜单.post(event.x_root, event.y_root) # 光标位置显示菜单
elif 库或表 == '表':
SV_数据库树_选中表名.set(选中名)
SV_数据库树_选中库名.set(上级名)
数据表_右键菜单.post(event.x_root, event.y_root)
else:
print("DEF_数据库树_鼠标右键 【点在树外】")
数据表_右键菜单_空白处.post(event.x_root, event.y_root)
def DEF_数据库树_鼠标左键双击(event):
Log.debug(f"DEF_数据库树_鼠标左键双击")
iid = Treeview_数据库结构.identify_row(event.y)
print("iid", iid)
if iid:
Treeview_数据库结构.selection_set(iid) # 鼠标左键点击
选中名 = Treeview_数据库结构.item(iid)['text']
库或表,上级名 = Treeview_数据库结构.item(iid)['values'] # ['库', 'TOP'] 或 ['表', '上级库名']
print(f"选中名={选中名} 库或表={库或表} 上级名={上级名}")
if 库或表 == '库':
SV_数据库树_选中库名.set(选中名)
if Treeview_数据库结构.item(iid)['open'] == 0:
print(f"打开库: {选中名}")
# DEF_数据库树_打开库() # 暂停使用
# DEF_数据库树_展开库() # 暂停使用 打开库以后展开节点
elif 库或表 == '表':
SV_数据库树_选中表名.set(选中名)
SV_数据库树_选中库名.set(上级名)
if Treeview_数据库结构.item(iid)['open'] == 0:
print(f"打开表: {上级名}.{选中名}")
DEF_数据库树_打开表()
else:
print("DEF_数据库树_鼠标左键双击 点在树外")
Treeview_数据库结构 = ttk.Treeview(Frame_数据库和表显示框, columns=('id', 'name'), show='tree', displaycolumns=(), height=20)
Treeview_数据库结构.bind('<>', DEF_数据库树_鼠标左键单击) # DEBUG用,鼠标左键点击选项事件
Treeview_数据库结构.bind('', DEF_数据库树_鼠标左键双击) # 打开库或表
Treeview_数据库结构.bind('', DEF_数据库树_鼠标右键) # 打开库菜单或表菜单
Scrollbar_数据库和表框_竖 = Scrollbar(Frame_数据库和表显示框)
Scrollbar_数据库和表框_竖['command'] = Treeview_数据库结构.yview
Scrollbar_数据库和表框_横 = Scrollbar(Frame_数据库和表显示框)
Scrollbar_数据库和表框_横['command'] = Treeview_数据库结构.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)
Treeview_数据库结构.config(xscrollcommand=Scrollbar_数据库和表框_横.set, yscrollcommand=Scrollbar_数据库和表框_竖.set) # 自动设置滚动条滑动幅度
Treeview_数据库结构.grid(row=0,column=0,sticky='NSEW')
def 清除数据库树指定节点():
数据库名 = SV_数据库树_选中库名.get()
ID = D_数据库名_Treeview对象[数据库名]
print(f"数据库名={数据库名} ID={ID} DELETE")
Treeview_数据库结构.delete(ID)
D_数据库名_Treeview对象[数据库名] = Treeview_数据库结构.insert('I001', END, text=数据库名, values=['库', 'TOP'])
def DEF_数据库树_打开库(): # 查询并显示库内数据表名
数据库名 = SV_数据库树_选中库名.get()
SQL_CMD = 生成_查表SQL语句()
if SQL_CMD != '':
R = DEF_SQL_查询和返回(SQL_CMD)
if R[0] == 0:
Log.debug(f"打开库 {数据库名} 查数据表列表 {SQL_CMD} {R[1]}")
查询结果 = R[1]
if len(查询结果) == 0:
SV_数据表列表.set('[]')
tkinter.messagebox.showinfo(title='INFO', message='无数据表')
else:
L_数据表名 = [i[0] for i in 查询结果]
SV_数据表列表.set(str(L_数据表名))
## X 刷新功能未实现
for DB_表名 in L_数据表名:
Treeview_数据库结构.insert(D_数据库名_Treeview对象[数据库名], END, text=DB_表名, values=['表',数据库名])
Log.debug(f" ['表',{数据库名}] : DB_表名={DB_表名}")
DEF_数据库树_展开库()
else:
tkinter.messagebox.showerror(title='ERROR', message=R[1])
def DEF_数据库树_新建表():
当前数据库类型 = SV_数据库类型.get()
if 当前数据库类型 == 'SQLite3':
DEF_弹出创建数据表窗口('新建')
elif 当前数据库类型 == 'MySQL':
DEF_弹出创建数据表窗口('新建')
else:
ERROR = f'未知类型数据库 {当前数据库类型}'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
def DEF_数据库树_展开库():
数据库名 = SV_数据库树_选中库名.get()
try:
ID = D_数据库名_Treeview对象[数据库名]
if Treeview_数据库结构.item(ID)['open'] == 0: # 判断未展开
Treeview_数据库结构.item(ID, open=True) # 设置展开
Log.debug(f"展开库 {数据库名} 完成 {ID}")
except Exception as e:
Log.error(f"展开库 {数据库名} 失败 {e}")
def DEF_数据库树_折叠库():
数据库名 = SV_数据库树_选中库名.get()
try:
ID = D_数据库名_Treeview对象[数据库名]
if Treeview_数据库结构.item(ID)['open'] != 0: # 判断已展开
Treeview_数据库结构.item(ID, open=0) # 设置折叠
Log.debug(f"折叠库 {数据库名} 完成 {ID}")
except Exception as e:
Log.error(f"折叠库 {数据库名} 失败 {e}")
def DEF_右键菜单_刷新全部库():
items = Treeview_数据库结构.get_children()
[Treeview_数据库结构.delete(item) for item in items]
DEF_DB_查库_填入数据库树()
数据库_右键菜单 = Menu(tearoff=False)
数据库_右键菜单.add_command(label='打开数据库', command=DEF_数据库树_打开库)
数据库_右键菜单.add_separator()
数据库_右键菜单.add_command(label='新建表', command=DEF_数据库树_新建表) ## 菜单按钮函数:新建数据库表
数据库_右键菜单.add_separator()
数据库_右键菜单.add_command(label='展开库', command=DEF_数据库树_展开库)
数据库_右键菜单.add_command(label='折叠库', command=DEF_数据库树_折叠库)
数据库_右键菜单.add_separator()
数据库_右键菜单.add_command(label='刷新全部库', command=DEF_右键菜单_刷新全部库)
#数据库_右键菜单.add_command(label='导出全库(CSV)', command=数据库导出CSV) ## 菜单按钮函数:导出数据库中每个表为CSV格式文件
def DEF_数据库树_打开表(): # 打开数据表(查询表内容)
SQL_CMD = 生成_查表内容SQL语句()
if SQL_CMD != '':
DEF_SQL_查询和显示(SQL_CMD)
def DEF_右键菜单_删除表():
SQL_库名表名 = 生成_库名表名限定SQL语句部分()
if SQL_库名表名 != '':
用户决定 = tkinter.messagebox.askquestion(title='请三思...', message='是否确定删除数据表: '+SQL_库名表名) # 返回值为:yes/no
if 用户决定 == 'yes':
SQL_CMD = f'DROP TABLE {SQL_库名表名}' # 删除表的SQL语句
R = DEF_SQL_执行(SQL_CMD)
if R[0] == 0:
DEF_右键菜单_刷新全部库()
else:
tkinter.messagebox.showerror(title='ERROR', message=R[1])
def DEF_右键菜单_复制表():
当前数据库类型 = SV_数据库类型.get()
if 当前数据库类型 == 'SQLite3':
DEF_弹出创建数据表窗口('复制')
elif 当前数据库类型 == 'MySQL':
tkinter.messagebox.showerror(title='ERROR', message='施工中')
else:
ERROR = f'未知类型数据库 {当前数据库类型}'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
def DEF_右键菜单_查看表结构():
显示字段信息()
数据表_右键菜单 = Menu(tearoff=False) ## 创建数据表右键菜单
数据表_右键菜单.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格式文件
数据表_右键菜单_空白处 = Menu(tearoff=False) ## 创建数据表右键菜单(空白处)
数据表_右键菜单_空白处.add_command(label='空白处')
##################################################
## TKinter 主窗口布局 - TOP框 - LF_数据库信息框 ##
SV_安全模式状态 = StringVar()
D_数据库名_Treeview对象 = {}
def DEF_DB_查库_填入数据库树():
当前数据库类型 = SV_数据库类型.get()
if 当前数据库类型 == 'SQLite3':
Treeview_顶点 = Treeview_数据库结构.insert("", END, text="Treeview_顶点", values=['根', 'ROOT']) # 在根节点插入子节点(作为顶级顶点) # 指定插入位置,0表示在头部插入,END表示在尾部插入。
D_数据库名_Treeview对象['SQLite3'] = Treeview_数据库结构.insert(Treeview_顶点, END, text='SQLite3', values=['库', 'TOP'])
SV_数据库树_选中库名.set('SQLite3')
DEF_数据库树_打开库()
elif 当前数据库类型 == 'MySQL':
SQL_CMD = 'SHOW DATABASES;'
R = DEF_SQL_查询和返回(SQL_CMD)
if R[0] == 0:
Treeview_顶点 = Treeview_数据库结构.insert("", END, text="Treeview_顶点", values=['根', 'ROOT']) # 在根节点插入子节点(作为顶级顶点) # 指定插入位置,0表示在头部插入,END表示在尾部插入。
Log.debug(f"DEF_MySQL_查库 {R[1]}")
查询结果 = R[1]
if 查询结果 == []:
DB_INFO['数据库列表'] = []
SV_数据库列表.set('[]')
D_数据库名_Treeview对象.clear()
else:
L_数据库名 = [i[0] for i in 查询结果]
DB_INFO['数据库列表'] = L_数据库名
SV_数据库列表.set(str(L_数据库名))
for DB_库名 in L_数据库名:
D_数据库名_Treeview对象[DB_库名] = Treeview_数据库结构.insert(Treeview_顶点, END, text=DB_库名, values=['库', 'TOP'])
else:
tkinter.messagebox.showerror(title='ERROR', message=R[1])
def 选择SQLite3数据库文件():
数据库文件 = filedialog.askopenfilename()
SV_SQLite3_数据库路径.set(数据库文件) # 实时更新显示
def 选择MySQL连接配置文件():
MySQL连接配置文件路径 = filedialog.askopenfilename()
DB_MySQL_CONF_PATH.set(MySQL连接配置文件路径) # 及时更新
def DEF_按钮_载入MySQL连接配置():
FRAME_CLEAR(LF_显示编辑框) # 先清空显编框内控件
FILE_MySQL_CONF = DB_MySQL_CONF_PATH.get()
if FILE_MySQL_CONF.strip() == '': # 截掉头尾的空格后为空
选择MySQL连接配置文件()
FILE_MySQL_CONF = DB_MySQL_CONF_PATH.get()
if FILE_MySQL_CONF.strip() != '': # 截掉头尾的空格后不为空
try:
f = open(FILE_MySQL_CONF, 'r')
S = f.read() # 读取全部内容,保存到变量
f.close()
SP = S.split(',')
填补参数数量 = 6-len(SP)
if 填补参数数量 > 0:
SP = [i.strip() for i in SP] + ['' for i in range(0,填补参数数量)]
SV_登录帐号.set(SP[0])
SV_登录密码.set(SP[1])
SV_登录地址.set(SP[2])
IV_服务端口.set(int(SP[3]))
SV_登录库名.set(SP[4])
SV_字符集.set(SP[5])
except Exception as e:
ERROR = f"{e}\n 格式: 账号,密码,地址,端口,库名,字符集"
tkinter.messagebox.showerror(title='ERROR', message=str(e))
def 打开MySQL数据库():
登录地址 = SV_登录地址.get()
服务端口 = IV_服务端口.get()
登录帐号 = SV_登录帐号.get()
登录密码 = SV_登录密码.get()
登录库名 = SV_登录库名.get()
字符集 = SV_字符集.get()
if 字符集.strip() == '':
字符集 = 'utf8'
R = DEV_MySQL_OPEN(登录地址, 登录帐号, 登录密码, 服务端口, 登录库名, 字符集)
if R[0] == 0:
DB_INFO['数据库连接对象'] = R[1] # 同步全局字典:保存数据库连接对象
SV_数据库连接对象.set(str(R[1]))
DEF_DB_查库_填入数据库树()
SV_数据库树_选中库名.set(登录库名)
TEXT_数据库操作日志.insert(0.0, 'MySQL 连接成功\n', 'tag_i')
else:
ERROR = f'MySQL 连接失败 {R[1]}'
TEXT_数据库操作日志.insert(0.0, ERROR+'\n', 'tag_e')
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
def 打开或新建SQLite3数据库():
FRAME_CLEAR(LF_显示编辑框) # 先清空显编框内控件
DB_File = SV_SQLite3_数据库路径.get() # 方便手动输入数据库名
if DB_File.strip() == '': # 截掉头尾的空格后为空
选择SQLite3数据库文件()
DB_File = SV_SQLite3_数据库路径.get()
if DB_File.strip() != '': # 截掉头尾的空格后不为空
R = DEV_SQLite3_OPEN(DB_File)
if R[0] == 0:
DB_INFO['数据库连接对象'] = R[1] # 同步全局字典:保存数据库连接对象
SV_数据库连接对象.set(str(R[1])) # DEBUG 查看
DEF_DB_查库_填入数据库树()
else:
ERROR = f'打开数据库文件"{DB_File}"失败,错误信息{R[1]}'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
def DEF_按钮_关闭数据库():
数据库连接对象 = DB_INFO['数据库连接对象']
if 数据库连接对象 == '':
ERROR = f"{SV_数据库类型.get()}数据库:未打开或已关闭"
TEXT_数据库操作日志.insert(0.0, ERROR+'\n', 'tag_e')
else:
数据库连接对象.close()
DB_INFO['数据库连接对象'] = ''
SV_数据库连接对象.set('')
TEXT_数据库操作日志.insert(0.0, f'{SV_数据库类型.get()}数据库:关闭\n', 'tag_i')
FRAME_CLEAR(LF_显示编辑框) # 清空框内控件
## 清理数据库树框
items = Treeview_数据库结构.get_children()
[Treeview_数据库结构.delete(item) for item in items]
Log.debug(f"Treeview_数据库结构.delete(item) items={items}")
## 清空全局变量
字典_查询字段_坐标_对象.clear()
字典_查询字段_坐标_初值.clear()
字典_查询结果_坐标_对象.clear()
字典_查询结果_坐标_初值.clear()
字典_添加记录_坐标_对象.clear()
字典_创建表_字段信息.clear()
字典_新加字段信息.clear()
字典_对象存储.clear()
def 使用SQlite3模式():
SV_数据库类型.set('SQLite3')
FRAME_CLEAR(LF_数据库信息框) # 清空控件
Label(LF_数据库信息框, text='[SQLite3 路径]').grid(row=0,column=0,sticky='W')
Entry(LF_数据库信息框, textvariable=SV_SQLite3_数据库路径, width=81).grid(row=0,column=1,sticky='W')
按钮_打开数据库 = Button(LF_数据库信息框, text='打开/新建数据库', command=打开或新建SQLite3数据库)
按钮_打开数据库.grid(row=1,column=0,sticky='NW')
Button(LF_数据库信息框, text='关闭数据库', command=DEF_按钮_关闭数据库).grid(row=1,column=1,sticky='NW')
def 使用MySQL模式():
SV_数据库类型.set('MySQL')
FRAME_CLEAR(LF_数据库信息框)
## 登录信息框
数据库登录框 = Frame(LF_数据库信息框)
Label(数据库登录框, text='[帐号]').grid( row=0,column=0,sticky='W')
Entry(数据库登录框, textvariable=SV_登录帐号,width=10).grid(row=0,column=1,sticky='W')
Label(数据库登录框, text='[密码]').grid( row=0,column=2,sticky='W')
Entry(数据库登录框, textvariable=SV_登录密码,width=10,show='*').grid(row=0,column=3,sticky='W')
Label(数据库登录框, text='[地址]').grid( row=0,column=4,sticky='W')
Entry(数据库登录框, textvariable=SV_登录地址,width=15).grid(row=0,column=5,sticky='W')
Label(数据库登录框, text='[端口]').grid( row=0,column=6,sticky='W')
Entry(数据库登录框, textvariable=IV_服务端口,width=5).grid( row=0,column=7,sticky='W')
Label(数据库登录框, text='[库名]').grid( row=0,column=8,sticky='W')
Entry(数据库登录框, textvariable=SV_登录库名,width=10).grid( row=0,column=9,sticky='W')
Label(数据库登录框, text='[字符集]').grid( row=0,column=10,sticky='W')
Entry(数据库登录框, textvariable=SV_字符集, width=8).grid( row=0,column=11,sticky='W')
数据库登录框.grid(row=0,column=0,sticky='NW')
登录按钮框_MySQL = Frame(LF_数据库信息框)
登录按钮框_MySQL.grid(row=2,column=0,sticky='NW')
Button(登录按钮框_MySQL, text='连接数据库', command=打开MySQL数据库).grid( row=0,column=0,sticky='NW')
Button(登录按钮框_MySQL, text='断开数据库', command=DEF_按钮_关闭数据库).grid(row=0,column=1,sticky='NW')
Button(登录按钮框_MySQL, text='载入MySQL连接配置文件', command=DEF_按钮_载入MySQL连接配置).grid(row=0,column=2,sticky='NW')
def 使用Oracle模式():
#SV_数据库类型.set('Oracle')
#FRAME_CLEAR(LF_数据库信息框)
tkinter.messagebox.showinfo(title='提示', message='未开工')
## 其他功能:SQL示例,根据字典自动生成多级菜单
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 "%查找内容%";'
}}
一级菜单 = Menu(tearoff=False) # 创建一级菜单对象
def DEF_点击二级菜单(K1,K2):
二级菜单选中值 = SQL语句示例字典[K1][K2]
文本框_命令行.delete(0.0, END)
文本框_命令行.insert(0.0, 二级菜单选中值)
文本框_命令行.focus_set()
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_示例SQL语句.winfo_rootx() + Button_示例SQL语句.winfo_width()
一级菜单_Y = Button_示例SQL语句.winfo_rooty() - Button_示例SQL语句.winfo_height()
一级菜单.post(一级菜单_X, 一级菜单_Y)
#################################################
## TKinter 主窗口布局 - TOP框 - 顶级功能菜单框 ##
Label_安全模式 = Label(顶级功能菜单框, text='[安全模式]')
Label_安全模式.grid(row=0,column=10,sticky='W')
Combobox_安全模式状态 = ttk.Combobox(顶级功能菜单框, width=4)
Combobox_安全模式状态['value'] = ('关闭', '开启')
Combobox_安全模式状态.current(0) # 默认值中的内容为索引,从0开始
Label_安全模式['fg'] = 'OrangeRed'
Combobox_安全模式状态.grid(row=0, column=11, sticky='W')
def DEF_设置安全模式状态(event):
SV_安全模式状态.set(Combobox_安全模式状态.get())
if Combobox_安全模式状态.get() == '关闭':
Label_安全模式['fg'] = 'OrangeRed'
else:
Label_安全模式['fg'] = 'Green'
Combobox_安全模式状态.bind('<>', DEF_设置安全模式状态)
SV_安全模式状态.set(Combobox_安全模式状态.get())
Button_使用SQLite = Button(顶级功能菜单框, text='SQLite3', command=使用SQlite3模式)
Button_使用MySQL = Button(顶级功能菜单框, text='MySQL', command=使用MySQL模式)
Button_使用Oracle = Button(顶级功能菜单框, text='Oracle', command=使用Oracle模式)
Button_示例SQL语句 = Button(顶级功能菜单框, text='示例SQL语句', command=DEF_选择SQL示例语句)
Button_使用SQLite.grid(row=0,column=0,sticky='NW')
Button_使用MySQL.grid(row=0,column=1,sticky='NW')
Button_使用Oracle.grid(row=0,column=2,sticky='NW')
Button_示例SQL语句.grid(row=0,column=9,sticky='E')
使用SQlite3模式() ## 默认开启SQLite3模式
##########################################################
## TKinter 主窗口布局 - TOP框 - 控表按钮框 - 分页按钮框 ##
def 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"往前显示{分页限制行数}(分页限制行数)行数据(定位显示开始位置行号={定位显示开始位置行号}) (已显示记录数){已显示记录数}>{分页限制行数}(分页限制行数)")
最后查询语句 = DB_INFO['最后查询语句']
if 最后查询语句 != '':
DEF_SQL_查询和显示(最后查询语句, 定位显示开始位置行号)
else:
ERROR = "找不到上次执行的查询语句 DB_INFO['最后查询语句']=''"
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_查询和显示_下一页()
def 选择后执行函数(event):
分页行数.set(Combobox_分页显示行数.get())
分页按钮框 = Frame(控表按钮框)
修改确认框 = Frame(控表按钮框)
分页按钮框.grid(row=1,column=0,sticky='NW')
修改确认框.grid(row=2,column=0,sticky='NW')
按钮_显编框起始页 = 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')
Combobox_分页显示行数.bind('<>', 选择后执行函数)
按钮_显编框下一页['state'] = 'disabled' ## 不能用时禁止
#按钮_显编框下一页['state'] = 'normal' ## 能用时再启用
##########################################################
## TKinter 主窗口布局 - TOP框 - 控表按钮框 - 修改确认框 ##
def DEF_按钮_确认修改数据表(): # 修改记录:遍历全部显示内容,找出被修改的部分,组成SQL语句并执行,为避免错误修改,显示行必须有主键,根据主键修改对应行数据
SQL_库名表名 = 生成_库名表名限定SQL语句部分()
if SQL_库名表名 != '':
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"修改数据表(UPDATE) D_修改信息={D_修改信息}")
## 根据 D_修改信息 制作数据库语句
L_SQL_CMD = []
for KEY_SQL_WHERE in D_修改信息:
合并修改部分语句 = ', '.join(D_修改信息[KEY_SQL_WHERE])
SQL_CMD = f'UPDATE {SQL_库名表名} SET {合并修改部分语句} {KEY_SQL_WHERE}'
L_SQL_CMD.append(SQL_CMD)
Log.debug(f"修改数据表(UPDATE) L_SQL_CMD={L_SQL_CMD}")
if L_SQL_CMD == []:
DEBUG = '内容没有改变,不操作数据库'
Log.debug(DEBUG)
TEXT_数据库操作日志.insert(0.0, DEBUG+'\n', 'tag_i')
else:
## 依次执行SQL语句
L_成功信息, L_失败信息 = DEF_SQL_执行多条_忽略错误语句(L_SQL_CMD)
## 失败任意一条SQL语句就弹框提示
if L_失败信息 != []:
SHOW_STR = ''
for i in L_成功信息+L_失败信息:
SHOW_STR += i + '\n'
tkinter.messagebox.showerror(title='ERROR', message=SHOW_STR)
## 成功任意一条语句后,更新显示表格
if L_成功信息 != []:
执行上条查询语句_刷新显示('已翻页位置开始')
else:
tkinter.messagebox.showerror(title='ERROR', message=R[1])
## 按钮执行后隐藏
按钮_确认修改数据库.grid_forget() # 隐藏 按钮_确认修改数据库
按钮_确认修改数据库 = Button(修改确认框, text='确认修改', bg='#7FFF00', command=DEF_按钮_确认修改数据表)
按钮_确认修改数据库.grid(row=0, column=0, sticky='NW')
按钮_确认修改数据库.grid_forget() # 隐藏,在进行编辑后再出现
#按钮_确认修改数据库.grid() # 显示
#########################################
## TKinter 主窗口布局 - TOP框 - 命令框 ##
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') or SQL_CMD.lstrip()[0:5].lower() == 'show ':
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':
pass ## X 更新数据库树
else:
ERROR = '没有输入SQL语句'
tkinter.messagebox.showerror(title='错误', message=ERROR)
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_选择SQL脚本文本():
本地SQL脚本文本 = filedialog.askopenfilename()
DB_SQL_SCRIPT_FILE.set(本地SQL脚本文本) # 实时更新显示
def DEF_按钮_执行SQL脚本文件():
脚本文件 = DB_SQL_SCRIPT_FILE.get().strip()
if 脚本文件.strip() == '':
DEF_选择SQL脚本文本()
脚本文件 = DB_SQL_SCRIPT_FILE.get().strip()
if 脚本文件.strip() != '':
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)
命令行_按钮框 = 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='W')
Label(本地SQL脚本文件操作框, text='[脚本文件路径]').grid(row=0, column=1, sticky='W')
Entry(本地SQL脚本文件操作框, textvariable=DB_SQL_SCRIPT_FILE, width=71).grid(row=0, column=2)
## END ##
top.mainloop() ## 进入消息循环
## END ##