1. 程式人生 > >python3.6 +tkinter GUI程式設計 實現介面化的文字處理工具

python3.6 +tkinter GUI程式設計 實現介面化的文字處理工具

更新:

2017.07.17  補充滾動條、win批處理拉起py

2017.08.13  新增自定義圖示

--------原創      blogs:    http://www.cnblogs.com/chenyuebai    --------

一、背景:

1.工作中自己及同事在查資料庫、測試介面時需要對一些字串或json串作預處理,目前這些問題網上均有線上轉換的工具,但很繁雜,可能需要開啟幾個網頁視窗;

2.之前給妹子做的文字處理工具(http://www.cnblogs.com/chenyuebai/p/6999561.html)是基於cmd命令列的,不太友好;

3.自己做的一些小工具也基本都是命令列執行的,也想接觸下GUI;

基於以上,瞭解到python自帶的tkinter庫可以初步滿足UI的需求,業餘時間做了個圖形化工具,涉及視窗、文字、按鈕、標籤、選單、選單樹、滾動條、檔案處理、介面佈局等;

下文將梳理從零到完成的全過程,作為自己GUI學習的一個總結;

ps:後面版本間隙有時間的話想試試用pyQt來做。。tkinter做出的介面確實。。。23333

工具介面截圖:     

 

 二、實現

1.安裝所需要的庫     ps:環境是win7+python 3.6,tkinter是py的標準庫,即:如不涉及EXCEL處理,可跳過此步驟;使用python處理Excel表格,後面有時間會單獨整理

(1)et-xmlfile  openpyxl安裝需要依賴,工具裡有涉及處理EXCEL(xlsx),需要用到openpyxl庫;

(2)jdcal    同上

(3)openpyxl  處理EXCEL(xlsx)

(4)xlrd     讀取EXCEL(xls)

(5)xlwt     寫入EXCEL(xls)

(6)xltils    配合上面兩個庫對已存在表格進行修改

2.新建父視窗(可理解為介面的最底層)

複製程式碼

#################################################################
#author: 陳月白
#_blogs: http://www.cnblogs.com/chenyuebai/
#################################################################
from tkinter import *

def gui_start():
    init_window = Tk()        #例項化出一個父視窗
    init_window.mainloop()    #父視窗進入事件迴圈,可以理解為保持視窗執行,否則介面不展示

gui_start()

複製程式碼

執行結果:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

執行會彈出上圖所示的空白視窗,這裡我們還可以修改視窗預設大小、預設彈出位置、視窗名、父視窗顏色、虛化等等;

    init_window.title("文字處理工具  by: 陳月白")
    init_window.geometry('290x160+10+10')      #290 160為視窗大小,+10 +10 定義視窗彈出時的預設展示位置
    init_window["bg"] = "pink"                 #視窗背景色,其他背景色見:blog.csdn.net/chl0000/article/details/7657887
    init_window.attributes("-alpha",0.8)       #虛化 值越小虛化程度越高

注意要加在 init_window.mainloop() 之前;

執行結果:

整理下程式碼:

複製程式碼

#################################################################
#author: 陳月白
#_blogs: http://www.cnblogs.com/chenyuebai/
#################################################################
from tkinter import *
import hashlib

class MY_GUI():
    def __init__(self,init_window_name):
        self.init_window_name = init_window_name


    #設定視窗
    def set_init_window(self):
        self.init_window_name.title("文字處理工具_v1.2   by: 陳月白")      #視窗名
        self.init_window_name.geometry('1068x680+10+10')                 #290 160為視窗大小,+10 +10 定義視窗彈出時的預設展示位置
        self.init_window_name["bg"] = "pink"                            #視窗背景色,其他背景色見:blog.csdn.net/chl0000/article/details/7657887
        self.init_window_name.attributes("-alpha",0.9)                  #虛化,值越小虛化程度越高


def gui_start():
    init_window = Tk()              #例項化出一個父視窗
    ZMJ_PORTAL = MY_GUI(init_window)
    # 設定根視窗預設屬性
    ZMJ_PORTAL.set_init_window()

    init_window.mainloop()          #父視窗進入事件迴圈,可以理解為保持視窗執行,否則介面不展示


gui_start()

複製程式碼

 先到這,有時間再整理。。

 --------------------------------------------------------------------------------

繼續。


3.父視窗完成後,即可基於父視窗建立其他元件

下面以“字串轉MD5”為例,介紹資料讀取、按鈕、佈局、事件觸發、後臺轉換處理、介面展示結果;

(1)標籤
標籤可以使用tkinter裡的Label()方法,生成標籤,括號內可帶入其他屬性引數;
生成標籤後需使用grid()或者pack()方法使其展示;  #注:tkinter對於同一個父視窗,只可允許使用一種佈局函式;另外還有個place()方法,不過目前我還沒用過;

self.init_data_label = Label(self.init_window_name, text="待處理資料")
self.init_data_label.grid(row=0, column=0)
self.result_data_label = Label(self.init_window_name, text="輸出結果")
self.result_data_label.grid(row=0, column=12)

這裡正好提一下grid(),說下我的理解。grid()方法是tkinter庫自帶的佈局函式,他可以將父視窗分割成類似一個Excel表格,對單個元件按照引數中傳遞的row、column放置在指定位置,並展示;

下圖紅框部分,代表整個父視窗介面:

程式碼中的:self.init_data_label.grid(row=0, column=0)   代表將“init_data_label”這個標籤放置在頁面的第一行,第一列的位置(即左上角),並展示;

(2)文字框
tkinter庫中的Text()方法,可以建立一個文字框,用於資料的錄入和結果展示;Text()方法可以傳入其他引數值,用於定義如背景顏色,前景色,字型,字號、字色等

self.init_data_Text = Text(self.init_window_name, width=67, height=35)  #原始資料錄入框
self.init_data_Text.grid(row=1, column=0, rowspan=10, columnspan=10)
self.result_data_Text = Text(self.init_window_name, width=70, height=49)  #處理結果展示
self.result_data_Text.grid(row=1, column=12, rowspan=15, columnspan=10)

self.init_data_Text.grid(row=1, column=0, rowspan=10, columnspan=10)      #"rowspan=10" 是grid()方法的一個引數,用於指定該元件(init_data_Text)佔用的行數,同理columnspan用於指定佔用列數;

(3)按鈕

按鈕的建立可以使用Button()方法實現,可自定義按鈕大小,寬度,按鈕文字,背景色,以及需要觸發的事件;

#按鈕
self.str_trans_to_md5_button = Button(self.init_window_name, text="字串轉MD5", bg="lightblue", width=10,command=self.str_trans_to_md5)  # 呼叫內部方法  加()為直接呼叫
self.str_trans_to_md5_button.grid(row=1, column=11)

觸發事件:在定義按鈕時,使用command=functionname這個引數來指定;當按鈕被點選,就會呼叫command後的方法,從而完成事件的觸發;

這裡有個小坑。。。就是command後跟函式名時,不可加括號,否則在程式碼執行時,就會觸發該函式;

舉個例子,上面貼的程式碼中“command=self.str_trans_to_md5”若寫為“command=self.str_trans_to_md5()”,該gui_test.py在執行時便會呼叫類中的self.str_trans_to_md5()方法,而不是在點選按鈕時才觸發;

(4)事件程式碼:後臺的處理

step3中在定義按鈕時,會選擇觸發一個事件(或稱為函式),函式的實現涉及:從介面文字框中讀取待處理資料---邏輯處理---介面結果文字框展示

code:

複製程式碼

    def str_trans_to_md5(self):
        src = self.init_data_Text.get(1.0,END).strip().replace("\n","").encode()
        #print("src =",src)
        if src:
            try:
                myMd5 = hashlib.md5()
                myMd5.update(src)
                myMd5_Digest = myMd5.hexdigest()
                #print(myMd5_Digest)
                #輸出到介面
                self.result_data_Text.delete(1.0,END)  
                self.result_data_Text.insert(1.0,myMd5_Digest)  #從第一行開始
            except:
                self.result_data_Text.delete(1.0,END)
                self.result_data_Text.insert(1.0,"字串轉MD5失敗")
        else:
            print("ERROR")    #介面左下角會有日誌框,支援動態列印,下面會提到

複製程式碼

讀取待處理資料:對之前定義的self.init_data_Text(待處理資料文字框)可使用tkinter庫中自帶的get()方法,獲取文字框中的內容,str型別;

邏輯處理:MD5加密,不展開了,從網上找的例子照著敲;

介面結果文字框展示:對獲取的字串處理後,即可使用Text.insert(index,strdata)方法插入結果文字框:self.result_data_Text;

插入前建議先清理下,Text.delete(1.0,END)代表將文字框從第一行刪除至最後一行,即清空;

 (5)日誌列印

和上面沒啥區別,直接貼程式碼了~

複製程式碼

  def get_current_time(self):
        current_time = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))
        return current_time


    #日誌動態列印
    def write_log_to_Text(self,logmsg):
        global LOG_LINE_NUM
        current_time = self.get_current_time()
        logmsg_in = str(current_time) +" " + str(logmsg) + "\n"      #換行
        if LOG_LINE_NUM <= 7:
            self.log_data_Text.insert(END, logmsg_in)
            LOG_LINE_NUM = LOG_LINE_NUM + 1
        else:
            self.log_data_Text.delete(1.0,2.0)
            self.log_data_Text.insert(END, logmsg_in)
        #移動游標
        self.log_data_Text.focus_force()

