Label ändern während Download

Fragen zu Tkinter.
Antworten
Ahsous
User
Beiträge: 5
Registriert: Samstag 22. Januar 2011, 21:36

Hallo,

ich schreibe gerade einen Downloader für private Zwecke und will mir dabei den Status des Downloads anzeigen lassen.
Per print in die Shell klappt das ganze auch. Jetzt will ich den Status in ein Label schreiben lassen, das wird jedoch erst nach dem Download verändert.

Jemand ne Idee wiso?

Code: Alles auswählen

import urllib.request, re, os, io
from tkinter import *

def mergeLists(a, b):
    for i in b:
        a.append(i)

    return a

def listJoin(l, glue):
    s = ''
    for i in range(len(l)):
        s += l[i]
        if i < len(l):
            s += glue
    return s


def dllHook(cnt, blockSize, totalSize):
    #print(b / c * 100)
    global txtStatus
    txtStatus.set(cnt)
#    txtStatus.set('%f (%i / %i)' % (((cnt*blockSize)/totalSize*100), cnt*blockSize, totalSize))
    print('%f (%i / %i)' % (((cnt*blockSize)/totalSize*100), cnt*blockSize, totalSize))

def main():
    global url, tenshiUrl, localPath, txtStatus

    txtStatus.set('Lade Daten...')
    
    files = []
    files = getFiles(url) # Liefert eine Liste von URLs
    for f in files:
        localDir = listJoin(f.split('/')[:-1], '/').replace(tenshiUrl, localPath)
        localFile = localDir + f.split('/')[-1]
        print(localDir)
        if not os.path.isdir(localDir):
            os.makedirs(localDir)
            print('Create %s' % localDir)

        # Datei schon heruntergeladen?
        if os.path.isfile(localFile):
            print('%s already exists.' % localFile)
        else:
            print('Download %s ...' % localFile)
            print(f, localFile)
            urllib.request.urlretrieve(f, localFile, dllHook)
            break

url = 'http://../'
tenshiUrl = 'http://../'
localPath = './'

# Status GUI
gui = Tk()

txtStatus = StringVar()
labStatus = Label(gui, textvariable=txtStatus)
labStatus.pack(fill = X, side = LEFT)
txtStatus.set('Willkommen!')

btnStart = Button(gui, text='Start', command=main)
btnStart.pack(side = LEFT)

gui.mainloop()
PS: Ist mein erstes "richtiges" Python Programm.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Du musst dafür sorgen, dass der Download in einem anderen Thread abläuft als die GUI. Leider vertragen sich GUIs und Pythons native Threadding-Libs idR. nicht wirklich gut. Dazu solltest Du mal die Doku zu TK konsultieren und auch hier im Forum suchen! Dazu hatten wir def. schon mehrere Threads.

Beides sind jedoch imho schon eher fortgeschrittenere Themen; ob das wirklich das richtige für Dich ist, schon jetzt damit zu "spielen"? Ich würde Dir raten da erst einmal auf GUI-Programmierung komplett zu verzichten.

Zum Code:

- mergeLists gibt es schon und ist die Methode "extend()" von Listen.

- listJoin() kapiere ich nicht wirklich, aber zumindest das Iterieren über den Index sieht vermeidbar aus.

- global ist böse und bei Dir doch gar nicht notwendig

- Du hast eine main()-Funktion, aber dennoch Code auf Modulebene; das ist unschön und ungewöhnlich. main() tut ja bei Dir schon etwas. evtl. verlagerst Du das mal in eine (oder mehrere) Funktionen und hebelst die GUI in main() hoch?

- Beachte den Styleguide PEP8. Funktionen werden nicht nach mixedCase benannt, sondern nach dem "lower_case_with_underscores"-Schema.

- Folgendes ist unnötig:

Code: Alles auswählen

    files = []
    files = getFiles(url) # Liefert eine Liste von URLs
Wenn getFiles() doch eine Liste liefert, musst Du vorher keine leere Liste an den Namen "files" binden. Du überschreibst hier in der zweiten Zeile die bisherige Zuordnung und bindest "files" neu. Zeile eins ist also überflüssig. woher stammt getFiles() eigentlich? Nebenbei sind "files" wohl eher "filenames", also Strings! Dein Name würde einem suggerieren, dass es sich um file-Objekte handelt.

- Dateipfade setzt man mit "os.path.join()" zusammen und nicht per "+" Operator (der übrigens sowieso eher eine schlechte Methode darstellt, Strings in Python zu konkatenieren). Da Du wohl Python3.x verwendest, musst Du mal gucken, ob das Modul und die Funktion noch so stimmen.

- Ich würde bei den Ersetzungen von Strings auf die .format()-Methode zurückgreifen und nicht mehr die Variante per "%"-Operator nehmen.

- Bist Du sicher, dass Du das "break" am Ende des else-Zweigs wirklich da haben willst? So bricht die Schleife ja nach einer Datei ab, die heruntergeladen wurde.

- Kommentare niemals hinter Python-Code schreiben, sondern in eine separate Zeile (darüber).

- ok, habe Dein listJoin() doch kapiert. Du willst doch einfach nur:

Code: Alles auswählen

"/".join(deine_liste)
Aber auch in diesem Falle wäre wohl os.path.join() die richtige Wahl.

Übrigens ist es sinnvoll, wenn möglich ein funktionierendes Script zu posten. Wir können das Script so ja nicht testen, weil uns die Daten für die Downloads fehlen. Anstelle des konkreten Downloads könntest Du einfach simulieren, dass Daten kommen, indem Du einige Sekunden lang (time.sleep()) Daten erzeugst / printest.

Bei längeren Scripten bietet sich übrigens das PasteBin des Forums an (Menü unter dem Logo ganz oben). Das hier geht so grad noch :-)

Zu einem vollständigen Script gehört imho auch noch ein Shebang und die Angabe des Encodings - wobei das in Python 3.x obsolet sein könnte, dort Standard mäßig von utf-8 ausgegangen wird iirc.

Ach so, last but not least: Herzlich willkommen hier im Forum :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Ahsous
User
Beiträge: 5
Registriert: Samstag 22. Januar 2011, 21:36

Danke schonmal für die Hilfe!

Hier erstmal meine überarbeitete Version: http://www.python-forum.de/pastebin.php?mode=view&s=133

Allerdings scheint das mit dem Thread nicht zu klappen. Er scheint garnicht zu starten?

// EDIT: Man sollte den Thread natürlich auch starten...

Code: Alles auswählen

dllThread.start()
angefügt, bekomme jetzt aber den Fehler
Exception in Tkinter callback
Traceback (most recent call last):
File "D:\Python31\lib\tkinter\__init__.py", line 1399, in __call__
return self.func(*args)
File "H:/coding/python/anime-ost/a.py", line 39, in btn_start_click
dllThread.start()
File "D:\Python31\lib\threading.py", line 449, in start
if not self._initialized:
AttributeError: 'downloadThread' object has no attribute '_initialized'
:/
Antworten