Statusanzeige während dem ein Ordner kopiert wird

Plattformunabhängige GUIs mit wxWidgets.
Antworten
Arno
User
Beiträge: 10
Registriert: Mittwoch 22. September 2010, 13:47

Hallo zusammen.
So, seit meinen letzten Fragan habe ich tatsächlich mein erstes Phyton Projekt fertiggestellt. und es funktioniert sogar :-) und ich hab Freude an Python bekommen.
Allerdings habe ich noch folgendes kleines Problem dass ich gerne lösen würde.

Ich kopiere eine komplette Ordnerstruktur mit copytree das über einen Start Button gestartet wird. Während diesem Kopiervorgang möchte ich gerne eine Meldung erhalten dass irgendwas am tun ist.

Dass muss nicht unbedingt ein ProgressDialog sein, denn dieser scheint mir sehr kompliziert und aufwendig da dieser ja in einem zusätzlichen Thread ablaufen muss damit soetwas wie ein paraleller Ablauf ensteht.Am liebsten hätte ich z.b ein MessageDialog benutzt. Nur hab ich da das Problem dass ich zuerst mit Ok bestätigen muss bevor der Code dan weiter geht. Oder kann man den MessageDialog auch ohne Ok Button erzeugen damit die Software weiterläuft und sobald das kopieren fertig ist wird der MessageDialog mit Close() oder so wieder geschlossen?
Als das auch nicht funktioniert hat hab ich mir gedacht ich erstelle einfach eine neue Klasse in der ich wieder ein Frame erstelle und einen StaticText dazu mit meiner Nachricht die ich ausgeben möchte. Diese Klasse ruf ich dann auf wenn der Start Button gedrückt wird. Dies funktioniert soweit nur wird mir der Text nicht angezeigt sondern nur ein Balken. Wenn ich das Close() auskomentiere sieht man dass zuerst das Frame angezeigt wird dann die Daten kopiert und erst dann der Text im Frame angezeigt..mhm...ich bin momentan ratlos...

Hier noch mein Code damit ihr euch auch ein Bild machen könnt.

Code: Alles auswählen

import wx
import os
import shutil
import sys

class FrameEingabemaske(wx.Frame):
    
    #Frame mit allen Elementen erstellen (GUI)
    def __init__(
            self, parent, ID, title, pos=wx.DefaultPosition,
            size=(600,400), style=wx.DEFAULT_FRAME_STYLE,
            ):
        wx.Frame.__init__(self, parent, ID, title, pos, size, style)
        panel = wx.Panel(self, -1)

        #Titel dem Frame vergeben
        self.SetTitle("SciptA")
        
        #Cancel Button initialisiern
        button_cancel = wx.Button(parent=panel,
                                  id=1003,
                                  label="Cancel",
                                  pos=(70,270),
                                  size=(100,50))
        button_cancel.SetFont(wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, ""))
        
        #Start Button initialisiern
        button_start = wx.Button(parent=panel,
                                 id=1004, 
                                 label="Start",
                                 pos=(380,270),
                                 size=(150,50))
        button_start.SetFont(wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, ""))
                                                
        #Events den Buttons und Checkboxes zuweisen 
        self.Bind(wx.EVT_BUTTON, self.Exit, button_cancel)
        self.Bind(wx.EVT_BUTTON, self.Start, button_start)
#***************************************************************************************
#Bei Exit wird das Fenster geschlossen wenn der Button Cancel gedrueckt wird
    def Exit(self, event):
        self.Close(True)
        event.Skip()
#***************************************************************************************
    def Start(self, event):
        
#        self.Message("Test")   # Versuch mit Message Fenster

        Frame_2 = InfoFenster(None, -1, "")  #Versuch mit neuem Frame erstellen
        Frame_2.Show()

        #Hier wird Kopiert
        shutil.copytree("C:\\temp\\TestQuelle",                         
                        "C:\\temp\\TestZiel",      
                        symlinks = False)

        Frame_2.Close()

        event.Skip()
        
#***************************************************************************************
    #Oeffnet ein Fenster mit dem uebergebenen Text
    def Message(self,text):
        Nachricht = wx.MessageDialog(parent= self,
                                     message=text,
                                     caption="Meldung",
                                     style=wx.OK)
        Nachricht.ShowModal()
#**********************************************************************************************
    #Oeffnet ein Fenster mit dem uebergebenen Text
