Python3 密碼存儲軟件

增加密文私鑰功能

 

 


#_*_ coding:utf8 _*_

## Python3 密碼管理 V2.2
## 2020-06-05
## 需要安裝第三方模塊 pycryptodome
## 安裝方式 pip install pycryptodome
## 1 生成密鑰對,設置要保存的用戶名和密碼
## 2 對【明文密碼】使用公鑰進行加密後存入本地SQLite3數據庫
## 3 提取數據庫中【密文密碼】使用私鑰解密爲【明文密碼】直接複製到粘貼板中

import os               # 操作Windows文件用
import time
import random

## pycryptodome
from Crypto import Random
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5

## 數據庫
import sqlite3

## 圖形化模塊
from tkinter import *
from tkinter import filedialog  # 選擇文件用
import tkinter.messagebox       # 彈出提示對話框
from tkinter import ttk         # 下拉菜單控件在ttk中

top = Tk()                          # 初始化Tk()
top.title('Python3 密碼管理 V2.2')  # 設置標題
width = 820                         # 設置窗口寬
height = 800                        # 設置窗口高
# 獲取屏幕尺寸以計算佈局參數,使窗口居屏幕中央
screenwidth = top.winfo_screenwidth()  
screenheight = top.winfo_screenheight() 
alignstr = '%dx%d+%d+%d' % (width, height, (screenwidth-width)/2, (screenheight-height)/2)   
top.geometry(alignstr)
top.resizable(width=True, height=True)  # 設置窗口是否可變長、寬,True:可變,False:不可變



######## Sqlite3 SQL 語句函數
##
## 數據庫操作:執行SQL查詢語句,返回執行狀態和執行結果(數據列表)
def DEF_SQL_查詢和返回(SQL_CMD):
    數據庫連接對象 = sqlite3.connect('PWD.DB')     # 嘗試打開數據庫文件
    print("打開連接")
    try:
        遊標對象 = 數據庫連接對象.cursor()     # 創建一個遊標
    except Exception as e:
        ERROR = '創建遊標失敗' + str(e)
        return(1, ERROR)
    else:
        print("創建遊標")
        try:
            遊標對象.execute(SQL_CMD)
        except Exception as e:
            ERROR = f'執行SQL語句 {SQL_CMD} 失敗 {e}'
            遊標對象.close()
            print("關閉遊標")
            return(1, ERROR)
        else:
            全部記錄 = 遊標對象.fetchall()
            遊標對象.close()
            print("關閉遊標")
            return(0, 全部記錄)

## 執行一條SQL語句
def DEF_SQL_執行(SQL_CMD):
    數據庫連接對象 = sqlite3.connect('PWD.DB')     # 嘗試打開數據庫文件
    print("打開連接")
    try:
        遊標對象 = 數據庫連接對象.cursor()         # 創建一個遊標
    except Exception as e:
        ERROR = '創建遊標失敗' + str(e)
        return(1, ERROR)
    else:
        print("創建遊標")
        try:
            遊標對象.execute(SQL_CMD)
        except Exception as e:
            遊標對象.close()
            print("關閉遊標")
            ERROR = str(e)
            return(1, ERROR)
        else:
            數據庫連接對象.commit()           # 提交更改
            遊標對象.close()
            print("關閉遊標")
            數據庫連接對象.close()
            print("關閉連接")
            return(0,)

## 執行SQL腳本
def DEF_SQL_執行腳本(SQL_SCRIPT):
    數據庫連接對象 = sqlite3.connect('PWD.DB')     # 嘗試打開數據庫文件
    print("打開連接")
    try:
        遊標對象 = 數據庫連接對象.cursor()
    except Exception as e:
        ERROR = str(e)
        return(1, ERROR)
    else:
        print("創建遊標")
        try:
            遊標對象.executescript(SQL_SCRIPT)      # 執行SQL腳本
        except Exception as e:
            遊標對象.close()
            print("關閉遊標")
            ERROR = str(e)
            return(1, ERROR)
        else:
            數據庫連接對象.commit()                 # 提交更改
            遊標對象.close()
            print("關閉遊標")
            數據庫連接對象.close()
            print("關閉連接")
            return(0,)

## 初始化數據庫:PWD表不存在則新建表
def 初始化數據庫():
    ## 如果數據表 PWD 不存在則新建
    SQL_CMD = '''CREATE TABLE IF NOT EXISTS PWD(
    ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
    PS TEXT,
    USER TEXT,
    PASS TEXT);'''
    R = DEF_SQL_執行(SQL_CMD)
    if R[0] == 0:
        print("初始化數據表 PWD 成功")
    else:
        ERROR = f'初始化數據表 PWD 失敗:{R[1]}'
        tkinter.messagebox.showerror(title='ERROR', message=ERROR)

## 打開/刷新數據表
def 打開數據庫():
    ## 打開數據庫,讀取數據庫密碼錶內容
    SQL_CMD = 'SELECT * FROM PWD'
    R = DEF_SQL_查詢和返回(SQL_CMD)
    if R[0] == 0:
        print("數據庫表 PWD 讀取成功")
        數據列表 = R[1]
        #print("數據列表", 數據列表)
        ## 顯示在界面
        字段列表 = ['ID', 'PS', 'USER', 'PASS']
        字段和數據的存儲和展示(字段列表, 數據列表)
    else:
        ERROR = f'數據庫表 PWD 讀取失敗:{R[1]}'
        print(ERROR)
        tkinter.messagebox.showerror(title='ERROR', message=ERROR)
##
########


