更新:html
2017.07.17 補充滾動條、win批處理拉起pypython
2017.08.13 新增自定義圖標shell
--------原創 blogs: http://www.cnblogs.com/chenyuebai --------數據庫
1、背景:json
1.工做中本身及同事在查數據庫、測試接口時須要對一些字符串或json串做預處理,目前這些問題網上均有在線轉換的工具,但很繁雜,可能須要打開幾個網頁窗口;windows
2.以前給妹子作的文本處理工具(http://www.cnblogs.com/chenyuebai/p/6999561.html)是基於cmd命令行的,不太友好;app
3.本身作的一些小工具也基本都是命令行執行的,也想接觸下GUI;ide
基於以上,瞭解到python自帶的tkinter庫能夠初步知足UI的需求,業餘時間作了個圖形化工具,涉及窗口、文本、按鈕、標籤、菜單、菜單樹、滾動條、文件處理、界面佈局等;函數
下文將梳理從零到完成的全過程,做爲本身GUI學習的一個總結;工具
ps:後面版本間隙有時間的話想試試用pyQt來作。。tkinter作出的界面確實。。。23333
工具界面截圖:
2、實現
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文本框的值,可使滾動條長度完美貼合文本框
效果:
3、其餘
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