2013-04-10 77 views
2

我試圖在PythonGtk3中使用進度條,但它沒有得到更新。我已閱讀this文檔和這個論壇中的一些問題(主要針對pygtk),我真的不明白!Gtk.ProgressBar不能在Python中工作

我爲測試目的只做了一個代碼。當你點擊一個按鈕時,它會遞歸地讀取一個目錄的內容。我的意圖是在閱讀所有這些文件時使用進度條。

這是整個代碼:

import os 
from gi.repository import Gtk 

class MyWindow(Gtk.Window): 
    """Progress Bar""" 

    def __init__(self): 
     Gtk.Window.__init__(self, title='Progress Bar') 
     self.set_default_size(300, 75) 
     self.set_position(Gtk.WindowPosition.CENTER) 
     self.set_border_width(10) 

     # read dir contents 
     mydir = 'Documents' 
     home_path = os.environ.get('HOME') 
     dir_path = os.path.join(home_path, mydir) 
     self.dir_files_list(dir_path) 

     # create a grid 
     self.grid = Gtk.Grid(column_homogeneous=True, row_homogeneous=True, 
          column_spacing=10, row_spacing=10) 
     self.add(self.grid) 

     # create a progress bar 
     self.progressbar = Gtk.ProgressBar() 
     self.grid.add(self.progressbar) 

     # create a button 
     self.button = Gtk.Button(stock=Gtk.STOCK_APPLY) 
     self.button.connect('clicked', self.on_button_pressed) 
     self.grid.attach(self.button, 0, 1, 1, 1) 

    # function to read the dir contents 
    def dir_files_list(self, dir_path): 
     self.dir_list = [] 
     for root, dirs, files in os.walk(dir_path): 
      for fn in files: 
       f = os.path.join(root, fn) 
       self.dir_list.append(f) 

    # function to update the progressbar 
    def on_button_pressed(self, widget): 
     self.progressbar.set_fraction(0.0) 
     frac = 1.0/len(self.dir_list) 
     for f in self.dir_list: 
      new_val = self.progressbar.get_fraction() + frac 
      print new_val, f 
      self.progressbar.set_fraction(new_val) 
     return True 

def main(): 
    """Show the window""" 
    win = MyWindow() 
    win.connect('delete-event', Gtk.main_quit) 
    win.show_all() 
    Gtk.main() 
    return 0 

if __name__ == '__main__': 
    main() 

從一個更有經驗的程序員任何幫助表示讚賞!

回答

1

首先,問題是上面的代碼運行速度太快,所以你看到進度條更新很快,看起來沒有更新。這是因爲你沒有搜索目錄並同時顯示結果。當你進行搜索時,__init__這個類,然後當你點擊按鈕時,這個列表將被讀取並全速顯示在進度條中。我非常確定,如果目錄很大,當啓動腳本時,窗口將顯示幾秒鐘,進度條最終將在幾毫秒內進展。

基本上你遇到的問題是你在同一個線程中做所有事情。 Gtk在它自己的線程中,監聽來自GUI的事件並更新GUI。大多數情況下,您將使用Gtk.ProgressBar來完成一些需要一些時間的事情,並且您需要同時執行G​​UI線程和工作線程(正在執行某個操作的線程),然後從工作線程到GUI線程,以便更新進度條。如果像上面的代碼一樣,你在同一個線程中運行所有的東西,你最終會遇到這種問題,例如當GUI凍結時,也就是說,它變得沒有響應,因爲GUI線程突然在做一些與GUI無關的工作。

在PyGTK的,你有方法gobject.idle_add(function, parameters)這樣你就可以從工作線程GUI線程溝通,因此當GUI線程處於空閒狀態,將執行該功能與參數,例如,更新Gtk.ProgressBar 。

我對這個問題的辦法是在這裏實現,請注意是PyGTK的:https://github.com/carlos-jenkins/nested/blob/master/src/lib/nested/core/gallery/loading.py

基本上,它是一個「LoadingWindow」整個應用程序共享。當你想開始加載某個東西或執行一些繁重的工作時,你必須繼承WorkingThread類(example)。然後,您只需使用WorkingThread子類實例作爲參數調用show()方法並完成。在WorkingThread子類中,必須實現payload()函數,即執行繁重工作的函數。您可以直接從WorkingThread調用LoadingWindow中的pulse方法來更新ProgressBar,並且不應該關心線程通信,因爲該邏輯在那裏實現。

希望解釋有幫助。

編輯:

我只是移植了上面PyGObject,你可以在這裏找到了例子:https://gist.github.com/carlos-jenkins/5358445

+0