######## 新密鑰對操作框
##
def 生成密鑰對_明文私鑰():
    當前設置密鑰長度 = IV_密鑰長度.get()
    隨機數生成器 = Random.new().read
    密鑰對 = RSA.generate(當前設置密鑰長度, 隨機數生成器)
    
    時間 = time.strftime('%Y%m%d_%H%M%S')
    
    ## 保存公鑰
    公鑰 = 密鑰對.publickey().exportKey()                        # 導出公鑰
    新公鑰文件名 = f'Python3_Crypto_RSA_{當前設置密鑰長度}_PUBLIC_{時間}.pem'
    with open(新公鑰文件名, 'wb') as f:
        f.write(公鑰)
    print("新公鑰文件名", 新公鑰文件名)
    
    ## 保存私鑰
    私鑰 = 密鑰對.exportKey()                                    # 導出明文私鑰
    新私鑰文件名 = f'Python3_Crypto_RSA_{當前設置密鑰長度}_PRIVATE_{時間}.pem'
    with open(新私鑰文件名, 'wb') as f:
        f.write(私鑰)
    print("新私鑰文件名(明文私鑰)", 新私鑰文件名)
    
    INFO = '生成新密鑰對完成,長度爲:' + str(當前設置密鑰長度)
    SV_新密鑰對提示信息.set(INFO)
    ## 自動替換爲新生成密鑰對
    SV_選擇公鑰文件.set(新公鑰文件名)
    Label_公鑰提示信息['fg'] = 'green'
    SV_公鑰提示信息.set('自動替換爲新生成的公鑰文件')
    SV_選擇私鑰文件.set(新私鑰文件名)
    Label_私鑰提示信息['fg'] = 'green'
    SV_私鑰提示信息.set('自動替換爲新生成的私鑰文件')

def 生成密鑰對_密文私鑰():
    當前設置密鑰長度 = IV_密鑰長度.get()
    私鑰密碼 = SV_新私鑰密碼.get().strip()
    if 私鑰密碼 == '':
        print("密文私鑰需要設置密碼")
        Label_新密鑰對提示信息['fg'] = 'red'
        SV_新密鑰對提示信息.set('密文私鑰需要設置密碼')
    else:
        隨機數生成器 = Random.new().read
        密鑰對 = RSA.generate(當前設置密鑰長度, 隨機數生成器)
        
        時間 = time.strftime('%Y%m%d_%H%M%S')
        
        ## 保存公鑰
        公鑰 = 密鑰對.publickey().exportKey()                        # 導出公鑰
        新公鑰文件名 = f'Python3_Crypto_RSA_{當前設置密鑰長度}_PUBLIC_{時間}.pem'
        with open(新公鑰文件名, 'wb') as f:
            f.write(公鑰)
        print("新公鑰文件名", 新公鑰文件名)
        
        ## 保存私鑰
        私鑰 = 密鑰對.exportKey(passphrase=私鑰密碼, pkcs=8, protection='scryptAndAES128-CBC')   # 導出密文私鑰
        新私鑰文件名 = f'Python3_Crypto_RSA_{當前設置密鑰長度}_PRIVATE_{時間}.pem'
        with open(新私鑰文件名, 'wb') as f:
            f.write(私鑰)
        print("新私鑰文件名(密文私鑰)", 新私鑰文件名)
        
        SV_新私鑰密碼.set('')    # 清空私鑰密碼輸入框
        
        INFO = '生成新密鑰對完成,長度爲:' + str(當前設置密鑰長度)
        SV_新密鑰對提示信息.set(INFO)
        ## 自動替換爲新生成密鑰對
        SV_選擇公鑰文件.set(新公鑰文件名)
        Label_公鑰提示信息['fg'] = 'green'
        SV_公鑰提示信息.set('自動替換爲新生成的公鑰文件')
        SV_選擇私鑰文件.set(新私鑰文件名)
        Label_私鑰提示信息['fg'] = 'green'
        SV_私鑰提示信息.set('自動替換爲新生成的私鑰文件')

def 生成新密鑰對():
    if 私鑰是否加密.get():
        print("密文私鑰")
        生成密鑰對_密文私鑰()
    else:
        print("文明私鑰")
        生成密鑰對_明文私鑰()

新密鑰對操作框 = LabelFrame(top, text='新密鑰對操作框')
IV_密鑰長度 = IntVar()
IV_密鑰長度.set(1024)               # 設置默認值 1024
SV_新私鑰密碼 = StringVar()
SV_新密鑰對提示信息 = StringVar()
SV_新密鑰對提示信息.set('生成密鑰需要等待數秒時間')

Label(新密鑰對操作框, text='設置新密鑰長度').grid(row=0, column=0, sticky='E')                # 新密鑰對操作框 0-0
Combobox_密鑰長度 = ttk.Combobox(新密鑰對操作框, width=5)
Combobox_密鑰長度['value'] = (1024, 2048, 3072, 4096)
Combobox_密鑰長度.current(0)                            # 默認值中的內容爲索引,從0開始
Combobox_密鑰長度.grid(row=0, column=1, sticky='E')                                           # 新密鑰對操作框 0-1
def 選擇後執行函數(event):
    設置新密鑰長度 = Combobox_密鑰長度.get()
    IV_密鑰長度.set(設置新密鑰長度)
    print("設置新密鑰長度", 設置新密鑰長度)
Combobox_密鑰長度.bind('<<ComboboxSelected>>', 選擇後執行函數)
Label_新密鑰對提示信息 = Label(新密鑰對操作框, textvariable=SV_新密鑰對提示信息)
Label_新密鑰對提示信息['fg'] = '#FF6600'
Label_新密鑰對提示信息.grid(row=0, column=2)                                                  # 新密鑰對操作框 0-2

## 私鑰是否加密
def DEF_選擇按鈕():
    print(私鑰是否加密.get())
    if 私鑰是否加密.get():
        print("密文私鑰")
    else:
        print("文明私鑰")
私鑰是否加密 = BooleanVar()
選擇按鈕 = Checkbutton(新密鑰對操作框, text='加密私鑰', variable=私鑰是否加密, command=DEF_選擇按鈕)
選擇按鈕.grid(row=1, column=0)                                                                         # 新密鑰對操作框 1-0