複製程式碼

整體的例子程式碼如下:

複製程式碼

#################################################################
#author: 陳月白
#_blogs: http://www.cnblogs.com/chenyuebai/
#################################################################
from tkinter import *
import hashlib
import time

LOG_LINE_NUM = 0

class MY_GUI():
    def __init__(self,init_window_name):
        self.init_window_name = init_window_name


    #設定視窗
    def set_init_window(self):
        self.init_window_name.title("文字處理工具_v1.2   by: 陳月白")           #視窗名
        #self.init_window_name.geometry('320x160+10+10')                         #290 160為視窗大小,+10 +10 定義視窗彈出時的預設展示位置
        self.init_window_name.geometry('1068x681+10+10')
        #self.init_window_name["bg"] = "pink"                                    #視窗背景色,其他背景色見:blog.csdn.net/chl0000/article/details/7657887
        #self.init_window_name.attributes("-alpha",0.9)                          #虛化,值越小虛化程度越高
        #標籤
        self.init_data_label = Label(self.init_window_name, text="待處理資料")
        self.init_data_label.grid(row=0, column=0)
        self.result_data_label = Label(self.init_window_name, text="輸出結果")
        self.result_data_label.grid(row=0, column=12)
        self.log_label = Label(self.init_window_name, text="日誌")
        self.log_label.grid(row=12, column=0)
        #文字框
        self.init_data_Text = Text(self.init_window_name, width=67, height=35)  #原始資料錄入框
        self.init_data_Text.grid(row=1, column=0, rowspan=10, columnspan=10)
        self.result_data_Text = Text(self.init_window_name, width=70, height=49)  #處理結果展示
        self.result_data_Text.grid(row=1, column=12, rowspan=15, columnspan=10)
        self.log_data_Text = Text(self.init_window_name, width=66, height=9)  # 日誌框
        self.log_data_Text.grid(row=13, column=0, columnspan=10)
        #按鈕
        self.str_trans_to_md5_button = Button(self.init_window_name, text="字串轉MD5", bg="lightblue", width=10,command=self.str_trans_to_md5)  # 呼叫內部方法  加()為直接呼叫
        self.str_trans_to_md5_button.grid(row=1, column=11)


    #功能函式
    def str_trans_to_md5(self):
        src = self.init_data_Text.get(1.0,END).strip().replace("\n","").encode()
        #print("src =",src)
        if src:
            try:
                myMd5 = hashlib.md5()
                myMd5.update(src)
                myMd5_Digest = myMd5.hexdigest()
                #print(myMd5_Digest)
                #輸出到介面
                self.result_data_Text.delete(1.0,END)
                self.result_data_Text.insert(1.0,myMd5_Digest)
                self.write_log_to_Text("INFO:str_trans_to_md5 success")
            except:
                self.result_data_Text.delete(1.0,END)
                self.result_data_Text.insert(1.0,"字串轉MD5失敗")
        else:
            self.write_log_to_Text("ERROR:str_trans_to_md5 failed")


    #獲取當前時間
    def get_current_time(self):
        current_time = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))
        return current_time


    #日誌動態列印
    def write_log_to_Text(self,logmsg):
        global LOG_LINE_NUM
        current_time = self.get_current_time()
        logmsg_in = str(current_time) +" " + str(logmsg) + "\n"      #換行
        if LOG_LINE_NUM <= 7:
            self.log_data_Text.insert(END, logmsg_in)
            LOG_LINE_NUM = LOG_LINE_NUM + 1
        else:
            self.log_data_Text.delete(1.0,2.0)
            self.log_data_Text.insert(END, logmsg_in)


