在python學(xué)習(xí)過(guò)程中有一次需要進(jìn)行GUI 的繪制, 而在python中有自帶的庫(kù)tkinter可以用來(lái)簡(jiǎn)單的GUI編寫(xiě),于是轉(zhuǎn)而學(xué)習(xí)tkinter庫(kù)的使用。 學(xué)以致用,現(xiàn)在試著編寫(xiě)一個(gè)簡(jiǎn)單的磁文件搜索工具, 方法就是將指定的文件夾進(jìn)行掃描遍歷,把其中的每個(gè)文件路徑數(shù)據(jù)存入數(shù)據(jù)庫(kù), 然后使用數(shù)據(jù)庫(kù)搜索文件就很快捷。實(shí)現(xiàn)的效果大致如下:
整個(gè)程序分為大致幾個(gè)模塊: 主界面的繪制, 指定文件夾功能函數(shù), 搜索文件功能函數(shù), ui線(xiàn)程與掃描線(xiàn)程同步函數(shù), 掃描線(xiàn)程工作函數(shù)
要實(shí)現(xiàn)掃描文件功能時(shí), 導(dǎo)入了一個(gè)這樣的模塊 disk.py 這個(gè)模塊實(shí)現(xiàn)的功能就是將指定文件夾下的所有文件遍歷,并將路徑和所在盤(pán)符存到一個(gè)列表中返回 import os import os.path as pt def scan_file(path): result = [] for root, dirs, files in os.walk(path): for f in files: file_path = pt.abspath(pt.join(root, f)) result.append((file_path, file_path[0])) # 保存路徑與盤(pán)符 return result 然后我們需要將掃描到的文件存入到數(shù)據(jù)庫(kù)中, 因此需要編寫(xiě)數(shù)據(jù)庫(kù)模塊 datebase.py import sqlite3 class DataMgr: def __init__(self): # 創(chuàng)建或打開(kāi)一個(gè)數(shù)據(jù)庫(kù) # check_same_thread 屬性用來(lái)規(guī)避多線(xiàn)程操作數(shù)據(jù)庫(kù)的問(wèn)題 self.conn = sqlite3.connect("file.db", check_same_thread=False) # 建表 self.conn.execute('create table if not exists disk_table(' 'id integer primary key autoincrement,' 'file_path text,' 'drive_letter text)') # 創(chuàng)建索引 用來(lái)提高搜索速度 self.conn.execute('create index if not exists index_path on disk_table(file_path)') # 批量插入數(shù)據(jù) def batch_insert(self, data): for line in data: self.conn.execute('insert into disk_table values (null,?,?)', line) self.conn.commit() # 模糊搜索 def query(self, key): cursor = self.conn.cursor() cursor.execute("select file_path from disk_table where file_path like ?", ('%{0}%'.format(key),)) r = [row[0] for row in cursor] cursor.close() return r def close(self): self.conn.close()
還需要一個(gè)額外的模塊為 progressbar.py 這個(gè)模塊的功能是在掃描時(shí)彈出一個(gè)進(jìn)度條窗口, 使得GUI功能看起來(lái)更完善 from tkinter import * from tkinter import ttk class GressBar: def start(self): top = Toplevel() # 彈出式窗口,實(shí)現(xiàn)多窗口時(shí)經(jīng)常用到 self.master = top top.overrideredirect(True) # 去除窗體的邊框 top.title("進(jìn)度條") Label(top, text="正在掃描選定路徑的文件,請(qǐng)稍等……", fg="blue").pack(pady=2) prog = ttk.Progressbar(top, mode='indeterminate', length=200) # 創(chuàng)建進(jìn)度條 prog.pack(pady=10, padx=35) prog.start() top.resizable(False, False) # 參數(shù)為false表示不允許改變窗口尺寸 top.update() # 計(jì)算窗口大小,使顯示在屏幕中央 curWidth = top.winfo_width() curHeight = top.winfo_height() scnWidth, scnHeight = top.maxsize() tmpcnf = '+%d+%d' % ((scnWidth - curWidth) / 2, (scnHeight - curHeight) / 2) top.geometry(tmpcnf) top.mainloop() def quit(self): if self.master: self.master.destroy()
主體的search.py 代碼: 1 from tkinter import * 2 from tkinter import ttk 3 import tkinter.filedialog as dir 4 import queue 5 import threading 6 import progressbar 7 import disk 8 from database import DataMgr 9 10 11 class SearchUI: 12 13 def __init__(self): 14 # 創(chuàng)建一個(gè)消息隊(duì)列 15 self.notify_queue = queue.Queue() 16 root = Tk() 17 self.master = root 18 self.create_menu(root) 19 self.create_content(root) 20 self.path = 'D:' 21 root.title('the search tool') 22 root.update() 23 # 在屏幕中心顯示窗體 24 curWidth = root.winfo_width() 25 curHeight = root.winfo_height() 26 scnWidth, scnHeight = root.maxsize() # 得到屏幕的寬度和高度 27 tmpcnf = '+%d+%d' % ((scnWidth - curWidth)/2, (scnHeight-curHeight)/2) 28 root.geometry(tmpcnf) 29 30 # 創(chuàng)建一個(gè)進(jìn)度條對(duì)話(huà)框?qū)嵗?/span> 31 self.gress_bar = progressbar.GressBar() 32 33 # 創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)的實(shí)例 34 self.data_mgr = DataMgr() 35 36 # 在UI線(xiàn)程啟動(dòng)消息隊(duì)列循環(huán) 37 self.process_msg() 38 root.mainloop() 39 40 # ui線(xiàn)程與掃描線(xiàn)程同步 41 def process_msg(self): 42 # after方法,相當(dāng)于一個(gè)定時(shí)器, 43 # 第一個(gè)參數(shù)是時(shí)間的毫秒值, 44 # 第二個(gè)參數(shù)指定執(zhí)行一個(gè)函數(shù) 45 self.master.after(400, self.process_msg) 46 # 這樣我們就在主線(xiàn)程建立了一個(gè)消息隊(duì)列, 47 # 每隔一段時(shí)間去消息隊(duì)列里看看, 48 # 有沒(méi)有什么消息是需要主線(xiàn)程去做的, 49 # 有一點(diǎn)需要特別注意, 50 # 主線(xiàn)程消息隊(duì)列里也不要干耗時(shí)操作, 51 # 該隊(duì)列僅僅用來(lái)更新UI。 52 while not self.notify_queue.empty(): 53 try: 54 msg = self.notify_queue.get() 55 if msg[0] == 1: 56 self.gress_bar.quit() 57 58 except queue.Empty: 59 pass 60 61 # 掃描線(xiàn)程工作 62 def execute_asyn(self): 63 # 定義一個(gè)scan函數(shù),放入線(xiàn)程中去執(zhí)行耗時(shí)掃描 64 def scan(_queue): 65 if self.path: 66 paths = disk.scan_file(self.path) # 位于disk.py 67 self.data_mgr.batch_insert(paths) # 位于database.py 68 69 _queue.put((1,)) 70 th = threading.Thread(target=scan, args=(self.notify_queue,)) 71 th.setDaemon(True) # 設(shè)置為守護(hù)進(jìn)程 72 th.start() 73 74 self.gress_bar.start() 75 76 # 菜單繪制 77 def create_menu(self, root): 78 menu = Menu(root) # 創(chuàng)建菜單 79 80 # 二級(jí)菜單 81 file_menu = Menu(menu, tearoff=0) 82 file_menu.add_command(label='設(shè)置路徑', command=self.open_dir) 83 file_menu.add_separator() 84 file_menu.add_command(label='掃描', command=self.execute_asyn) 85 86 about_menu = Menu(menu, tearoff=0) 87 about_menu.add_command(label='version1.0') 88 89 # 在菜單欄中添加菜單 90 menu.add_cascade(label='文件', menu=file_menu) 91 menu.add_cascade(label='關(guān)于', menu=about_menu) 92 root['menu'] = menu 93 94 # 主界面繪制 95 def create_content(self, root): 96 lf = ttk.LabelFrame(root, text='文件搜索') 97 lf.pack(fill=X, padx=15, pady=8) 98 99 top_frame = Frame(lf) 100 top_frame.pack(fill=X, expand=YES, side=TOP, padx=15, pady=8) 101 102 self.search_key = StringVar() 103 ttk.Entry(top_frame, textvariable=self.search_key, width=50).pack(fill=X, expand=YES, side=LEFT) 104 ttk.Button(top_frame, text="搜索", command=self.search_file).pack(padx=15, fill=X, expand=YES) 105 106 bottom_frame = Frame(lf) 107 bottom_frame.pack(fill=BOTH, expand=YES, side=TOP, padx=15, pady=8) 108 109 band = Frame(bottom_frame) 110 band.pack(fill=BOTH, expand=YES, side=TOP) 111 112 self.list_val = StringVar() 113 listbox = Listbox(band, listvariable=self.list_val, height=18) 114 listbox.pack(side=LEFT, fill=X, expand=YES) 115 116 vertical_bar = ttk.Scrollbar(band, orient=VERTICAL, command=listbox.yview) 117 vertical_bar.pack(side=RIGHT, fill=Y) 118 listbox['yscrollcommand'] = vertical_bar.set 119 120 horizontal_bar = ttk.Scrollbar(bottom_frame, orient=HORIZONTAL, command=listbox.xview) 121 horizontal_bar.pack(side=BOTTOM, fill=X) 122 listbox['xscrollcommand'] = horizontal_bar.set 123 124 # 給list動(dòng)態(tài)設(shè)置數(shù)據(jù),set方法傳入一個(gè)元組 125 self.list_val.set(('等待搜索',)) 126 127 # 搜索文件 128 def search_file(self): 129 if self.search_key.get(): 130 result_data = self.data_mgr.query(self.search_key.get()) 131 if result_data: 132 self.list_val.set(tuple(result_data)) 133 134 # 指定文件夾 135 def open_dir(self): 136 d = dir.Directory() 137 self.path = d.show(initialdir=self.path) 138 139 140 if __name__ == '__main__': 141 SearchUI()
問(wèn)題總結(jié): 1.UI線(xiàn)程負(fù)責(zé)界面的繪制與更新,如果在UI線(xiàn)程中進(jìn)行耗時(shí)操作,會(huì)影響界面的流暢性,所以需要異步線(xiàn)程。 此時(shí)的問(wèn)題在于UI的主線(xiàn)程與異步線(xiàn)程的通信問(wèn)題,為什么一定要兩個(gè)線(xiàn)程通信? 因?yàn)樵诖蠖鄶?shù)GUI界面編程中,異步線(xiàn)程都是不能對(duì)當(dāng)前界面進(jìn)行操作更新的,否則會(huì)引起界面混亂。 可以簡(jiǎn)單的理解成 如果異步線(xiàn)程也操作主界面,則兩個(gè)線(xiàn)程對(duì)相同資源進(jìn)行操作,就會(huì)導(dǎo)致混亂。 接下來(lái)的問(wèn)題是tkinter中沒(méi)有提供接口進(jìn)行線(xiàn)程通信,因此我們通過(guò)消息隊(duì)列的方式來(lái)同步線(xiàn)程,用到的類(lèi)為Queue。 項(xiàng)目中當(dāng)在消息隊(duì)列中檢索到消息為元組(1, )時(shí),說(shuō)明子線(xiàn)程(掃描)已經(jīng)結(jié)束了,告知主線(xiàn)程可以結(jié)束子線(xiàn)程了。
2.掃描文件夾時(shí)需要將所選文件夾中的所有文件遍歷一遍,發(fā)現(xiàn)python中提供了方法os.walk(path), 可以直接達(dá)到這一效果,所以說(shuō)python在寫(xiě)代碼時(shí)確實(shí)提供了方便。
3.該磁盤(pán)搜索工具用到的原理是將文件路徑存到數(shù)據(jù)庫(kù)中,再進(jìn)行檢索。 選用的數(shù)據(jù)庫(kù)為sqlite,已經(jīng)可以滿(mǎn)足該項(xiàng)目的要求。在主線(xiàn)程創(chuàng)建數(shù)據(jù)庫(kù),子線(xiàn)程操作數(shù)據(jù)庫(kù),有可能出現(xiàn)問(wèn)題,因此設(shè)置check_same_thread = false 來(lái)拒絕多線(xiàn)程的訪問(wèn)。
4.在進(jìn)行GUI編程時(shí),打算在掃描等待時(shí)添加一個(gè)進(jìn)度條顯示窗口,也就需要多窗口,用到了toplevel,表現(xiàn)為一個(gè)彈出式窗口,在使用toplevel時(shí),要注意首先需要一個(gè)根窗口。
|
|
來(lái)自: wenxuefeng360 > 《待分類(lèi)1》