Label(新密鑰對操作框, text='設置新私鑰密碼').grid(row=1, column=1, sticky='W')                         # 新密鑰對操作框 1-1
Entry(新密鑰對操作框, textvariable=SV_新私鑰密碼, width=20, show='*').grid(row=1, column=2)            # 新密鑰對操作框 1-2
Button(新密鑰對操作框, text='生成新密鑰對', command=生成新密鑰對).grid(row=1, column=3, sticky='NW')   # 新密鑰對操作框 1-3

新密鑰對操作框.grid(row=0, column=0, sticky='NW')      ## top 0-0
##
########


######## 公鑰文件框
##
SV_選擇公鑰文件 = StringVar()       ## TK全局變量:可以實時更新顯示
SV_公鑰提示信息 = StringVar()       ## 顯示公鑰文件的情況

def DEF_按鈕_選擇公鑰文件():
    選擇文件 = filedialog.askopenfilename()
    SV_選擇公鑰文件.set(選擇文件)
    print("SV_選擇公鑰文件", SV_選擇公鑰文件)
    ## 選擇文件後更新一下提示信息
    Label_公鑰提示信息['fg'] = 'green'
    SV_公鑰提示信息.set('使用手動選擇公鑰文件')

公鑰操作框 = LabelFrame(top, text='公鑰')
Button(公鑰操作框, text='選擇公鑰文件', command=DEF_按鈕_選擇公鑰文件).grid(row=0, column=0, sticky='NW')
Entry(公鑰操作框, textvariable=SV_選擇公鑰文件, width=55).grid(row=0, column=1)
Label_公鑰提示信息 = Label(公鑰操作框, textvariable=SV_公鑰提示信息)
Label_公鑰提示信息.grid(row=0, column=2)

默認公鑰文件 = '公鑰.pem'
if os.path.isfile(默認公鑰文件):
    SV_選擇公鑰文件.set(默認公鑰文件)           ## 設置公鑰默認存放位置爲程序同目錄下,方便使用
    Label_公鑰提示信息['fg'] = 'green'
    SV_公鑰提示信息.set('自動選擇默認公鑰文件')
else:
    Label_公鑰提示信息['fg'] = '#FF6600'
    SV_公鑰提示信息.set('加密需要先選擇公鑰')

公鑰操作框.grid(row=1, column=0, sticky='NW')        ## top 1-0
##
########


######## 私鑰文件框
##
SV_選擇私鑰文件 = StringVar()
SV_私鑰提示信息 = StringVar()

def DEF_按鈕_選擇私鑰文件():
    選擇文件 = filedialog.askopenfilename()
    SV_選擇私鑰文件.set(選擇文件)              # 實時更新顯示
    print("SV_選擇私鑰文件", SV_選擇私鑰文件)
    Label_私鑰提示信息['fg'] = 'green'
    SV_私鑰提示信息.set('使用手動選擇私鑰文件')

私鑰操作框 = LabelFrame(top, text='私鑰')
Button(私鑰操作框, text='選擇私鑰文件', command=DEF_按鈕_選擇私鑰文件).grid(row=0, column=0, sticky='NW')
Entry(私鑰操作框, textvariable=SV_選擇私鑰文件, width=55).grid(row=0, column=1)
Label_私鑰提示信息 = Label(私鑰操作框, textvariable=SV_私鑰提示信息)
Label_私鑰提示信息.grid(row=0, column=2)

默認私鑰文件 = '私鑰.pem'
if os.path.isfile(默認私鑰文件):
    SV_選擇私鑰文件.set(默認私鑰文件)           ## 設置私鑰默認存放位置爲程序同目錄下,方便使用(不安全,私鑰加密後可以提高安全性)
    Label_私鑰提示信息['fg'] = 'green'
    SV_私鑰提示信息.set('自動選擇默認私鑰文件')
else:
    Label_私鑰提示信息['fg'] = '#FF6600'
    SV_私鑰提示信息.set('解密需要先選擇私鑰')

私鑰操作框.grid(row=2, column=0, sticky='NW')        ## top 2-0
##
########



######## 私鑰密碼框
##
私鑰密碼框 = LabelFrame(top, text='私鑰密碼框')
Entry_私鑰密碼 = Entry(私鑰密碼框, width=15, show='*')
Entry_私鑰密碼.grid(row=0, column=0)
私鑰密碼框.grid(row=2, column=1, sticky='W')        ## top 2-1
##
########



######## 存儲操作框
##
def 明文存入():
    print("明文存入")
    備註 = Entry_備註.get().strip()
    帳號 = Entry_帳號.get().strip()
    密碼 = Entry_密碼.get().strip()
    if 備註 == '' or 帳號 == '' or 密碼 == '':
        ERROR = '備註/帳號/密碼 中有空內容'
        tkinter.messagebox.showerror(title='ERROR', message=ERROR)
    else:
        #print(備註,帳號,密碼)
        ## 存入數據庫
        SQL_插入數據 = f'INSERT INTO PWD (PS, USER, PASS) VALUES ("{備註}", "{帳號}", "{密碼}");'
        R_INSERT = DEF_SQL_執行(SQL_插入數據)
        if R_INSERT[0] == 0:
            print("插入數據成功")
            Entry_備註.delete(0, END)         # 界面:清空輸入框
            Entry_帳號.delete(0, END)         # 界面:清空輸入框
            Entry_密碼.delete(0, END)         # 界面:清空輸入框
            打開數據庫()
        else:
            #print("插入數據失敗", R_INSERT[1])
            ERROR = f'插入數據失敗:{R_INSERT[1]}'
            tkinter.messagebox.showerror(title='ERROR', message=ERROR)