class InfoFenster(wx.Frame):
    def __init__(
            self, parent, ID, title, pos=wx.DefaultPosition,
            size=(600,600), style=wx.DEFAULT_FRAME_STYLE,
            ):
        wx.Frame.__init__(self, parent, ID, title, pos, size, style)
        panel1 = wx.Panel(self, -1)


        #Titel dem Frame vergeben
        self.SetTitle("INFO")

        #Statischer Text Release initialisiern
        Info = wx.StaticText(parent=panel1,
                                     id=1000,
                                     label="Test",
                                     pos=(20,40),
                                     size=(100,25),
                                     style=wx.ALIGN_RIGHT)
 
        Info.SetFont(wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, ""))
             
if __name__ == "__main__":
    app = wx.PySimpleApp(0)
    wx.InitAllImageHandlers()
    frame_1 = FrameEingabemaske(None, -1, "")
    app.SetTopWindow(frame_1)
    frame_1.Show()
    app.MainLoop()

Besten Dank für eure Hilfe im vorein..
ntrunk
User
Beiträge: 83
Registriert: Sonntag 7. September 2008, 23:09
Wohnort: Buchen (Odenwald)

Hi Arno,
probier mal

Code: Alles auswählen

[...]
        Frame_2 = InfoFenster(None, -1, "")  #Versuch mit neuem Frame erstellen
        Frame_2.Show()
        wx.GetApp().Yield()
[...]
Gruß
Norbert
BlackJack

Der Vorschlag von ntrunk wäre aber IMHO auch nur eine Notlösung, denn dann wird zwar der Text dargestellt, danach friert die GUI aber trotzdem ein bis der Kopiervorgang beendet ist. Wenn man das nicht möchte, führt kein Weg an einer parallelen Abarbeitung vorbei.
Arno
User
Beiträge: 10
Registriert: Mittwoch 22. September 2010, 13:47

Vielen Dank für diesen Tipp. Das hat funktioniert und ist für meine Anwendung auch nicht tragisch dass das GUI einfriert weil dieses Kopieren der letzte Schritt in meinem Projekt ist und ich den GUI in dieser Zeit nicht mehr bedienen muss.
Aber trotzdem würde ich das mit der Parallelen Abarbeitung gerne begreifen für ev weitere Applikationen die ich schreibe.

Ich habe auch an dem Threading gebastelt..nur haut das gar nicht hin so wie ich möchte. Ich habe den Grundcode wie man ein ProgressDialog darstellt auch hier aus diesem Forum. Der funktioniert auch perfekt, d.H. der Balken wird angezeigt. jetzt hab ich in diesen Code mein kopieren von den Daten eingepflanzt. Man sieht auch dass er das datsächlich "parallel" macht. nur wird der Statusbalken in dieser zeit wo das Kopieren noch läuft nicht angezeigt.Es wird in der Zeit während dem kopieren die
def progressdialog_update nicht aufgerufen. Das ist mal mein erstes Problem und mein zweites ist wie ich dann den Balken so einstelle dass er gerade am Ende ist wenn das kopieren fertig ist. So wie ich gelesen habe kann man einen Wert bis max 100 mitgeben. Meine Daten sind aber immer unterschiedlich gross in dieser Ordnerstruktur die kopiert werden soll. Meine erste Idee wäre die Grösse des Ordners auslesen(wenn man das kann, muss ich noch nachlesen) und dann durch einen Faktor teilen dass man sicher immer unter 100 ist und das dann als max definieren. Oder geht das einfacher?

Hier noch der Code mit dem Threading:

Code: Alles auswählen

import wx
import threading
import time
import shutil

wx.SetDefaultPyEncoding("iso-8859-15")


class MyWorkerThread(threading.Thread):
    """
    Dieser Thread ruft in Intervallen die übergebene Funktion "statusfunction" auf.
    Damit kann ein Status an die GUI übergeben werden.
    """
   
    def __init__(self, statusfunction = None):
        threading.Thread.__init__(self)
        self.canceled = threading.Event()
        self.statusfunction = statusfunction
   
   
    def run(self):
        """
        Hier wird gearbeitet
        """
        
        for i in xrange(1, 101):
            # Abbruch, wenn notwendig
            if self.canceled.isSet():
                print "Der Thread wurde abgebrochen"
                break
            print i

           
            # Statusfunktion aufrufen
            if self.statusfunction:
                print i
                self.statusfunction(i)
           
            # Ein wenig Arbeit simulieren
            time.sleep(0.1)
  
    def cancel(self):
        """
        Abbruchbedingung setzen
        """
       
        self.canceled.set()
   