def gui_start():
    init_window = Tk()              #例項化出一個父視窗
    ZMJ_PORTAL = MY_GUI(init_window)
    # 設定根視窗預設屬性
    ZMJ_PORTAL.set_init_window()

    init_window.mainloop()          #父視窗進入事件迴圈,可以理解為保持視窗執行,否則介面不展示


gui_start()

複製程式碼

上面例子程式碼的執行結果:

大概就是這些,其他功能、按鈕大同小異,就不贅述了;

有問題歡迎留言交流;

後面有時間會補充用windows批處理拉起python程式碼,這樣就可以像普通軟體一樣使用這個小工具了;

2017.07.14

 --------------------------------------------------------------------------------


繼續。

(6)滾動條

對於展示長文字,需要在Text文字框側翼(或下方)提供滾動條;tkinter庫中提供Scrollbar()方法建立一個滾動條

# 滾動條
self.result_data_scrollbar_y = Scrollbar(self.init_window_name)                #建立縱向滾動條
self.result_data_scrollbar_y.config(command=self.result_data_Text.yview)       #將建立的滾動條通過command引數繫結到需要拖動的Text上
self.result_data_Text.config(yscrollcommand=self.result_data_scrollbar_y.set) #Text反向繫結滾動條
self.result_data_scrollbar_y.grid(row=1, column=23, rowspan=15, sticky='NS')   