def 密碼加密存入():
    print("密碼加密存入")
    備註 = Entry_備註.get().strip()
    帳號 = Entry_帳號.get().strip()
    密碼 = Entry_密碼.get().strip()
    if 備註 == '' or 帳號 == '' or 密碼 == '':
        ERROR = '備註/帳號/密碼 中有空內容'
        tkinter.messagebox.showerror(title='ERROR', message=ERROR)
    else:
        #print(備註,帳號,密碼)
        ## 明文密碼加密
        公鑰文件 = SV_選擇公鑰文件.get()
        if 公鑰文件.rstrip() == '':
            ERROR = '沒有選擇公鑰文件'
            tkinter.messagebox.showerror(title='ERROR', message=ERROR)
        else:
            try:
                f = open(公鑰文件,'r')
            except Exception as E1:
                ERROR = f'打開公鑰文件失敗:{E1}'
                print(ERROR)
                tkinter.messagebox.showerror(title='ERROR', message=ERROR)
            else:
                print("打開公鑰文件成功")
                STR_公鑰 = f.read()
                f.close()
                try:
                    OJB_公鑰 = RSA.importKey(STR_公鑰)
                except Exception as E2:
                    ERROR = f'導入公鑰失敗:{E2}'
                    print(ERROR)
                    tkinter.messagebox.showerror(title='ERROR', message=ERROR)
                else:
                    公鑰_PKCS115 = PKCS1_v1_5.new(OJB_公鑰)
                    明文字節碼 = 密碼.encode('utf8')
                    try:
                        密文字節碼 = 公鑰_PKCS115.encrypt(明文字節碼)   # 公鑰加密
                    except Exception as E3:
                        ERROR = f'公鑰加密失敗:{E3}'
                        print(ERROR)
                        tkinter.messagebox.showerror(title='ERROR', message=ERROR)
                    else:
                        print("公鑰加密成功")
                        #print(密文字節碼)
                        ## 字節類型轉成16進制字符串存儲到數據庫
                        STR_HEX = Bytes2HEX_STR(密文字節碼)
                        #print(STR_HEX)
                        
                        ## 存入數據庫
                        SQL_插入數據 = f'INSERT INTO PWD (PS, USER, PASS) VALUES ("{備註}", "{帳號}", "{STR_HEX}");'
                        R_INSERT = DEF_SQL_執行(SQL_插入數據)
                        if R_INSERT[0] == 0:
                            print("插入數據成功")
                            Entry_備註.delete(0, END)         # 界面:清空輸入框
                            Entry_帳號.delete(0, END)         # 界面:清空輸入框
                            Entry_密碼.delete(0, END)         # 界面:清空輸入框
                            打開數據庫()                      # 更新數據庫顯示框
                        else:
                            ERROR = f'插入數據失敗:{R_INSERT[1]}'
                            tkinter.messagebox.showerror(title='ERROR', message=ERROR)

def 帳號密碼加密存入():
    print("帳號和密碼都加密存儲")
    賬號加密狀態 = 0
    密碼加密狀態 = 0
    備註 = Entry_備註.get().strip()
    帳號 = Entry_帳號.get().strip()
    密碼 = Entry_密碼.get().strip()
    if 備註 == '' or 帳號 == '' or 密碼 == '':
        ERROR = '備註/帳號/密碼 中有空內容'
        tkinter.messagebox.showerror(title='ERROR', message=ERROR)
    else:
        #print(備註,帳號,密碼)
        ## 明文密碼加密
        公鑰文件 = SV_選擇公鑰文件.get()
        if 公鑰文件.rstrip() == '':
            ERROR = '沒有選擇公鑰文件'
            print(ERROR)
            tkinter.messagebox.showerror(title='ERROR', message=ERROR)
        else:
            try:
                f = open(公鑰文件,'r')
            except Exception as E1:
                ERROR = f'打開公鑰文件失敗:{E1}'
                print(ERROR)
                tkinter.messagebox.showerror(title='ERROR', message=ERROR)
            else:
                print("打開公鑰文件成功")
                STR_公鑰 = f.read()
                f.close()
                try:
                    OJB_公鑰 = RSA.importKey(STR_公鑰)
                except Exception as E2:
                    ERROR = f'導入公鑰失敗:{E2}'
                    print(ERROR)
                    tkinter.messagebox.showerror(title='ERROR', message=ERROR)
                else:
                    公鑰_PKCS115 = PKCS1_v1_5.new(OJB_公鑰)
                    
                    ## 公鑰加密帳號
                    帳號字節碼 = 帳號.encode('UTF8')
                    try:
                        密文帳號字節碼 = 公鑰_PKCS115.encrypt(帳號字節碼)   # 公鑰加密
                    except Exception as E3:
                        ERROR = f'公鑰加密賬號失敗:{E3}'
                        print(ERROR)
                        tkinter.messagebox.showerror(title='ERROR', message=ERROR)
                    else:
                        print("公鑰加密賬號成功")
                        #print(密文帳號字節碼)
                        ## 字節類型轉成16進制字符串存儲到數據庫
                        STR_HEX_帳號 = Bytes2HEX_STR(密文帳號字節碼)
                        #print(STR_HEX_帳號)
                        賬號加密狀態 = 1
                    
                    ## 公鑰加密密碼
                    密碼字節碼 = 密碼.encode('utf8')
                    try:
                        密文密碼字節碼 = 公鑰_PKCS115.encrypt(密碼字節碼)   # 公鑰加密
                    except Exception as E4:
                        ERROR = f'公鑰加密密碼失敗:{E4}'
                        print(ERROR)
                        tkinter.messagebox.showerror(title='ERROR', message=ERROR)
                    else:
                        print("公鑰加密密碼成功")
                        #print(密文密碼字節碼)
                        ## 字節類型轉成16進制字符串存儲到數據庫
                        STR_HEX_密碼 = Bytes2HEX_STR(密文密碼字節碼)
                        #print(STR_HEX_密碼)
                        密碼加密狀態 = 1

                    ## 存入數據庫
                    if 賬號加密狀態 == 密碼加密狀態 == 1:
                        SQL_插入數據 = f'INSERT INTO PWD (PS, USER, PASS) VALUES ("{備註}", "{STR_HEX_帳號}", "{STR_HEX_密碼}");'
                        R_INSERT = DEF_SQL_執行(SQL_插入數據)
                        if R_INSERT[0] == 0:
                            print("插入數據成功")
                            Entry_備註.delete(0, END)         # 界面:清空輸入框
                            Entry_帳號.delete(0, END)         # 界面:清空輸入框
                            Entry_密碼.delete(0, END)         # 界面:清空輸入框
                            打開數據庫()                      # 更新數據庫顯示框
                        else:
                            ERROR = f'插入數據失敗:{R_INSERT[1]}'
                            tkinter.messagebox.showerror(title='ERROR', message=ERROR)