非常感謝。現在,您可以解釋一下Gtk +和線程,但我不確定我是否能夠在我的程序中實現此功能......至少我會去嘗試...我感謝您爲解釋隱藏在我的問題之外的內容而付出的努力。 – skytux 2013-04-10 19:45:15

+0

我更新了答案,檢查PyGObject版本:https://gist.github.com/carlos-jenkins/5358445 – Havok 2013-04-10 21:20:09

+0

非常感謝您的時間。我要去看看那個版本。格拉西亞斯! – skytux 2013-04-10 22:57:33

3

問題是你正在處理的整個目錄時所創建的窗口。這甚至在你展示它之前(與self.dir_files_list(dir_path)一致)。在我看來你想在按下按鈕apply後調用它。

至少有2個可能的解決方案:使用線程或使用迭代器。對於你的具體用例,我認爲迭代器就足夠了,除了比較簡單和更加pythonic。我會建議線程,只有當你真正需要他們。

而不是通過事先整個目錄及處理他們走之後,你可以在同一時間處理每個目錄下的每個文件。

在你的榜樣,我會改變的方法dir_files_list(更名爲walk)和on_button_pressed爲:

from gc import collect 
from gi.repository import Gtk, GObject 

我們現在還需要進口的GObject使用idle_add這就要求只要沒有更高優先級的回調未決事件。此外,一旦任務完成,我們需要刪除回調不再調用它(我們需要source_remove爲)。

我將您的方法dir_files_list更名爲walk,因爲它在語義上似乎更接近迭代器。當我們通過每個目錄和文件走,我們會暫時「迴歸」(使用yield)。請記住,yield True表示有待處理項目要處理。 yield False意味着我們停止迭代。

因此,該方法是:現在

def walk(self, dir_path): 
    self.dir_list = [] 
    for root, dirs, files in os.walk(dir_path): 
     i = 0.0 
     self.set_title(os.path.dirname(root)) 

     for fn in files: 
      i = i + 1.0 
      f = os.path.join(root, fn) 
      self.dir_list.append(f) 

      self.progressbar.set_fraction(i/len(files)) 

      collect() 
      yield True 

     yield True 

    GObject.source_remove(self.id) 
    yield False 

,我們在這裏更新progressbar。在這個特定的部分,我正在更新每個目錄內的進度條。也就是說,它將在每個子目錄中重新啓動,並且每個子目錄中的進度條都會重新啓動。要想知道哪個目錄正在訪問,請使用當前目錄設置窗口標題。這裏要了解的重要部分是yield。你可以將它適應任何你想做的事情。

一旦我們走完了整個目錄,我們必須返回yield False,但在此之前我們刪除了回調(GObject.source_remove(self.id))。

我在這裏認爲self.dir_list在這裏已經不再有用了,但是你可能會有不同的想法。

你可能不知道何時以及如何walk叫?可當按下按鈕進行設置:

def on_button_pressed(self, button, *args): 
    homedir = os.path.expanduser('~') 
    rootdir = os.path.join(homedir, 'Documents') 

    self.task = self.walk(rootdir) 
    self.id = GObject.idle_add(self.task.next) 

self.task是一個迭代器,它具有檢索來自self.task下一個項目,這是怎麼回事,只要不存在未決的事件被調用的方法next()(與idle_add )。我們得到id爲了一旦我們完成刪除回調。

您必須從__init__方法中刪除行self.dir_files_list(dir_path)

還有一件事:我手動調用垃圾收集器(gc.collect()),因爲它在處理大型目錄時(取決於您對它們做什麼)可能很有用。

+0

非常感謝@gpoo的幫助!我將花一些時間來閱讀這些內容,並嘗試理解和吸收一切:-) – skytux 2013-04-12 19:36:33

+0

隨意問任何可能看起來模糊或需要更多解釋的東西。 – gpoo 2013-04-12 20:24:24

-1

簡單進度:

enter image description here

import pygtk 
pygtk.require('2.0') 
import gtk 
import urllib2 

class MainWin: 

    def destroy(self, widget, data=None): 
     print "destroy signal occurred" 
     gtk.main_quit() 

    def __init__(self): 
     self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) 
     self.window.connect("destroy", self.destroy) 
     self.window.set_border_width(10) 

     self.vbox = gtk.VBox() 
     self.vbox.set_border_width(0) 

     # create a progress bar 
     self.progressbar = gtk.ProgressBar() 
     self.progressbar.set_fraction(0.50) 
     self.vbox.add(self.progressbar) 

     self.window.add(self.vbox) 
     self.window.show_all() 

    def main(self): 
     gtk.main() 

if __name__ == "__main__": 
    MainWin().main() 
+1

我的問題與此不同。另外,我正在用PyGobject進行編程,而不是PyGtk。不管怎麼說,還是要謝謝你。 – skytux 2013-11-05 22:25:10