使用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("<Button-3>", DEF_彈出_字段框_右鍵菜單) # 每個控件對象都綁定右鍵菜單事件
## 數據值
for 行 in range(0, 行數):
for 列 in range(0, 列數):
初始值 = str(LL[行][列]) # 轉成字符串
字典_查詢結果_座標_對象[(行,列)] = Entry(數據框) # 控件對象放到指定框內,並保存對象到對象字典中
字典_查詢結果_座標_初值[(行,列)] = 初始值 # 保存初始值
字典_查詢結果_座標_對象[(行,列)].insert(0, 初始值) # 寫入Entry作爲初始值(從標籤內開頭開始填充新值,因爲是全新標籤,所有不用先刪除裏面內容)
字典_查詢結果_座標_對象[(行,列)].grid(row=行,column=列,sticky='W') # Entry排放到指定位置
字典_查詢結果_座標_對象[(行,列)].bind("<Button-1>", 左鍵單擊) # 每個控件對象都綁定左鍵單擊事件
字典_查詢結果_座標_對象[(行,列)].bind("<Button-3>", 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('<Leave>') # 離開後解除控件的離開事件
文本框_命令行.focus_set() # 焦點移到命令文本輸入框
## 數據框:左鍵單擊
def 左鍵單擊(event):
# 取值
選中控件 = event.widget
行 = 選中控件.grid_info()['row']
列 = 選中控件.grid_info()['column']
#選中控件['bg'] = '#7FFF00'
# 賦值
數據框_定位行.set(行)
數據框_定位列.set(列)
字典_查詢結果_座標_對象[(行,列)].bind('<Leave>', 離開控件) # 單擊是進入編輯,給這個控件加個離開事件
## 數據框:右鍵菜單
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('<Double-Button-1>', DEF_雙擊表名) # 綁定雙擊命令
Listbox_數據表列表.bind('<Button-3>', 彈出_數據庫表_右鍵菜單) # 綁定右鍵菜單事件
## 控件佈局
Listbox_數據表列表.grid(row=0,column=0,sticky='NW') #00
Scrollbar_數據表列表_橫.grid(row=1,column=0,sticky=S+W+E+N) #10
Scrollbar_數據表列表_豎.grid(row=0,column=1,sticky=S+W+E+N) #01
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('<<ComboboxSelected>>', 選擇後執行函數)
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()