def 搜索備註信息():
    print("搜索備註信息")
    備註 = Entry_備註.get().strip()
    if 備註 == '':
        ERROR = '備註空內容'
        tkinter.messagebox.showerror(title='ERROR', message=ERROR)
    else:
        print("搜索備註", 備註)
        SQL_CMD = f'SELECT * FROM PWD WHERE PS LIKE "%{備註}%"'
        print("SQL_CMD", SQL_CMD)
        R = DEF_SQL_查詢和返回(SQL_CMD)
        if R[0] == 0:
            print("執行搜索語句成功")
            數據列表 = R[1]
            #print("數據列表", 數據列表)
            字段列表 = ['ID', 'PS', 'USER', 'PASS']
            字段和數據的存儲和展示(字段列表, 數據列表)
        else:
            ERROR = f'執行搜索語句失敗:{R[1]}'
            tkinter.messagebox.showerror(title='ERROR', message=ERROR)

存儲操作框 = LabelFrame(top, text='存儲操作框')
存儲操作框.grid(row=3, column=0, sticky='NW', columnspan=2)        ## top 3-0

備註框 = Frame(存儲操作框)
備註框.grid(row=1, column=0, sticky='NW')                        ## 存儲操作框 1-0
Label(備註框, text='備註').grid(row=0, column=0)                                   ## 存儲操作框 備註框 0-0
Entry_備註 = Entry(備註框, width=20)
Entry_備註.grid(row=1, column=0)                                                   ## 存儲操作框 備註框 1-0
Button(備註框, text='搜索備註信息', command=搜索備註信息).grid(row=2, column=0)    ## 存儲操作框 備註框 2-0

帳號框 = Frame(存儲操作框)
帳號框.grid(row=1, column=1, sticky='NW')                        ## 存儲操作框 1-1
Label(帳號框, text='帳號').grid(row=0, column=0)                                   ## 存儲操作框 帳號框 0-0
Entry_帳號 = Entry(帳號框, width=20)
Entry_帳號.grid(row=1, column=0)                                                   ## 存儲操作框 帳號框 1-0

密碼框 = Frame(存儲操作框)
密碼框.grid(row=1, column=2, sticky='NW')                        ## 存儲操作框 1-2
Label(密碼框, text='密碼').grid(row=0, column=0)                                   ## 存儲操作框 密碼框 0-0
Entry_密碼 = Entry(密碼框, width=30)
Entry_密碼.grid(row=1, column=0)                                                   ## 存儲操作框 密碼框 1-0

數據庫寫入框 = Frame(存儲操作框)
數據庫寫入框.grid(row=1, column=3, sticky='ESWN')                ## 存儲操作框 1-3
Label(數據庫寫入框, text='密碼存入數據庫方式').grid(row=0, column=0, columnspan=3)                                ## 存儲操作框 數據庫寫入框 0-0
Button(數據庫寫入框, text='  明文存入  ', command=明文存入).grid(row=1, column=0, sticky='NW')                    ## 存儲操作框 數據庫寫入框 1-0
Button(數據庫寫入框, text='  密碼加密存入  ', command=密碼加密存入).grid(row=1, column=1, sticky='NW')                    ## 存儲操作框 數據庫寫入框 1-1
Button(數據庫寫入框, text='  帳號和密碼都加密  ', command=帳號密碼加密存入).grid(row=1, column=2, sticky='NW')    ## 存儲操作框 數據庫寫入框 1-2

SV_剪貼板提示信息 = StringVar()
Label_剪貼板提示信息 = Label(存儲操作框, textvariable=SV_剪貼板提示信息)
Label_剪貼板提示信息['fg'] = '#FF6600'
Label_剪貼板提示信息.grid(row=2, column=0, columnspan=3, sticky='ESWN')        ## 存儲操作框 2-0
##
########


######## 密碼操作框
##
def 生成隨機密碼():
    密碼長度 = IV_密碼位數.get()
    數字字符列表 = ['0','1','2','3','4','5','6','7','8','9']
    大寫字母列表 = ['A','B','C','D','E','F','G','H','J','K','M','N','P','Q','R','S','T','U','V','W','X','Y','Z']
    小寫字母列表 = ['a','b','c','d','e','f','g','h','j','k','m','n','p','q','r','s','t','u','v','w','x','y','z']
    符號字符列表 = ['`','~','!','@','#','$','%','^','&','*','(',')','-','_','=','+','[','{',']','}','|',';',':','<','.','>','/','?']
    組合列表 = 數字字符列表 + 大寫字母列表 + 小寫字母列表 + 符號字符列表
    if 密碼長度 > 84:
        ERROR = '過長,請設置長度 <= 84'
        tkinter.messagebox.showerror(title='ERROR', message=ERROR)
    else:
        random.shuffle(組合列表)
        隨機密碼 = ''
        for i in range(0, 密碼長度):
            隨機密碼 += 組合列表[i]
        #print(隨機密碼)
        Entry_密碼.delete(0, END)         # 界面:清空輸入框
        Entry_密碼.insert(END, 隨機密碼)

## 字節碼轉16進制格式的字符串
def Bytes2HEX_STR(Bytes):
    STR_HEX = ''
    for i in Bytes:
        STR_HEX += hex(i)[2:].zfill(2)      ## '0x50' 取 '50' 部分,如果不足2個字符,填充0
    return(STR_HEX)