class MyFrame(wx.Frame):
   
    def __init__(self, parent = None, title = "Example", size = wx.Size(250, 150)):
        wx.Frame.__init__(self, parent, -1, title, size = size)
       
        panel = wx.Panel(self)
       
        vbox_main = wx.BoxSizer(wx.VERTICAL)
        panel.SetSizer(vbox_main)
       
        vbox_main.AddStretchSpacer()
       
        btn_start = wx.Button(panel, label = u"Start")
        vbox_main.Add(btn_start, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 5)
        btn_start.Bind(wx.EVT_BUTTON, self.start_thread)
       
        vbox_main.AddStretchSpacer()
   
   
    def start_thread(self, event = None):
        """
        Startet den neuen Thread
        """
        
        self.progressdialog = wx.ProgressDialog(
            title = u"Demo", message = u"Huiiiiii... Jetzt geht's loooos...", parent = self,
            style = wx.PD_AUTO_HIDE | wx.PD_APP_MODAL | wx.PD_CAN_ABORT
        )
        self.progressdialog.Show()
        

        # Beim erstellen des neuen Threads wird gleich die Funktion mitgegeben,
        # die bei Statusänderungen aufgerufen werden soll.
        self.worker = MyWorkerThread(self.worker_statusfunc)
        self.worker.start()

 
        shutil.copytree("C:\\temp\\TestQuelle",  #Daten kopieren                        
                        "C:\\temp\\TestZiel",      
                        symlinks = False)
        

    def worker_statusfunc(self, value):
        """
        Diese Funktion wird vom Worker-Thread aus aufgerufen
       
        Es wird ``wx.CallAfter`` verwendet um einen Trennung zwischen Thread und
        GUI zu verwirklichen.
        """
       
        wx.CallAfter(self.progressdialog_update, value)
        print "a"
   
   
    def progressdialog_update(self, value):
        (thread_continue, thread_skip) = self.progressdialog.Update(value)
        print "b"
        # Wenn die Rückgabe (thread_continue) nicht mehr True ist, dann
        # Thread abbrechen
        if not thread_continue:
            # Thread abbrechen
            self.worker.cancel()
           
            # Kurz warten um dem Thread noch ein wenig Zeit zum Beenden zu lassen.
            time.sleep(0.3)
           
            # ProgressDialog zerstören
            self.progressdialog.Destroy()


def main():
    """Testing"""
    app = wx.PySimpleApp()
    f = MyFrame()
    f.Center()
    f.Show()
    app.MainLoop()


if __name__ == "__main__":
    main()
Vielen Dnak für eure Hilfe. Das bring mich immer ein Stück weiter..:-)
ntrunk
User
Beiträge: 83
Registriert: Sonntag 7. September 2008, 23:09
Wohnort: Buchen (Odenwald)

@BlackJack:
Du hast sicher recht mit der Notlösung, manchmal entpuppt sich diese aber als bereits ausreichend :-)

@Arno:
Du machst die Arbeit immer noch im Haupthread und blockierst damit nach wie vor die Anzeige der GUI während des Kopierens. Der Gedanke bei der Verwendung von Threads ist aber eher, die Arbeit auf die Nebenthreads auszulagern und den Hauptthread nur für die GUI und zum Anstossen der Arbeitsthreads zu verwenden.
Zu deinem zweiten Problem: Das gesamte Kopieren wird durch den Aufruf einer einzigen Funktion erledigt. Wenn diese Funktion während ihrer Laufzeit keine Statusinformationen liefert, hast du auch keine Möglichkeit, einen Progressbalken synchron mit dem Fortschritt der Funktion zu halten. Du könntest hier z.B. einfach als Hinweis auf die laufende Arbeit eine Animation anzeigen.
Alternativ, wenn du einen Fortschritt mit dem Progressbalken benötigst, musst du deine Arbeit auf kleine Schritte aufteilen und dann nach jedem Schritt den Fortschritt an den GUI-Thread melden, damit die GUI die Fortschrittsanzeige aktualisieren kann.

Gruß
Norbert
Arno
User
Beiträge: 10
Registriert: Mittwoch 22. September 2010, 13:47

Hallo Norbert

Besten Dank für deine Hilfe und die Erklärung
Das klappt jetzt wunderbar.

Gruss Arno
Antworten