使用Python3自带GUI做个图形化操作SQLite3数据库的工具
#_*_ coding:utf8 _*_
## Python3-GUI-DB-SQLite3
## V1.6
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添加的日志处理器
# 设置记录的日志级别
Log.setLevel(logging.DEBUG)
#Log.setLevel(logging.INFO)
#Log.setLevel(logging.WARNING)
#Log.setLevel(logging.ERROR)
#Log.setLevel(logging.CRITICAL)
##################
## SQLite3 操作 ##
##################
import 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层)
top = Tk() # 初始化Tk()
top.title('SQLite3 图形化数据库管理工具') # 设置标题
窗口宽 = 900
窗口高 = 800
# 获取屏幕尺寸以计算布局参数,使窗口居屏幕中央
屏幕宽 = top.winfo_screenwidth()
屏幕高 = top.winfo_screenheight()
alignstr = '%dx%d+%d+%d' % (窗口宽, 窗口高, (屏幕宽-窗口宽)/2, (屏幕高-窗口高)/2)
top.geometry(alignstr)
top.resizable(width=True, height=True) # 设置窗口是否可变长、宽(True:可变,False:不可变)
## TOP框布局(第1层)
顶框 = Frame(top)
左侧框 = Frame(top, bg='#00CED1')
分隔左右框 = Frame(top, width=20)
右侧框 = Frame(top)
日志框 = LabelFrame(top, text='数据库改动记录(倒序)')
顶框.grid(row=0,column=0,sticky='NW', columnspan=3) #0-012
日志框.grid(row=1,column=0,sticky='NW', columnspan=3) #1-012
左侧框.grid(row=2,column=0,sticky='NW') #2-0
分隔左右框.grid(row=2,column=1,sticky='NW') #2-1
右侧框.grid(row=2,column=2,sticky='NW') #2-2
##################
## 全局字典变量 ##
##################
## 存储数据库信息
DB_INFO = {'数据库文件':'', '数据库连接':'', '数据库游标':'', '字段名列表':[], 'LAST_SELECT':''}
字典_查询字段_坐标_对象 = {} # KEY=控件坐标 || VAULE=控件对象 || {(控件行号,控件列号):控件对象} || { (0,0):obj }
字典_查询字段_坐标_初值 = {} # KEY=控件坐标 || VAULE=初始值 || {(控件行号,控件列号):初始值} || { (0,0):123 }
字典_查询结果_坐标_对象 = {} # KEY=控件坐标 || VAULE=控件对象 || {(控件行号,控件列号):控件对象} || { (0,0):obj }
字典_查询结果_坐标_初值 = {} # KEY=控件坐标 || VAULE=初始值 || {(控件行号,控件列号):初始值} || { (0,0):123 }
字典_添加记录_坐标_对象 = {} # KEY=控件坐标 || VAULE=控件对象 || {(控件行号,控件列号):控件对象} || { (0,0):obj }
字典_添加记录_坐标_初值 = {} # KEY=控件坐标 || VAULE=初始值 || {(控件行号,控件列号):初始值} || { (0,0):123 }
字典_创建表_字段信息 = {}
字典_新加字段信息 = {}
字典_对象存储 = {} # {'文本编辑对象':''} 大文本编辑框用
## TKinter 实时更新的全局变量
DB_FULL_NAME = StringVar() # 当前操作的数据库文件名
DB_TABLE_NAME = StringVar() # 当前操作的数据库数据表名
SV_最后查询语句 = StringVar() # 记录上一次的查询语句,用于在修改后刷新显示编辑框内容
SV_查询字段列表 = StringVar() # 查询语句查询结果的字段信息
字段框_定位列 = IntVar()
数据框_定位行 = IntVar()
数据框_定位列 = IntVar()
新建数据表名 = StringVar()
## 分页
分页行数 = IntVar() # 设置要读取数据的行数
分页行数.set(5) # 设置以5条分页
IV_已显示记录数 = IntVar() ### 用于修改数据库后,再次显示在修改位置
IV_上次分页行数 = IntVar() ### 用于修改数据库后,再次显示在修改位置
## 选择本地SQL脚本文件
DB_SQL_SCRIPT_FILE = StringVar() # 选择本地SQL脚本文件
## 光标位置记录
IV_光标X轴 = IntVar()
IV_光标Y轴 = IntVar()
##########
## 函数 ##
##########
## SQLite3 查找主键
## LL 是通过 PRAGMA table_info(TABLE_NAME) 命令获取的表字段信息
def PK(LL):
L_PK_NAME = [] # 主键字段名列表
for i in LL:
if i[5] == 1: # 第6列为1表示是主键
L_PK_NAME.append(i[1]) # 字段名名信息在第2列
return(L_PK_NAME)
## 执行更新显示/编辑框(从头显示)
def UPDATE_SELECT():
LAST_SELECT = DB_INFO['LAST_SELECT']
if LAST_SELECT == '':
WARNING = '上一步查询语句为空'
print(WARNING)
else:
print("从头查询显示")
DEF_SQL_查询和显示(LAST_SELECT)
## 执行更新显示/编辑框(从编辑处显示)
### 用于修改数据库后,再次显示在修改位置
def UPDATE_SELECT_LINIT():
LAST_SELECT = DB_INFO['LAST_SELECT']
if LAST_SELECT == '':
WARNING = '上一步查询语句为空'
print(WARNING)
else:
显编框修改处定位 = IV_已显示记录数.get() - IV_上次分页行数.get()
if 显编框修改处定位 <= 0:
print("从头查询显示")
DEF_SQL_查询和显示(LAST_SELECT)
else:
print("从修改处查询显示")
IV_已显示记录数.set(显编框修改处定位)
DEF_SQL_查询和显示_定位到编辑处(LAST_SELECT, 显编框修改处定位)
## 执行SQLite3命令语句,返回执行状态和执行结果(数据列表)
def DEF_SQLite3_CMD(SQLite3_CMD):
数据库连接对象 = DB_INFO['数据库连接']
try:
游标对象 = 数据库连接对象.cursor() # 创建一个游标
except Exception as e:
ERROR = '创建游标失败' + str(e)
return(1, ERROR)
else:
print("创建DEF_SQLite3_CMD游标,执行SQLite3命令语句")
try:
游标对象.execute(SQLite3_CMD)
except Exception as e:
ERROR = f'数据库 {DB_FULL_NAME.get()} 执行SQL语句 {SQLite3_CMD} 失败 {e}'
return(1, ERROR)
else:
全部记录 = 游标对象.fetchall()
游标对象.close()
print("关闭DEF_SQLite3_CMD游标")
return(0, 全部记录)
## 执行SQL查询语句,返回执行状态和执行结果(数据列表)
def DEF_SQL_查询和返回(SQL_CMD):
数据库连接对象 = DB_INFO['数据库连接']
try:
游标对象 = 数据库连接对象.cursor() # 创建一个游标
except Exception as e:
ERROR = '创建游标失败' + str(e)
return(1, ERROR)
else:
print("创建游标")
try:
游标对象.execute(SQL_CMD)
except Exception as e:
ERROR = f'数据库 {DB_FULL_NAME.get()} 执行SQL语句 {SQL_CMD} 失败 {e}'
return(1, ERROR)
else:
全部记录 = 游标对象.fetchall()
游标对象.close()
print("关闭游标")
DB_INFO['数据库游标'] = ''
return(0, 全部记录)
## 执行SQL查询语句,返回执行状态和执行结果(数据列表,字段列表)
## 导出使用
def DEF_SQL_查询和返回_数据列表_字段列表(SQL_CMD):
数据库连接对象 = DB_INFO['数据库连接']
try:
游标对象 = 数据库连接对象.cursor()
except Exception as e:
ERROR = '创建游标失败' + str(e)
return(1, ERROR)
else:
print("创建游标", 游标对象)
try:
游标对象.execute(SQL_CMD)
except Exception as e:
ERROR = f'数据库 {DB_FULL_NAME.get()} 执行SQL语句 {SQL_CMD} 失败 {str(e)}'
游标对象.close()
print("关闭游标", 游标对象)
return(1, ERROR)
else:
全部记录 = 游标对象.fetchall() # 获取全部查询数据记录
游标对象_字段名列表 = 游标对象.description # 获取查询结果的字段信息
字段名列表 = [i[0] for i in 游标对象_字段名列表] # 整理成字段名列表
游标对象.close()
print("关闭游标", 游标对象)
return(0, 全部记录, 字段名列表)
## 执行SQL查询语句,直接显示在界面,不返回
def DEF_SQL_查询和显示(SQL_CMD):
数据库连接对象 = DB_INFO['数据库连接']
try:
游标对象 = 数据库连接对象.cursor() # 创建一个游标
except Exception as e:
ERROR = '创建游标失败' + str(e)
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
print("创建游标(预备缓存游标,用于读取显示下一页)", 游标对象)
try:
游标对象.execute(SQL_CMD)
except Exception as e:
ERROR = f'数据库 {DB_FULL_NAME.get()} 执行SQL语句 {SQL_CMD} 失败 {e}'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
DB_INFO['LAST_SELECT'] = SQL_CMD # 保存成功执行的SQL查询语句
SV_最后查询语句.set(SQL_CMD) # 保存成功执行的SQL查询语句
游标对象_字段名列表 = 游标对象.description
字段名列表 = [i[0] for i in 游标对象_字段名列表]
DB_INFO['字段名列表'] = 字段名列表 # 保存字段名查询结果
SV_查询字段列表.set(字段名列表) # 展示字段名查询结果
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' # 禁止下一页按钮(第一次显示就全部显示完整,不需要下一页按钮)
print("关闭游标(数据<分页第一页)", 游标对象)
else:
DB_INFO['数据库游标'] = 游标对象 # 缓存数据库游标对象
按钮_显编框下一页['state'] = 'normal' # 启用下一页按钮
print("缓存游标(数据>=分页第一页)", 游标对象)
else:
字段和数据的存储和展示(字段名列表, [])
游标对象.close() # 关闭游标对象
DB_INFO['数据库游标'] = '' # 清空数据库游标对象
按钮_显编框下一页['state'] = 'disabled' # 禁止下一页按钮
print("关闭游标(数据=空,不用分页)", 游标对象)
else: # 分页限制行数 <= 0 读取全部记录,不分页
全部记录 = 游标对象.fetchall() # 从SQL查询结果中取出全部的记录
字段和数据的存储和展示(字段名列表, 全部记录)
游标对象.close() # 关闭游标对象
DB_INFO['数据库游标'] = '' # 清空数据库游标对象
按钮_显编框下一页['state'] = 'disabled' # 禁止下一页按钮
print("关闭游标(用户通过设置分页行数<=0一次性读取全部数据)", 游标对象)
实际读取记录行数 = len(全部记录) ### 用于修改数据库后,再次显示在修改位置
IV_已显示记录数.set(IV_已显示记录数.get() + 实际读取记录行数) ### 用于修改数据库后,再次显示在修改位置
IV_上次分页行数.set(实际读取记录行数) ### 用于修改数据库后,再次显示在修改位置
def DEF_SQL_查询和显示_定位到编辑处(SQL_CMD, 显编框修改处定位):
数据库连接对象 = DB_INFO['数据库连接']
try:
游标对象 = 数据库连接对象.cursor() # 创建一个游标
except Exception as e:
ERROR = '创建游标失败' + str(e)
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
print("创建游标")
try:
游标对象.execute(SQL_CMD)
except Exception as e:
ERROR = SQL_CMD + '\n' + str(e)
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
游标对象_字段名列表 = 游标对象.description
#print("游标对象_字段名列表", 游标对象_字段名列表)
字段名列表 = [i[0] for i in 游标对象_字段名列表]
DB_INFO['字段名列表'] = 字段名列表 # 保存字段名查询结果
SV_查询字段列表.set(字段名列表) # 展示字段名查询结果
丢弃记录 = 游标对象.fetchmany(显编框修改处定位) # 先从SQL查询结果中取编辑位置前面叶的内容部分,丢弃
## 分页控制
## 游标对象.fetchmany(<=0) 和 游标对象.fetchall() 效果一样,为读取全部数据
分页限制行数 = 分页行数.get()
if 分页限制行数 > 0: # 分页限制行数 > 0 读取部分记录,可分页显示
部分记录 = 游标对象.fetchmany(分页限制行数) # 从SQL查询结果中取指定行数的记录,如果查询结果为空则返回空列表
实际读取记录行数 = len(部分记录)
IV_已显示记录数.set(IV_已显示记录数.get() + 实际读取记录行数) ### 用于修改数据库后,再次显示在修改位置
IV_上次分页行数.set(实际读取记录行数) ### 用于修改数据库后,再次显示在修改位置
if 部分记录 != []:
字段和数据的存储和展示(字段名列表, 部分记录) # 在显编框展示结果,保存结果到全局变量,可以进行修改操作
if 实际读取记录行数 < 分页限制行数: # 已经全部显示,无法分页
游标对象.close() # 关闭游标对象
print("关闭游标")
DB_INFO['数据库游标'] = '' # 清空数据库游标对象
按钮_显编框下一页['state'] = 'disabled' # 禁止下一页按钮(第一次显示就全部显示完整,不需要下一页按钮)
else:
DB_INFO['数据库游标'] = 游标对象 # 缓存数据库游标对象
按钮_显编框下一页['state'] = 'normal' # 启用下一页按钮
else:
字段和数据的存储和展示(字段名列表, [])
print("空记录")
游标对象.close() # 关闭游标对象
print("关闭游标")
DB_INFO['数据库游标'] = '' # 清空数据库游标对象
按钮_显编框下一页['state'] = 'disabled' # 禁止下一页按钮
else: # 分页限制行数 <= 0 读取全部记录,不分页
全部记录 = 游标对象.fetchall() # 从SQL查询结果中取出全部的记录
字段和数据的存储和展示(字段名列表, 全部记录)
游标对象.close() # 关闭游标对象
print("关闭游标")
DB_INFO['数据库游标'] = '' # 清空数据库游标对象
按钮_显编框下一页['state'] = 'disabled' # 禁止下一页按钮
实际读取记录行数 = len(全部记录) ### 用于修改数据库后,再次显示在修改位置
IV_已显示记录数.set(IV_已显示记录数.get() + 实际读取记录行数) ### 用于修改数据库后,再次显示在修改位置
IV_上次分页行数.set(实际读取记录行数) ### 用于修改数据库后,再次显示在修改位置
## 分页操作:返回起始页
def DEF_SQL_查询和显示_返回起始页():
UPDATE_SELECT()
## 分页操作:显示下一页
def DEF_SQL_查询和显示_下一页():
游标对象 = DB_INFO['数据库游标'] # 获取缓存的数据库游标对象
if 游标对象 == '':
print("DB_INFO['数据库游标'] == '' 操作终止")
else:
print("再次使用同一个游标对象读取后续数据", 游标对象)
字段名列表 = DB_INFO['字段名列表'] # 从全局变量中取出保存的字段名信息
分页限制行数 = 分页行数.get()
if 分页限制行数 > 0:
部分记录 = 游标对象.fetchmany(分页限制行数)
实际读取记录行数 = len(部分记录)
if 实际读取记录行数 == 0:
游标对象.close() ## 关闭游标对象
DB_INFO['数据库游标'] = '' ## 清空数据库游标对象
按钮_显编框下一页['state'] = 'disabled' ## 禁止下一页按钮
INFO = '已经到底,当前页已经是全部记录'
tkinter.messagebox.showinfo(title='INFO', message=INFO)
print("关闭游标(数据=空,当前页已经是最后)", 游标对象)
else:
if 实际读取记录行数 < 分页限制行数: # 已经全部显示,没有后续分页
游标对象.close() ## 关闭游标对象
DB_INFO['数据库游标'] = '' ## 清空数据库游标对象
按钮_显编框下一页['state'] = 'disabled' ## 禁止下一页按钮
字段和数据的存储和展示(字段名列表, 部分记录)
print("关闭游标(数据<分页显示行数)", 游标对象)
else: # 实际读取和限制显示相等,可能后面还有。可能刚刚读完
字段和数据的存储和展示(字段名列表, 部分记录)
print("保持游标(数据>=分页显示行数)", 游标对象)
IV_已显示记录数.set(IV_已显示记录数.get() + 实际读取记录行数) ### 用于修改数据库后,再次显示在修改位置
IV_上次分页行数.set(实际读取记录行数) ### 用于修改数据库后,再次显示在修改位置
else:
全部记录 = 游标对象.fetchall() # 从SQL查询结果中取出全部的记录
游标对象.close() ## 关闭游标对象
print("关闭游标(用户通过设置分页行数<=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:
ERROR = '创建游标失败' + str(e)
return(1, ERROR)
else:
print("创建游标")
try:
游标对象.execute(SQL_CMD)
except Exception as e:
ERROR = str(e)
##失败情况不关闭游标,以免点击下一页失效
return(1, ERROR)
else:
数据库连接对象.commit() # 提交更改
游标对象.close()
print("关闭游标")
DB_INFO['数据库游标'] = ''
return(0,)
## 非查询的SQL语句(执行多条SQL语句)遇到错误终止(成功的提交更改,失败及后面的语句不操作)
## SQLite3 添加字段用,不能使用事件,无法回退
def DEF_SQL_执行多条_遇到错误终止(L_SQL_CMD):
数据库连接对象 = DB_INFO['数据库连接']
try:
游标对象 = 数据库连接对象.cursor() # 创建一个游标
except Exception as e:
ERROR = '创建游标失败' + str(e)
print(ERROR)
return(1, ERROR)
else:
print("创建游标")
成功记录列表 = []
失败记录列表 = []
忽略记录列表 = []
失败节点 = 0
SQL语句数量 = len(L_SQL_CMD)
for i in range(0, SQL语句数量):
SQL_CMD = L_SQL_CMD[i]
try:
游标对象.execute(SQL_CMD)
except Exception as e:
失败信息 = 'SQL语句 ' + SQL_CMD + ' 执行失败 ' + str(e)
失败记录列表.append(失败信息)
失败节点 = i
break
else:
成功信息 = 'SQL语句 ' + SQL_CMD + ' 执行成功 '
成功记录列表.append(成功信息)
数据库连接对象.commit() # 提交更改
游标对象.close()
print("关闭游标")
DB_INFO['数据库游标'] = ''
if len(成功记录列表) == SQL语句数量:
return(0,)
else:
for j in range(失败节点+1, SQL语句数量):
取消执行信息 = 'SQL语句 ' + L_SQL_CMD[j] + ' 取消执行 '
忽略记录列表.append(取消执行信息)
return(1, (成功记录列表, 失败记录列表, 忽略记录列表))
## 执行SQL脚本
def DEF_SQL_执行脚本(SQL_SCRIPT):
数据库连接对象 = DB_INFO['数据库连接']
if 数据库连接对象 == '':
ERROR = '数据库没有打开'
return(1, ERROR)
else:
try:
游标对象 = 数据库连接对象.cursor()
except Exception as e:
ERROR = str(e)
return(1, ERROR)
else:
print("创建游标")
try:
游标对象.executescript(SQL_SCRIPT) # 执行SQL脚本
except Exception as e:
ERROR = str(e)
#失败情况不关闭游标,以免点击下一页失效
return(1, ERROR)
else:
数据库连接对象.commit() # 提交更改
游标对象.close()
print("关闭游标")
DB_INFO['数据库游标'] = ''
return(0,)
## 清空框内控件
def FRAME_CLEAR(FRAME_NAME):
for X in FRAME_NAME.winfo_children():
X.destroy()
## 在显编框展示结果,保存结果到全局变量,可以进行修改操作
def 字段和数据的存储和展示(L, LL):
列数 = len(L)
行数 = len(LL)
FRAME_CLEAR(LabelFrame_显编框) # 清空框内控件
## 创建画布
画布 = Canvas(LabelFrame_显编框, 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')
## 动态设置画布窗口宽高:根据主主窗口的参数设置限宽限高
主窗口大小和位置 = top.geometry()
主窗口宽, 主窗口高, 主窗口X, 主窗口Y = re.findall('[0-9]+', 主窗口大小和位置)
画布限宽 = int(主窗口宽) -310 # 减去左边框和中间分隔框的宽
print("画布限宽", 画布限宽)
if 画布限宽 < 589:
画布限宽 = 589 # 保障最小宽度
画布限高 = int(主窗口高) -500
print("画布限高", 画布限高)
if 画布限高 < 250:
画布限高 = 250 # 保障最小高度
## 设置画布参数
总行数 = 行数 + 1
## 画布可滚动显示的最大宽和高(要刚好能放下画布里的Frame里的全部控件)
画布滚动最右边 = 144*列数 # 140*列数 + 列数*4
画布滚动最下边 = 21*总行数 # 20*行数 + 行数*1
## 动态设置显示画布固定显示宽和高(要和主显示框的大小匹配)
if 画布限宽 > 画布滚动最右边:
画布['width'] = 画布滚动最右边
else:
画布['width'] = 画布限宽 - 30
if 画布限高 > 画布滚动最下边:
画布['height'] = 画布滚动最下边
else:
画布['height'] = 画布限高
画布['scrollregion'] = (0,0,画布滚动最右边,画布滚动最下边) # 一个元组 tuple (w, n, e, s) ,定义了画布可滚动的最大区域,w 为左边,n 为头部,e 为右边,s 为底部
# 竖滚动条
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里创建控件
# 清除全局字典的内容
字典_查询字段_坐标_对象.clear()
字典_查询字段_坐标_初值.clear()
字典_查询结果_坐标_对象.clear()
字典_查询结果_坐标_初值.clear()
## 字段名
for 列 in range(0, 列数):
初始值 = str(L[列]) # 转成字符串
字典_查询字段_坐标_对象[(0,列)] = Entry(字段框, 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, 列数):
初始值 = str(LL[行][列]) # 转成字符串
字典_查询结果_坐标_对象[(行,列)] = Entry(数据框) # 控件对象放到指定框内,并保存对象到对象字典中
字典_查询结果_坐标_初值[(行,列)] = 初始值 # 保存初始值
字典_查询结果_坐标_对象[(行,列)].insert(0, 初始值) # 写入Entry作为初始值(从标签内开头开始填充新值,因为是全新标签,所有不用先删除里面内容)
字典_查询结果_坐标_对象[(行,列)].grid(row=行,column=列,sticky='W') # Entry排放到指定位置
字典_查询结果_坐标_对象[(行,列)].bind("", 左键单击) # 每个控件对象都绑定左键单击事件
字典_查询结果_坐标_对象[(行,列)].bind("", DEF_弹出_数据框_右键菜单) # 每个控件对象都绑定右键菜单事件
## 查询数据库表
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:
print("DEF_SQL_查询和返回() 失败,错误:", R[1])
################
## 按钮函数区 ##
################
## 数据库文件操作 选择
def DEF_按钮_选择数据库文件():
数据库文件 = filedialog.askopenfilename()
DB_INFO['数据库文件'] = 数据库文件 # 同步全局字典
DB_FULL_NAME.set(数据库文件) # 实时更新显示
print("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')
else:
ERROR = f'打开数据库文件"{DB_File}"失败,DB_INFO[数据库连接]清空,错误信息{R[1]}'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
## 关闭数据库
def DEF_按钮_关闭数据库():
数据库连接对象 = DB_INFO['数据库连接']
if 数据库连接对象 == '':
print("数据库未打开")
else:
数据库连接对象.close()
Frame_数据表列表显示框.grid_forget()
FRAME_CLEAR(LabelFrame_显编框) # 清空框内控件
## 清空全局变量
字典_查询字段_坐标_对象 = {}
字典_查询字段_坐标_初值 = {}
字典_查询结果_坐标_对象 = {}
字典_查询结果_坐标_初值 = {}
字典_添加记录_坐标_对象 = {}
字典_添加记录_坐标_初值 = {}
字典_创建表_字段信息 = {}
字典_新加字段信息 = {}
字典_对象存储 = {}
## 事件函数:双击列表中的数据表名查询表内全部记录
def DEF_双击表名(event):
当前选择 = Listbox_数据表列表.curselection() # 列表数据定位 (序号数,)
当前选择值 = Listbox_数据表列表.get(当前选择) # 对应的值
print("DEF_双击表名 当前选择", 当前选择, "当前选择值", 当前选择值) # 如:当前选择 (0,) 当前选择值 001
if 当前选择值 != '<空>':
DB_TABLE_NAME.set(当前选择值) # 更新表名变量
## 打开数据表(查询表内容)
SQL_CMD = f'SELECT * FROM {当前选择值}'
DEF_SQL_查询和显示(SQL_CMD)
else:
print("<空> 是数据库内无数据表的提示,忽略")
## 返回起始页
def DEF_按钮_显编框起始页():
print("DEF_按钮_显编框起始页")
DEF_SQL_查询和显示_返回起始页()
## 下一页
def DEF_按钮_显编框下一页():
print("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:
UPDATE_SELECT()
INFO = f'数据库 {DB_FULL_NAME.get()} 执行SQL脚本文件 {脚本文件} 内容 {脚本文件内容} 成功'
TEXT_数据库变动日志.insert(0.0, INFO+'\n')
Log.info(INFO)
tkinter.messagebox.showinfo(title='成功', message=INFO)
else:
ERROR = f'数据库 {DB_FULL_NAME.get()} 执行SQL脚本文件 {脚本文件} 内容 {脚本文件内容} 失败 {R[1]}'
tkinter.messagebox.showerror(title='失败', message=ERROR)
else:
ERROR = 'SQL脚本文件无内容'
tkinter.messagebox.showerror(title='错误', message=ERROR)
###########################
## 创建新窗口 新建数据表 ##
###########################
def 创建数据表窗口_确定(窗口对象):
print("确定")
#for K in 字典_创建表_字段信息:
# for 控件对象 in 字典_创建表_字段信息[K]:
# print(K, 控件对象.get())
新表名 = 新建数据表名.get()
if 新表名 == '':
print("没有新建表名")
else:
## 字段名不能为空,不能重复
错误标记 = 0
测试字段名列表 = []
for K in 字典_创建表_字段信息:
测试字段名 = 字典_创建表_字段信息[K][2].get().strip()
if 测试字段名 == '':
错误标记 = 1
print("含有空字段名")
break
else:
测试字段名列表.append(测试字段名)
if len(测试字段名列表) != len(set(测试字段名列表)):
错误标记 = 1
print("有重复字段名")
if 错误标记 == 0:
全部字段信息列表 = []
for 字段编号 in 字典_创建表_字段信息:
字段对象列表 = 字典_创建表_字段信息[字段编号]
字段名 = 字段对象列表[2].get()
字段类型 = 字段对象列表[3].get()
是否可空 = 字段对象列表[4].get()
默认值 = 字段对象列表[5].get()
是否主键 = 字段对象列表[6].get()
if 是否主键 == '是(自增数)':
主键标识 = 'PRIMARY KEY AUTOINCREMENT'
elif 是否主键 == '是(自定义)':
主键标识 = 'PRIMARY KEY'
else:
主键标识 = ''
if 默认值 != '':
默认值 = f'default {默认值}'
单条字段信息 = f'{字段名} {字段类型} {是否可空} {默认值} {主键标识}'
print("单条字段信息", 单条字段信息)
全部字段信息列表.append(单条字段信息)
SQL_CMD = f'CREATE TABLE {新表名}( \n'
长度 = len(全部字段信息列表)
for i in range(0, 长度):
if i != 长度-1:
SQL_CMD += 全部字段信息列表[i] + ',\n'
else:
SQL_CMD += 全部字段信息列表[i] + '\n'
SQL_CMD += ');'
print("SQL_CMD", SQL_CMD)
R = DEF_SQL_执行(SQL_CMD)
if R[0] == 0: # 创建新表成功
窗口对象.withdraw() # 关闭编辑窗口
DEF_查询数据库表() # 重新查询数据库表
else:
print("提示错误")
ERROR = SQL_CMD + '\n' + R[1]
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
def 创建数据表窗口_取消(窗口对象):
print("取消")
窗口对象.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)
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 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(新窗口)
按钮框 = 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)
## 表名框:用户输入新建的表名
Label(表名框, text='[新建数据表名]').grid(row=0,column=0,sticky='NW')
Entry(表名框, textvariable=新建数据表名).grid(row=0, column=1, 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'
## 数据框:编辑填入原值,新建填入空白
创建数据表窗口_增加字段(数据框)
## 按钮框
确定按钮 = Button(按钮框, text='确定', command=lambda 窗口对象=新窗口:创建数据表窗口_确定(窗口对象))
确定按钮.grid(row=1,column=0)
取消按钮 = Button(按钮框, text='取消', command=lambda 窗口对象=新窗口:创建数据表窗口_取消(窗口对象))
取消按钮.grid(row=1,column=1)
增加按钮 = Button(按钮框, text='增加字段', command=lambda Frame_控件对象=数据框:创建数据表窗口_增加字段(Frame_控件对象))
增加按钮.grid(row=1,column=2,sticky='NW')
#########################
## 创建新窗口 新加字段 ##
#########################
def 新加字段窗口_确定(窗口对象):
print("新加字段窗口_确定")
表名 = DB_TABLE_NAME.get()
if 表名 == '':
print("没有表名")
else:
## 字段名不能为空,不能重复
错误标记 = 0
测试字段名列表 = []
for K in 字典_新加字段信息:
测试字段名 = 字典_新加字段信息[K][2].get().strip()
if 测试字段名 == '':
错误标记 = 1
print("含有空字段名")
break
else:
测试字段名列表.append(测试字段名)
if len(测试字段名列表) != len(set(测试字段名列表)):
错误标记 = 1
print("有重复字段名")
if 错误标记 == 0:
全部新增字段信息列表 = []
for 字段编号 in 字典_新加字段信息:
单条新增字段信息 = []
字段对象列表 = 字典_新加字段信息[字段编号]
字段名 = 字段对象列表[2].get()
字段类型 = 字段对象列表[3].get()
是否可空 = 字段对象列表[4].get()
默认值 = 字段对象列表[5].get()
是否主键 = 字段对象列表[6].get()
if 是否主键 == '是(自增数)':
主键标识 = 'PRIMARY KEY AUTOINCREMENT'
elif 是否主键 == '是(自定义)':
主键标识 = 'PRIMARY KEY'
else:
主键标识 = ''
if 默认值 != '':
默认值 = f'default {默认值}'
else:
默认值 == ''
单条新增字段信息 = f'ALTER TABLE {表名} ADD {字段名} {字段类型} {是否可空} {默认值} {主键标识}'
print("单条新增字段信息", 单条新增字段信息)
全部新增字段信息列表.append(单条新增字段信息)
R = DEF_SQL_执行多条_遇到错误终止(全部新增字段信息列表)
print(R)
if R[0] == 0: # 创建新字段全部成功
窗口对象.withdraw() # 关闭编辑窗口
## 成功后,更新显示表格
UPDATE_SELECT()
else:
ERROR = '执行信息\n'
for i in R[1]:
for j in i:
ERROR += j + '\n'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
def 新加字段窗口_取消(窗口对象):
print("取消")
窗口对象.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)
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(新窗口)
按钮框 = 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)
## 标题框:固定不变的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(按钮框, text='确定', command=lambda 窗口对象=新窗口:新加字段窗口_确定(窗口对象))
确定按钮.grid(row=1,column=0)
取消按钮 = Button(按钮框, text='取消', command=lambda 窗口对象=新窗口:新加字段窗口_取消(窗口对象))
取消按钮.grid(row=1,column=1)
增加按钮 = Button(按钮框, text='增加字段', command=lambda Frame_控件对象=数据框:新加字段窗口_增加字段(Frame_控件对象))
增加按钮.grid(row=1,column=2,sticky='NW')
###########################
## 创建新窗口 大文本编辑 ##
###########################
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_新增记录():
数据表名 = DB_TABLE_NAME.get()
SQLite3_CMD = f'PRAGMA table_info({数据表名})'
R = DEF_SQLite3_CMD(SQLite3_CMD)
if R[0] == 0:
查询结果 = R[1]
字段列表 = [i[1] for i in 查询结果]
DEF_弹出新加记录窗口(字段列表)
else:
ERROR = SQLite3_CMD + '\n' + str(e)
print(ERROR)
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
def 新加记录窗口_确定(窗口对象):
print("确定")
数据表名 = DB_TABLE_NAME.get()
## 查找有值的字段,拼成INSERT SQL语句
LT = [] # 插入信息列表,元素是元组,元组元素是要插入的(字段名,字段值)
字段数量 = len(字典_添加记录_坐标_初值)//2 # 添加记录框总是显示2行,第一行为字段名,第二行初始全为空,字段数量=2行总格子数的一半
for i in range(0, 字段数量): # 按序号遍历每一列
字段名 = 字典_添加记录_坐标_对象[(0,i)].get() # 字段名都在第一行
字段值 = 字典_添加记录_坐标_对象[(1,i)].get() # 获取用户设置的值
if 字段值 != '': # 如果用户设置的值不是空的
LT.append((字段名,字段值)) # 加入到插入信息列表
LT_len = len(LT) # 计算插入信息列表长度
if LT_len != 0: # 不为0说明有插入信息
SK = '' # 字段名组成字符串,多个字段名用,分割
SV = '' # 字段值组成字符串,多个字段值用,分割
for i in range(0, LT_len): # 按序号遍历每个插入信息列表
字段名,字段值 = LT[i] # 提取字段名和字段值
if i == 0: # 第一个写法特殊一些
SK += 字段名 # 直接加字段名
SV += '"' + 字段值 + '"' # 直接加字段值,字段值用引号引起,数据库会根据字段类型自动适应改变类型的
else: # 后面开始的字段都要加逗号
SK += ',' + 字段名 # 先加个逗号和上一个字段名分隔,再加字段名
SV += ',"' + 字段值 + '"' # 先加个逗号和上一个字段值分隔,再加字段值,字段值用引号引起
SQL_CMD = f'INSERT INTO {数据表名} ({SK}) VALUES ({SV})' # 拼成INSERT SQL语句
R = DEF_SQL_执行(SQL_CMD)
if R[0] == 0:
INFO = f'数据库 {DB_FULL_NAME.get()} 执行SQL语句 {SQL_CMD} 成功'
TEXT_数据库变动日志.insert(0.0, INFO+'\n')
Log.info(INFO)
## 成功后,更新显示表格
UPDATE_SELECT()
窗口对象.withdraw() # 关闭新窗口
else:
ERROR = SQL_CMD + '\n' + R[1]
print(ERROR)
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
ERROR = '请填入数据'
print(ERROR)
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
def 新加记录窗口_取消(窗口对象):
print("取消")
窗口对象.withdraw()
def DEF_弹出新加记录窗口(字段名列表):
新窗口 = Toplevel()
新窗口.title('添加新记录')
## 新窗口布局
显编框 = Frame(新窗口)
按钮框 = Frame(新窗口)
显编框.grid(row=0,column=0,sticky='NW')
按钮框.grid(row=1,column=0)
## 宽高参数
行数 = 2
列数 = len(字段名列表)
## 创建画布
画布 = 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 # 保障最小高度
## 设置画布参数
总行数 = 行数 + 1
## 画布可滚动显示的最大宽和高(要刚好能放下画布里的Frame里的全部控件)
画布滚动最右边 = 144*列数 # 140*列数 + 列数*4
画布滚动最下边 = 21*总行数 # 20*行数 + 行数*1
## 动态设置显示画布固定显示宽和高(要和主显示框的大小匹配)
if 画布限宽 > 画布滚动最右边:
画布['width'] = 画布滚动最右边
else:
画布['width'] = 画布限宽 - 30
if 画布限高 > 画布滚动最下边:
画布['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行是字段行,序号为0
for 列 in range(0, 列数):
初始值 = str(字段名列表[列])
字典_添加记录_坐标_初值[(行,列)] = 初始值
字典_添加记录_坐标_对象[(行,列)] = Entry(字段框)
字典_添加记录_坐标_对象[(行,列)].insert(0, 初始值)
字典_添加记录_坐标_对象[(行,列)].grid(row=行,column=列,sticky='W')
字典_添加记录_坐标_对象[(行,列)]['state'] = 'readonly' # 设置为只读,用户不能修改
行 = 1 # 第2行是数据行,序号为1
for 列 in range(0, 列数):
字典_添加记录_坐标_初值[(行,列)] = ''
字典_添加记录_坐标_对象[(行,列)] = Entry(数据框)
字典_添加记录_坐标_对象[(行,列)].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(显示坐标)
## 修改记录
def DEF_按钮_确认修改数据库():
数据表名 = DB_TABLE_NAME.get()
查询字段列表 = DB_INFO['字段名列表']
## 获取当前数据库的数据表的主键信息
SQLite3_CMD = f'PRAGMA table_info({数据表名})'
R = DEF_SQLite3_CMD(SQLite3_CMD)
if R[0] == 0:
查询结果 = R[1]
L_PK_NAME = PK(查询结果)
#print("查数据库表得到主键信息 L_PK_NAME", L_PK_NAME)
if L_PK_NAME == []:
ERROR = f'数据表“{数据表名}”没有主键'
print(ERROR)
else:
主键信息 = ''
for 主键名 in L_PK_NAME:
for 列号 in range(0, len(查询字段列表)):
if 查询字段列表[列号] == 主键名:
主键信息 = (列号,主键名)
break
if 主键信息 != '':
#编辑信息字典 = {(主键名,主键值):[(字段名,新值),(字段名,新值)]}
编辑信息字典 = {}
for i in 字典_查询结果_坐标_对象: # 遍历编辑框(查询结果框)中的全部控件
控件旧值 = 字典_查询结果_坐标_初值[i]
控件新值 = 字典_查询结果_坐标_对象[i].get()
if 控件旧值 != 控件新值: # 当原值和当前值不一致,说明此控件值被修改
行号,列号 = i # 提取当前控件的坐标
字段名 = 字典_查询字段_坐标_初值[(0,列号)] # 字段名存储在字段全局变量中
字段值 = 控件新值
主键列号 = 主键信息[0]
主键名 = 主键信息[1]
主键值 = 字典_查询结果_坐标_初值[(行号,主键列号)]
## 把同行的修改信息合并在一起,方便整合成一条修改语句
if (主键名,主键值) not in 编辑信息字典:
编辑信息字典[(主键名,主键值)] = [(字段名,字段值)]
else:
编辑信息字典[(主键名,主键值)].append((字段名,字段值))
#print("编辑信息字典", 编辑信息字典)
## 根据 编辑信息字典 制作数据库语句
L_SQL_CMD = []
for i in 编辑信息字典:
#print("K (主键名,主键值)", i, "V [(字段名,新值),(字段名,新值)]", 编辑信息字典[i])
主键名,主键值 = i
修改字段列表 = 编辑信息字典[i]
修改字段数量 = len(修改字段列表)
S = ''
for i in range(0, 修改字段数量):
字段名,字段值 = 修改字段列表[i]
if i == 0:
S += f'{字段名} = "{字段值}"'
else:
S += f', {字段名} = "{字段值}"'
SQL_CMD = f'UPDATE {数据表名} SET {S} WHERE {主键名} = "{主键值}"'
L_SQL_CMD.append(SQL_CMD)
#print("L_SQL_CMD", L_SQL_CMD)
if L_SQL_CMD == []:
WARNING = '用户没有修改内容'
print(WARNING)
else:
## 依次执行SQL语句
成功列表 = []
失败列表 = []
#剩余列表 = []
for i in L_SQL_CMD:
RR = DEF_SQL_执行(i)
if RR[0] == 0:
成功列表.append(i)
INFO = f'数据库 {DB_FULL_NAME.get()} 执行SQL语句 {i} 成功'
TEXT_数据库变动日志.insert(0.0, INFO+'\n')
Log.info(INFO)
else:
ERROR = f'数据库 {DB_FULL_NAME.get()} 执行SQL语句 {i} 失败 {RR[1]}'
失败列表.append(ERROR)
#print("成功列表", 成功列表)
print("失败列表", 失败列表)
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)
else:
ERROR = f'主键字段{L_PK_NAME}未包含在当前查询结果中,当前查询字段列表{查询字段列表}'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
ERROR = f'查询数据表 {数据表名} 的字段信息失败 {R[1]}'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
## 按钮任务完成后,后续操作
按钮_确认修改数据库.grid_forget() # 隐藏 按钮_确认修改数据库
## 成功后,更新显示表格
###UPDATE_SELECT()
UPDATE_SELECT_LINIT()
## 执行用户输入的SQL语句
def DEF_按钮_执行SQL语句():
SQL_CMD = 文本框_命令行.get(1.0, END).rstrip('\n') # 获取编写的SQL语句,去掉后面的回车符号
if SQL_CMD.strip() != '':
## 区别处理查询语句和其他语句
if SQL_CMD.lstrip()[0:6].lower() == 'select':
DEF_SQL_查询和显示(SQL_CMD) # 调用查询语句专用函数
else:
R = DEF_SQL_执行(SQL_CMD) # 调用非查询语句函数
if R[0] == 0:
## 操作成功后更新一下显示/编辑框
UPDATE_SELECT()
INFO = f'数据库 {DB_FULL_NAME.get()} 执行SQL语句 {SQL_CMD} 成功'
TEXT_数据库变动日志.insert(0.0, INFO+'\n')
Log.info(INFO)
tkinter.messagebox.showinfo(title='成功', message=INFO)
else:
ERROR = f'数据库 {DB_FULL_NAME.get()} 执行SQL语句 {SQL_CMD} 失败 {R[1]}'
TEXT_数据库变动日志.insert(0.0, ERROR+'\n')
Log.error(ERROR)
tkinter.messagebox.showerror(title='失败', message=ERROR)
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:
## 操作成功后更新一下显示/编辑框
UPDATE_SELECT()
INFO = f'数据库 {DB_FULL_NAME.get()} 执行SQL脚本 {SQL_CMD} 成功'
TEXT_数据库变动日志.insert(0.0, INFO+'\n')
Log.info(INFO)
tkinter.messagebox.showinfo(title='成功', message=INFO)
else:
ERROR = f'数据库 {DB_FULL_NAME.get()} 执行SQL脚本 {SQL_CMD} 失败 {R[1]}'
TEXT_数据库变动日志.insert(0.0, ERROR+'\n')
Log.error(ERROR)
tkinter.messagebox.showerror(title='失败', message=ERROR)
else:
ERROR = 'SQL脚本无内容'
tkinter.messagebox.showerror(title='错误', message=ERROR)
## 清除命令框里的内容
def DEF_按钮_清屏():
文本框_命令行.delete(0.0, END)
文本框_命令行.focus_set()
################
## 事件函数区 ##
################
## 数据库表菜单
def 弹出_数据库表_右键菜单(event):
光标Y轴值 = event.y
print("光标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 OPEN_TABLE():
数据库表名 = DB_TABLE_NAME.get()
## 打开数据表(查询表内容)
SQL_CMD = f'SELECT * FROM {数据库表名}'
DEF_SQL_查询和显示(SQL_CMD)
def ADD_TABLE():
print("新建表")
DEF_弹出创建数据表窗口()
def DEL_TABLE():
print("删除表")
数据库表名 = DB_TABLE_NAME.get()
用户决定 = tkinter.messagebox.askquestion(title='请三思...', message='是否确定删除数据表: '+数据库表名) # 返回值为:yes/no
if 用户决定 == 'yes':
print("确定删除表")
SQL_CMD = f'DROP TABLE {数据库表名}' # 删除表的SQL语句
R = DEF_SQL_执行(SQL_CMD)
if R[0] == 0:
print("删除数据表成功")
DEF_查询数据库表() # 重新查询数据库表
else:
print("删除数据表失败")
ERROR = SQL_CMD + '\n' + R[1]
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
print("取消删除表")
def EDIT_TABLE():
print("编辑表")
INFO = 'SQLite3 没有这个功能,请使用新建表替换旧表'
tkinter.messagebox.showinfo(title='提示', message=INFO)
## 导出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]
#print(字段信息)
F_CSV.writerow(字段信息)
for 记录 in 数据记录:
#print(记录)
F_CSV.writerow(记录)
F.close()
INFO = f'导出数据库"{导出数据库名}"中的数据表"{导出数据表名}"成功\n导出文件为"{导出文件名}"'
tkinter.messagebox.showinfo(title='INFO', message=INFO)
else:
F.close()
ERROR = R[1]
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_%H%M%S') + '].csv'
导出文件名 = tkinter.simpledialog.askstring(title='导出文件名', prompt='请输入导出文件名:', initialvalue=默认导出文件名)
print(导出文件名) # 确定为输入内容,取消为None
if 导出文件名 == None:
print("取消导出")
else:
## 检查文件名是否可用
if os.path.exists(导出文件名): # 判断 目录、文件 是否存在
数据表导出CSV() # 文件名已经被使用,循环操作,直到用户输入不重复的文件名或取消
else:
CSV导出一个表(导出文件名, 数据库名, 数据表名)
def 数据库导出CSV():
print("数据库导出CSV")
用户选择结果 = tkinter.messagebox.askyesno(title='数据库导出(csv)', message='导出数据库内全部数据表\n一个CSV只能存储一张表,是否分成多个文件存储') # 返回值为:True或者False
print(用户选择结果)
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_%H%M%S') + '].csv'
CSV导出一个表(导出文件名, 数据库名, 数据表名)
def DEF_按钮_显编框数据导出为CSV文件():
if LabelFrame_显编框.winfo_children() == []: # 当显示编辑框内组件被销毁后
ERROR = '无法导出数据:显编框内无数据'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
默认导出文件名 = '导出部分数据 ['+ time.strftime('%Y%m%d_%H%M%S') + '].csv'
导出文件名 = tkinter.simpledialog.askstring(title='导出显编框内数据(csv)', prompt='所见即所得\n请输入导出文件名:', initialvalue=默认导出文件名)
print(导出文件名) # 确定为输入内容,取消为None
if 导出文件名 == 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 = 0
F.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_%H%M%S') + '].xlsx'
导出文件名 = tkinter.simpledialog.askstring(title='导出显编框内数据(xlsx)', prompt='所见即所得\n请输入导出文件名:', initialvalue=默认导出文件名)
# 确定为输入内容,取消为None
if 导出文件名 == None:
print("取消导出")
else:
## 检查文件名是否可用
if os.path.exists(导出文件名): # 判断 目录、文件 是否存在
DEF_按钮_显编框数据导出为Excel文件() # 文件名已经被使用,循环操作,直到用户输入不重复的文件名或取消
else:
try:
from openpyxl import Workbook
except 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 = 0
EXCEL文件.save(导出文件名)
INFO = f'导出数据"{导出文件名}"成功'
tkinter.messagebox.showinfo(title='INFO', message=INFO)
## 导出EXCEL文件
def 数据表导出EXCEL():
print("数据表导出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=默认导出文件名)
print(导出文件名) # 确定为输入内容,取消为None
if 导出文件名 == None:
print("取消导出")
else:
## 检查文件名是否可用
if os.path.exists(导出文件名): # 判断 目录、文件 是否存在
数据表导出EXCEL() # 文件名已经被使用,循环操作,直到用户输入不重复的文件名或取消
else:
try:
from openpyxl import Workbook
except Exception as e:
ERROR = f'导出数据库"{数据库名}"中的数据表"{数据表名}"失败\n错误信息:{e}\n请检查写入权限或openpyxl模块的安装和加载'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
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)
else:
ERROR = R[1]
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
def 数据库导出EXCEL():
print("数据库导出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=默认导出文件名)
## 确定为输入内容,取消为None
if 导出文件名 == None:
print("取消导出")
else:
## 检查文件名是否可用
if os.path.exists(导出文件名): # 判断 目录、文件 是否存在
数据库导出EXCEL() # 文件名已经被使用,循环操作,直到用户输入不重复的文件名或取消
else:
try:
from openpyxl import Workbook
except 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=OPEN_TABLE)
数据库表_右键菜单_表名处.add_command(label='删除表', command=DEL_TABLE)
数据库表_右键菜单_表名处.add_command(label='编辑表', command=EDIT_TABLE)
数据库表_右键菜单_表名处.add_command(label='导出表(CSV)', command=数据表导出CSV)
数据库表_右键菜单_表名处.add_command(label='导出表(Excel)最大1048575行记录', command=数据表导出EXCEL)
## 数据表列表框-空白处右键菜单
数据库表_右键菜单_空白处 = Menu()
数据库表_右键菜单_空白处.add_command(label='创建表', command=ADD_TABLE)
数据库表_右键菜单_空白处.add_command(label='导出库(CSV)', command=数据库导出CSV)
数据库表_右键菜单_空白处.add_command(label='导出库(Excel)最大1048575行记录', command=数据库导出EXCEL)
## 字段框右键菜单
def DEF_弹出_字段框_右键菜单(event):
# 取值
选中控件 = event.widget
行 = 0 # 字段名只有1行,恒等于0
列 = 选中控件.grid_info()['column']
# 赋值
字段框_定位列.set(列)
## 选择的控件变红
#选中控件['bg'] = 'red'
## 弹出菜单
字段框_右键菜单.post(event.x_root, event.y_root) # 光标位置显示菜单
## 菜单函数
def ADD_COL():
print("添加列(添加字段)")
DEF_弹出新加字段窗口()
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 ADD_DATA():
print("添加数据记录")
DEF_新增记录() # 调用新增记录函数
# 创建字段框右键菜单
字段框_右键菜单 = Menu()
字段框_右键菜单.add_command(label='添加列(添加字段)', command=ADD_COL)
字段框_右键菜单.add_command(label='删除列(删除字段)', command=DEL_COL)
字段框_右键菜单.add_command(label='编辑列(修改字段)', command=EDIT_COL)
字段框_右键菜单.add_separator() # 分割线
字段框_右键菜单.add_command(label='添加数据记录', command=ADD_DATA)
## 数据框:离开控件
def 离开控件(event):
# 取值
离开控件 = event.widget
离开行 = 离开控件.grid_info()['row']
离开列 = 离开控件.grid_info()['column']
## 判断内容是否有变动
#print("刚刚离开(行,列)", (离开行,离开列))
if 数据框_定位行.get() == 离开行 and 数据框_定位列.get() == 离开列: # 应该是多余的判断,先留着DEBUG
新值 = 字典_查询结果_坐标_对象[(离开行,离开列)].get()
#print("刚刚离开的新值", 新值)
旧值 = 字典_查询结果_坐标_初值[(离开行,离开列)]
#print("刚刚离开的旧值", 旧值)
if 新值 == 旧值:
print("离开控件:无变化")
离开控件['bg'] = '#FFFFFF' # 无变化还原白底
#解禁分页按钮
按钮_显编框下一页['state'] = 'normal'
else:
print("离开控件:有变化")
离开控件['bg'] = '#7FFF00' # 有变化改成草绿
按钮_确认修改数据库.grid() # 显示修改数据库的按钮
# 禁止分页按钮
按钮_显编框下一页['state'] = 'disabled' # 禁止下一页按钮
else:
print("从其他地方离开,忽略")
字典_查询结果_坐标_对象[(离开行,离开列)].unbind('') # 离开后解除控件的离开事件
文本框_命令行.focus_set() # 焦点移到命令文本输入框
## 数据框:左键单击
def 左键单击(event):
# 取值
选中控件 = event.widget
行 = 选中控件.grid_info()['row']
列 = 选中控件.grid_info()['column']
#选中控件['bg'] = '#7FFF00'
# 赋值
数据框_定位行.set(行)
数据框_定位列.set(列)
字典_查询结果_坐标_对象[(行,列)].bind('', 离开控件) # 单击是进入编辑,给这个控件加个离开事件
## 数据框:右键菜单
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 ADD_ROW():
DEF_新增记录()
## 菜单按钮 删除整行 动作函数
def DEL_ROW():
控件行号 = 数据框_定位行.get()
DEF_删除记录(控件行号)
## 删除成功后的行列号和当前行列号有差别,立刻设置为无效行列号,防止后面误删
数据框_定位行.set(-1)
数据框_定位列.set(-1)
def DEF_删除记录(控件行号):
数据表名 = DB_TABLE_NAME.get()
字段名列表 = DB_INFO['字段名列表']
## 找主键名:查数据库表得到主键信息 L_PK_NAME
SQLite3_CMD = f'PRAGMA table_info({数据表名})'
R = DEF_SQLite3_CMD(SQLite3_CMD)
if R[0] == 0:
查询结果 = R[1]
L_PK_NAME = PK(查询结果)
if L_PK_NAME == []:
ERROR = f'数据表“{数据表名}”没有主键'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
## 多个主键的情况下也只要找到其中一个存在于显示/编辑框的主键即可
主键信息 = ''
for 主键名 in L_PK_NAME:
for 列号 in range(0, len(字段名列表)):
if 字段名列表[列号] == 主键名:
主键信息 = (列号,主键名)
break
if 主键信息 != '':
(列号,主键名) = 主键信息 # 获得主键名
控件列号 = 列号
主键值 = 字典_查询结果_坐标_初值[(控件行号,控件列号)] # 获得主键值
#print("主键名", 主键名, "主键值", 主键值)
SQL_CMD = f'DELETE FROM {数据表名} WHERE {主键名} = "{主键值}"'
#print("SQL_CMD", SQL_CMD)
RR = DEF_SQL_执行(SQL_CMD)
if RR[0] == 0:
## 操作成功后更新一下显示/编辑框
###UPDATE_SELECT()
UPDATE_SELECT_LINIT()
INFO = f'数据库 {DB_FULL_NAME.get()} 执行SQL语句 {SQL_CMD} 成功'
TEXT_数据库变动日志.insert(0.0, INFO+'\n')
Log.info(INFO)
else:
ERROR = f'数据库 {DB_FULL_NAME.get()} 执行SQL语句 {SQL_CMD} 失败 {RR[1]}'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
ERROR = f'主键字段{L_PK_NAME}未包含在当前查询结果中,当前查询字段列表{字段名列表}'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
else:
ERROR = f'查询数据库“{数据库名}”的数据表“{数据表名}”的字段信息失败:{R[1]}'
tkinter.messagebox.showerror(title='ERROR', message=ERROR)
# 右键菜单
数据框_右键菜单 = Menu()
数据框_右键菜单.add_command(label='添加新行', command=ADD_ROW)
数据框_右键菜单.add_command(label='删除整行', command=DEL_ROW)
数据框_右键菜单.add_separator()
数据框_右键菜单.add_command(label='大文本编辑', command=DEF_弹出大文本窗口)
## 顶框 ======================================================================================================================
数据库文件操作框 = LabelFrame(顶框, text='数据库文件操作框')
数据库文件操作框.grid(row=0,column=0,sticky='NW') #0-0
## 数据库文件操作框 ------------------------------------------------------------------------------------------------------------
Button(数据库文件操作框, text='选择数据库文件', command=DEF_按钮_选择数据库文件).grid(row=0,column=0,sticky='NW') #00
数据库路径 = Entry(数据库文件操作框, textvariable=DB_FULL_NAME, width=123).grid(row=1,column=0,sticky='NW',columnspan=2) #10
按钮_打开数据库 = Button(数据库文件操作框, text='打开/新建数据库', command=DEF_按钮_打开数据库)
按钮_打开数据库.grid(row=2,column=0,sticky='NW') #20
Button(数据库文件操作框, text='关闭数据库', command=DEF_按钮_关闭数据库).grid(row=2,column=1,sticky='E') #21
## 左侧框 ======================================================================================================================
Frame_数据表列表显示框 = LabelFrame(左侧框, text='数据库内的数据表')
全局变量框 = LabelFrame(左侧框, text='全局变量框,实时更新,请勿修改')
#Frame_数据表列表显示框.grid(row=1,column=0,sticky='NW') #1-0 # 由打开数据库按钮控制显示
全局变量框.grid(row=2,column=0,sticky='NW') #2-0
## Frame_数据表列表显示框 ------------------------------------------------------------------------------------------------------
STR_数据表列表内容 = StringVar() # 实时更新变量
## Listbox 列表控件
Listbox_数据表列表 = Listbox(Frame_数据表列表显示框, listvariable=STR_数据表列表内容, height=8, width=30) # 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('', DEF_双击表名) # 绑定双击命令
Listbox_数据表列表.bind('', 弹出_数据库表_右键菜单) # 绑定右键菜单事件
## 控件布局
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
Button(Frame_数据表列表显示框, text='新建表', command=ADD_TABLE).grid(row=2,column=0,sticky='W') # 20
## 全局变量框 -------------------------------------------------------------------------------------------------------------------
数据库信息框 = 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='W', columnspan=2)
显编框_字段框 = LabelFrame(全局变量框, text='显编框.字段框定位')
Label(显编框_字段框, text='[列]').grid(row=0,column=0,sticky='W')
Entry(显编框_字段框, textvariable=字段框_定位列, width=3).grid(row=0,column=1,sticky='W')
显编框_字段框.grid(row=1,column=0,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=1,column=1,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=2,column=0,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=2,column=1,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=3,column=0,sticky='W')
## 右侧框 =======================================================================================================================
LabelFrame_显编框 = LabelFrame(右侧框, text='显示/编辑框', bg='#FFD700')
分页按钮框 = Frame(右侧框)
修改确认框 = Frame(右侧框)
分隔填充1 = Frame(右侧框)
命令框 = LabelFrame(右侧框, text='SQL语句/SQL脚本')
# 框架的位置布局
LabelFrame_显编框.grid(row=0,column=0,sticky='NW') #0-0
分页按钮框.grid(row=1,column=0,sticky='NW') #1-0
修改确认框.grid(row=2,column=0,sticky='NW') #2-0
分隔填充1.grid(row=3,column=0,sticky='NW') #3-0
命令框.grid(row=4,column=0,sticky='NW') #4-0
Label(分隔填充1, text='\n').grid()
#######################
## LabelFrame_显编框 ##
#######################
####################################################################################
## 分页按钮框 ######################################################################
####################################################################################
按钮_显编框起始页 = Button(分页按钮框, text='返回起始页/刷新', command=DEF_按钮_显编框起始页)
按钮_显编框下一页 = Button(分页按钮框, text='下一页', command=DEF_按钮_显编框下一页)
按钮_显编框起始页.grid(row=0,column=0, sticky='NW') # 显示起始页按钮
按钮_显编框下一页.grid(row=0,column=1, sticky='NW') # 显示下一页按钮
Label(分页按钮框, text='[分页显示行数]').grid(row=0,column=2,sticky='E')
Combobox_分页显示行数 = ttk.Combobox(分页按钮框, width=6)
Combobox_分页显示行数['value'] = (5, 10, 20, 50, 100, 200)
Combobox_分页显示行数.current(0) # 默认值中的内容为索引,从0开始
Combobox_分页显示行数.grid(row=0, column=3, sticky='E')
def 选择后执行函数(event):
分页行数.set(Combobox_分页显示行数.get())
Combobox_分页显示行数.bind('<>', 选择后执行函数)
Button(分页按钮框, text='可见部分数据导出(CSV)', command=DEF_按钮_显编框数据导出为CSV文件).grid(row=0,column=4,sticky='E')
Button(分页按钮框, text='可见部分数据导出(Excel)', command=DEF_按钮_显编框数据导出为Excel文件).grid(row=0,column=5,sticky='E')
## 不用时禁止
#按钮_显编框起始页['state'] = 'disabled'
按钮_显编框下一页['state'] = 'disabled'
## 需要时启用
#按钮_显编框起始页['state'] = 'normal'
#按钮_显编框下一页['state'] = 'normal'
##########################################################################################
## 修改确认框 ############################################################################
##########################################################################################
按钮_确认修改数据库 = Button(修改确认框, text='确认修改', command=DEF_按钮_确认修改数据库)
#进行编辑后再出现
按钮_确认修改数据库.grid(row=0,column=0, sticky='NW')
按钮_确认修改数据库.grid_forget() # 隐藏
#按钮_确认修改数据库.grid() # 显示
#############################################################################################
## 命令框 ###################################################################################
#############################################################################################
本地SQL脚本文件操作框 = Frame(命令框)
本地SQL脚本文件操作框.grid(row=0,column=0)
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')
命令行_按钮框 = Text(命令框)
命令行_按钮框.grid(row=1,column=0)
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=6, wrap='none')
文本框_命令行.grid(row=2,column=0,sticky='NESW')
Scrollbar_命令框_竖 = Scrollbar(命令框)
Scrollbar_命令框_竖['command'] = 文本框_命令行.yview
Scrollbar_命令框_横 = Scrollbar(命令框)
Scrollbar_命令框_横['command'] = 文本框_命令行.xview
Scrollbar_命令框_横['orient'] = HORIZONTAL
Scrollbar_命令框_竖.grid(row=2, column=1, sticky=S+W+E+N)
Scrollbar_命令框_横.grid(row=3, column=0, sticky=S+W+E+N)
文本框_命令行.config(xscrollcommand=Scrollbar_命令框_横.set, yscrollcommand=Scrollbar_命令框_竖.set) # 自动设置滚动条滑动幅度
######################################################################################################################################
## 日志框 ############################################################################################################################
######################################################################################################################################
TEXT_数据库变动日志 = Text(日志框, width=120, height=3, wrap='none') # 显示改动了数据库的操作日志
TEXT_数据库变动日志.grid(row=0, column=0, sticky='NW')
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) # 自动设置滚动条滑动幅度
# 进入消息循环
top.mainloop()