IV_密碼位數 = IntVar()
IV_密碼位數.set(20)         ## 設置默認密碼位數
密碼操作框 = LabelFrame(存儲操作框, text='')
Label(密碼操作框, text='設置密碼長度').grid(row=0, column=0, sticky='NW')
Entry(密碼操作框, textvariable=IV_密碼位數, width=5).grid(row=0, column=1)
Button(密碼操作框, text='生成隨機密碼', command=生成隨機密碼).grid(row=0, column=2, sticky='NW')
Button(密碼操作框, text='刷新數據庫', bg='#00FFFF', command=打開數據庫).grid(row=0, column=3, sticky='NW')
密碼操作框.grid(row=0, column=0, sticky='NW', columnspan=8)
##
########



######## 數據庫顯示/操作框
##
字典_查詢字段_座標_初值 = {}  # KEY=控件座標 || VAULE=初始值   || {(控件行號,控件列號):初始值}   || { (0,0):123 }
字典_查詢結果_座標_初值 = {}  # KEY=控件座標 || VAULE=初始值   || {(控件行號,控件列號):初始值}   || { (0,0):123 }

數據框_定位行 = IntVar()
數據框_定位列 = IntVar()

## 信息複製到剪貼板
def DEF_解密後複製到剪貼板(): 
    控件行號 = 數據框_定位行.get()
    控件列號 = 數據框_定位列.get()
    print("控件行號", 控件行號, "控件列號", 控件列號)
    選中單元格內容 = 字典_查詢結果_座標_初值[(控件行號,控件列號)]
    print("選中單元格內容", 選中單元格內容)
    
    ## 讀取選中單元格字符串嘗試還原字節類型
    try:
        還原字節碼 = bytes.fromhex(選中單元格內容)
    except Exception as E1:
        #print("還原字節碼失敗,格式有誤(非16進制字符串形式", E1)
        ERROR = f'還原字節碼失敗,格式有誤(非16進制字符串形式:{E1}'
        tkinter.messagebox.showerror(title='ERROR', message=ERROR)
    else:
        #print("還原字節碼", type(還原字節碼))
        #print(還原字節碼)
        ## 嘗試加載私鑰
        私鑰文件 = SV_選擇私鑰文件.get()
        if 私鑰文件.rstrip() == '':
            ERROR = '沒有選擇私鑰文件'
            print(ERROR)
            tkinter.messagebox.showerror(title='ERROR', message=ERROR)
        else:
            try:
                f = open(私鑰文件,'r')
            except Exception as E2:
                ERROR = f'打開私鑰文件失敗:{E2}'
                print(ERROR)
                tkinter.messagebox.showerror(title='ERROR', message=ERROR)
            else:
                print("打開私鑰文件成功")
                STR_私鑰 = f.read()
                f.close()
                
                ## 根據是否私鑰是否被加密分別處理
                私鑰密碼 = Entry_私鑰密碼.get()
                if 私鑰密碼 != '':
                    print("私鑰被加密,先解密私鑰,再導入使用")
                    try:
                        OJB_私鑰 = RSA.importKey(STR_私鑰, passphrase=私鑰密碼)
                    except Exception as E3:
                        ERROR = f'導入私鑰失敗,私鑰【密碼錯誤】或【格式錯誤】:{E3}'
                        print(ERROR)
                        tkinter.messagebox.showerror(title='ERROR', message=ERROR)
                    else:
                        print("導入私鑰成功")
                        私鑰_PKCS115 = PKCS1_v1_5.new(OJB_私鑰) 
                        
                        ## 私鑰嘗試解密
                        隨機數生成器 = Random.new().read
                        try:
                            明文字節碼 = 私鑰_PKCS115.decrypt(還原字節碼, 隨機數生成器)
                        except Exception as E4:
                            ERROR = f'私鑰解密失敗:{E4}'
                            print(ERROR)
                            tkinter.messagebox.showerror(title='ERROR', message=ERROR)
                        else:
                            print("私鑰解密成功")
                            明文字符串 = 明文字節碼.decode('utf8')
                            top.clipboard_clear()
                            top.clipboard_append(明文字符串)
                            ## 提示哪個數據被複制到剪貼板
                            選中行ID = 字典_查詢結果_座標_初值[(控件行號,0)]
                            選中行列名 = 字典_查詢字段_座標_初值[(0,控件列號)]
                            INFO = f'ID【{選中行ID}】的【{選中行列名}】【解密後內容】 已經複製到剪貼板'
                            #tkinter.messagebox.showinfo(title='INFO', message=INFO)
                            SV_剪貼板提示信息.set(INFO)
                else:
                    print("私鑰無加密,直接導入使用")
                    try:
                        OJB_私鑰 = RSA.importKey(STR_私鑰)
                    except Exception as E3:
                        ERROR = f'導入私鑰失敗,私鑰【被加密】或【格式錯誤】:{E3}'
                        print(ERROR)
                        tkinter.messagebox.showerror(title='ERROR', message=ERROR)
                    else:
                        print("導入私鑰成功")
                        私鑰_PKCS115 = PKCS1_v1_5.new(OJB_私鑰) 
                        
                        ## 私鑰嘗試解密
                        隨機數生成器 = Random.new().read
                        try:
                            明文字節碼 = 私鑰_PKCS115.decrypt(還原字節碼, 隨機數生成器)
                        except Exception as E4:
                            ERROR = f'私鑰解密失敗:{E4}'
                            print(ERROR)
                            tkinter.messagebox.showerror(title='ERROR', message=ERROR)
                        else:
                            print("私鑰解密成功")
                            明文字符串 = 明文字節碼.decode('utf8')
                            top.clipboard_clear()
                            top.clipboard_append(明文字符串)
                            ## 提示哪個數據被複制到剪貼板
                            選中行ID = 字典_查詢結果_座標_初值[(控件行號,0)]
                            選中行列名 = 字典_查詢字段_座標_初值[(0,控件列號)]
                            INFO = f'ID【{選中行ID}】的【{選中行列名}】【解密後內容】 已經複製到剪貼板'
                            #tkinter.messagebox.showinfo(title='INFO', message=INFO)
                            SV_剪貼板提示信息.set(INFO)


