使用Python3自帶GUI做個圖形化操作SQLite3數據庫的工具

使用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()

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章