ps1:滾動條與待繫結的元件(Text或其他)時,需要二者相互繫結,從而達到拖動任意一方,對應方同步移動;

ps2:grid()中rowspan的值最好取Text文字框的值,可以使滾動條長度完美貼合文字框

效果:

三、其他

1.windows批處理拉起python

py程式碼完成後,為方便使用,可以使用windows批處理指令碼拉起python程式碼

common_tools_gui.py所在目錄下新建start.bat,寫入   @python common_tools_gui.py %* @pause

@pause可以保持程式碼一直處於執行狀態,而不是控制檯一閃而過;  #python需配置在環境變數中,或者使用絕對路徑。。

但也會帶來一個問題,就是在雙擊這個批處理時,軟體介面出來的同時,會附帶一個黑色的cmd控制檯視窗;

百度了一下,可以使用windows的.vbs拉起批處理來規避:

common_tools_gui.py所在目錄下新建start_my_gui.vbs,寫入

Set ws = CreateObject("Wscript.Shell")
ws.run "cmd /c start.bat",vbhide

此時雙擊該vbs指令碼,即可拉起軟體介面,且隱藏cmd控制檯介面;

2. 暫時木有了。。

後面專案間隙有可能會研究下robotFramework~

也有可能會試著用pyQt重寫下這個介面

先到這了;

2017.07.17

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

3.自定義圖示

python開啟工具時,左上角預設為tkinter預設圖示工作列為python預設圖示;

查了下,python的tkinter庫支援自定義圖示:py檔案當前目錄下製作ico圖示檔案(https://www.ico.la/),父視窗使用iconbitmap()方法,指定該檔案即可;

self.init_window_name.iconbitmap('text_processing_tools.ico')     #指定介面圖示

中途有個坑,就是僅左上角生效,但windows下方的工作列無效;

百度之,大概意思是文字工具的py檔案執行時,windows認為彈出的工具視窗是python直譯器的衍生程式(子程式?),即預設仍使用pythpn直譯器的圖示;

需要在程式碼中指定彈出視窗的AppUserModelID即可:

import ctypes    
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("myappid")  

效果:

左上角:             底側工作列: 

附目錄結構:

2017.08.13