def DEF_保留原值複製到剪貼板(): 
    控件行號 = 數據框_定位行.get()
    控件列號 = 數據框_定位列.get()
    print("控件行號", 控件行號, "控件列號", 控件列號)
    選中單元格內容 = 字典_查詢結果_座標_初值[(控件行號,控件列號)]
    print("選中單元格內容", 選中單元格內容)
    top.clipboard_clear()
    top.clipboard_append(選中單元格內容)
    
    ## 提示哪個數據被複制到剪貼板
    選中行ID = 字典_查詢結果_座標_初值[(控件行號,0)]
    選中行列名 = 字典_查詢字段_座標_初值[(0,控件列號)]
    INFO = f'ID【{選中行ID}】的【{選中行列名}】【原值】 已經複製到剪貼板'
    #tkinter.messagebox.showinfo(title='INFO', message=INFO)
    SV_剪貼板提示信息.set(INFO)



## 菜單按鈕函數:刪除整行(提取控件行號信息)
def DEL_ROW():
    控件行號 = 數據框_定位行.get()
    DEF_刪除記錄(控件行號)
    ## 刪除成功後的行列號和當前行列號有差別,立刻設置爲無效行列號,防止後面誤刪
    數據框_定位行.set(-1)
    數據框_定位列.set(-1)

## 菜單按鈕函數:刪除整行(根據控件行號刪除)
def DEF_刪除記錄(控件行號):
    ID = 字典_查詢結果_座標_初值[(控件行號, 0)]
    print("DELETE ID", ID)
    SQL_CMD = f'DELETE FROM PWD WHERE ID = "{ID}"'
    print("SQL_CMD", SQL_CMD)
    ## 刪除記錄
    R = DEF_SQL_執行(SQL_CMD)
    if R[0] == 0:
        打開數據庫()
    else:
        ERROR = f'刪除記錄失敗:{R[1]}'
        tkinter.messagebox.showerror(title='ERROR', message=ERROR)

## 事件函數:右鍵菜單
def DEF_彈出_數據框_右鍵菜單(event):
    # 取值控制座標
    選中控件 = event.widget
    行 = 選中控件.grid_info()['row']
    列 = 選中控件.grid_info()['column']
    # 賦值控制座標變量
    數據框_定位行.set(行)
    數據框_定位列.set(列)
    ## 彈出菜單
    光標X軸 = event.x_root
    光標Y軸 = event.y_root
    數據框_右鍵菜單.post(光標X軸, 光標Y軸)    # 光標位置顯示菜單

## 創建數據框右鍵菜單
數據框_右鍵菜單 = Menu()
數據框_右鍵菜單.add_command(label='解密 到 剪貼板', command=DEF_解密後複製到剪貼板)
數據框_右鍵菜單.add_separator()                                                        # 分隔線
數據框_右鍵菜單.add_separator()
數據框_右鍵菜單.add_command(label='原值 到 剪貼板', command=DEF_保留原值複製到剪貼板)
數據框_右鍵菜單.add_separator()
數據框_右鍵菜單.add_separator()
數據框_右鍵菜單.add_command(label='刪除此行', command=DEL_ROW)

## 清空框內控件
def FRAME_CLEAR(FRAME_NAME):
    for X in FRAME_NAME.winfo_children():
        X.destroy()

## 在顯編框展示結果,保存結果到全局變量
def 字段和數據的存儲和展示(L, LL):
    ID寬 = 3
    PS寬 = 55
    USER寬 = 30
    PASS寬 = 20

    行數 = 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()
    print("主窗口大小和位置", 主窗口大小和位置)
    主窗口寬, 主窗口高, 主窗口X, 主窗口Y = re.findall('[0-9]+', 主窗口大小和位置)
    print("主窗口寬", 主窗口寬)
    print("主窗口高", 主窗口高)
    if int(主窗口寬) < 500:
        主窗口寬 = width
    else:
        主窗口寬 = int(主窗口寬)
    畫布限寬 = 主窗口寬
    print("畫布限寬(想和主框同寬)", 畫布限寬)

    if int(主窗口高) < 700:
        主窗口高 = height
    else:
        主窗口高 = int(主窗口高)
    畫布限高 = int(主窗口高) -500       # 減去顯示框上邊的內容
    print("畫布限高(預計)", 畫布限高)
    if 畫布限高 < 200:
        畫布限高 = 200                  # 保障最小高度
        print("畫布限高需要保障最小高度", 畫布限高)
    print("畫布限寬(實際)", 畫布限寬)
    print("畫布限高(實際)", 畫布限高)
    
    ## 設置畫布參數
    
    ## 畫布可滾動顯示的最大寬和高(要剛好能放下畫布裏的Frame裏的全部控件)
    畫布滾動最右邊 = (ID寬+PS寬+USER寬+PASS寬)*7 + 19   ## Enter默認寬20==140像素(7像素/每寬)
    畫布滾動最下邊 = (行數+1)*21 + 3                    ## (數據行數+字段行數)*(每行20高+間隔1像素) + (留空) 
    print("畫布包含元素需寬", 畫布滾動最右邊)
    print("畫布包含元素需高", 畫布滾動最下邊)
    
    ## 動態設置顯示畫布固定顯示寬和高(要和主顯示框的大小匹配)
    if 畫布限寬 > 畫布滾動最右邊:
        畫布['width'] = 畫布滾動最右邊
        print("畫布限寬>畫布包含元素最大寬度,畫布寬採用實際需要的寬度", 畫布滾動最右邊)
    else:
        畫布['width'] = 畫布限寬 - 30
        print("畫布限寬<=畫布包含元素最大寬度,畫布寬度採用(畫布限寬-30)留出右邊滾動條的顯示位置", 畫布限寬-30)
    
    if 畫布限高 > 畫布滾動最下邊:
        畫布['height'] = 畫布滾動最下邊
        print("畫布限高>畫布包含元素最大高度,畫布高採用實際需要的高度", 畫布滾動最下邊)
    else:
        畫布['height'] = 畫布限高
        print("畫布限高<=畫布包含元素最大高度,畫布高度採用(畫布限高)", 畫布限高)
    
    畫布['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()
    
    ## 字段名
    ## 固定行:0
    ## 固定列:ID PS USER PASS
    ## ID
    初始值 = str(L[0])                                 # 轉成字符串
    字典_查詢字段_座標_初值[(0,0)] = 初始值            # 保存初始值
    輸入框對象 = Entry(字段框, width=ID寬, bg='#00BFFF')
    輸入框對象.insert(0, 初始值)
    輸入框對象.grid(row=0,column=0,sticky='W')
    
    ## PS
    初始值 = str(L[1])
    字典_查詢字段_座標_初值[(0,1)] = 初始值
    輸入框對象 = Entry(字段框, width=PS寬, bg='#00BFFF')
    輸入框對象.insert(0, 初始值)
    輸入框對象.grid(row=0,column=1,sticky='W')
    
    ## USER
    初始值 = str(L[2])
    字典_查詢字段_座標_初值[(0,2)] = 初始值
    輸入框對象 = Entry(字段框, width=USER寬, bg='#00BFFF')
    輸入框對象.insert(0, 初始值)
    輸入框對象.grid(row=0,column=2,sticky='W')
    
    ## PASS
    初始值 = str(L[3])
    字典_查詢字段_座標_初值[(0,3)] = 初始值
    輸入框對象 = Entry(字段框, width=PASS寬, bg='#00BFFF')
    輸入框對象.insert(0, 初始值)
    輸入框對象.grid(row=0,column=3,sticky='W')
    
    ## 數據值
    for 行 in range(0, 行數):
        ## 固定列:ID PS USER PASS
        ## ID
        初始值 = str(LL[行][0])                  # 轉成字符串
        字典_查詢結果_座標_初值[(行,0)] = 初始值 # 保存初始值
        輸入框對象 = Entry(數據框, width=ID寬)
        輸入框對象.insert(0, 初始值)
        輸入框對象.grid(row=行,column=0,sticky='W')
        輸入框對象.bind("<Button-3>", DEF_彈出_數據框_右鍵菜單) # 每個控件對象都綁定右鍵菜單事件
        
        ## PS
        初始值 = str(LL[行][1])                  # 轉成字符串
        字典_查詢結果_座標_初值[(行,1)] = 初始值 # 保存初始值
        輸入框對象 = Entry(數據框, width=PS寬)
        輸入框對象.insert(0, 初始值)
        輸入框對象.grid(row=行,column=1,sticky='W')
        輸入框對象.bind("<Button-3>", DEF_彈出_數據框_右鍵菜單) # 每個控件對象都綁定右鍵菜單事件
        
        ## USER
        初始值 = str(LL[行][2])                  # 轉成字符串
        字典_查詢結果_座標_初值[(行,2)] = 初始值 # 保存初始值
        輸入框對象 = Entry(數據框, width=USER寬)
        輸入框對象.insert(0, 初始值)
        輸入框對象.grid(row=行,column=2,sticky='W')
        輸入框對象.bind("<Button-3>", DEF_彈出_數據框_右鍵菜單) # 每個控件對象都綁定右鍵菜單事件
        
        ## PASS
        初始值 = str(LL[行][3])                  # 轉成字符串
        字典_查詢結果_座標_初值[(行,3)] = 初始值 # 保存初始值
        輸入框對象 = Entry(數據框, width=PASS寬)
        輸入框對象.insert(0, 初始值)
        輸入框對象.grid(row=行,column=3,sticky='W')
        輸入框對象.bind("<Button-3>", DEF_彈出_數據框_右鍵菜單) # 每個控件對象都綁定右鍵菜單事件

LabelFrame_顯編框 = LabelFrame(top, text='顯示/編輯框', bg='#FFD700')
LabelFrame_顯編框.grid(row=4,column=0,sticky='NW',columnspan=2)                         ## top 4-0
##
########



######## SQL命令框
##
## 執行用戶輸入的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:
            INFO = f'數據庫執行SQL語句 {SQL_CMD} 成功'
            打開數據庫()     ## 更新一下數據庫顯示
            tkinter.messagebox.showinfo(title='成功', message=INFO)
        else:
            ERROR = f'數據庫執行SQL語句 {SQL_CMD} 失敗 {R[1]}'
            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:
            INFO = f'數據庫執行SQL腳本 {SQL_CMD} 成功'
            打開數據庫()     ## 更新一下數據庫顯示
            tkinter.messagebox.showinfo(title='成功', message=INFO)
        else:
            ERROR = f'數據庫執行SQL腳本 {SQL_CMD} 失敗 {R[1]}'
            tkinter.messagebox.showerror(title='失敗', message=ERROR)
    else:
        ERROR = 'SQL腳本無內容'
        tkinter.messagebox.showerror(title='錯誤', message=ERROR)

## 清除命令框裏的內容
def DEF_按鈕_清屏():
    文本框_命令行.delete(0.0, END)
    文本框_命令行.focus_set()


命令框 = LabelFrame(top, text='SQL語句/SQL腳本')
命令框.grid(row=5,column=0,sticky='NW',columnspan=2)                                    ## top 5-0

命令行_按鈕框 = 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=3, 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)  # 自動設置滾動條滑動幅度



## 開軟件後先執行數據庫的初始化和打開
初始化數據庫()
打開數據庫()



# 進入消息循環
top.mainloop()

 

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