wxPython: Events von einer Timer-Klasse an GUI übergeben

Code-Stücke können hier veröffentlicht werden.
Antworten
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Hallo!

In diesem Beispiel möchte ich aufzeigen, wie man Events von einem arbeitenden Objekt aus als Benachrichtigung zum Aktualisieren der Anzeige an ein Frame weitergeben kann.

Erklärungen zum Beispiel:
Die "MyEventTimer"-Klasse stellt einen Timer dar, der jede Sekunde ein Event auslöst.

Die "MyFrame"-Klasse bindet beim Initialisieren einen Eventhandler an die "MyEventTimer"-Klasseninstanz. Jedes Mal, wenn in der "MyEventTimer"-Klasseninstanz das Event "EVT_NEW_STATUS" ausgelöst wird, dann wird die Eventhandler-Methode des Frames ausgeführt. Diese Methode aktualisiert die Anzeige im Frame.

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-

import wx
import wx.lib.newevent 
import time

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


# Event-Klasse und Eventbinder-Funktionen erstellen
(NewStatusEvent, EVT_NEW_STATUS) = wx.lib.newevent.NewEvent()


class MyEventTimer(wx.Timer):
    
    def __init__(self):
        wx.Timer.__init__(self)
        self.Start(1000)
        # Es wird jetzt jede Sekunde der Timer ausgelöst. Dabei wird
        # jedes Mal die Methode "Notify" aufgerufen. In dieser Notify-Methode 
        # wird eine Event-Instanz erstellt und ausgelöst.
    
    
    def Notify(self):
        """
        Eine Methode mit dem Namen "Notify" wird bei jedem Timer-Event
        aufgerufen, ohne dass man diese an ein Event binden muss.
        
        Dient nur zum Simulieren von ARBEIT! ;-)
        """
        
        # damit man sieht, dass etwas passiert...
        print "Notify wird jetzt ausgefuehrt..."
        
        # Event-Instanz erstellen
        evt = NewStatusEvent()
        
        # Aktuelle Zeit an die Event-Instanz binden
        evt.current_time = time.asctime()
        
        # Event auslösen
        self.ProcessEvent(evt)
    

class MyFrame(wx.Frame):
    
    def __init__(
        self, parent = None, id = -1, title = "Example", size = wx.Size(550, 420)
    ):
        wx.Frame.__init__(self, parent, id, title, size = size)
        
        panel = wx.Panel(self)
        
        vbox_main = wx.BoxSizer(wx.VERTICAL)
        panel.SetSizer(vbox_main)
        
        # Label zum Anzeigen des Status bzw. hier im Beispiel der Uhrzeit
        self.lab_status = wx.StaticText(panel)
        vbox_main.Add(self.lab_status, 0, wx.ALL, 10)
        
        # Event der **MyEventTimer-Instanz** an Handler binden
        myeventtimer = wx.GetApp().myeventtimer
        myeventtimer.Bind(EVT_NEW_STATUS, self.update_status)
    
    
    def update_status(self, event):
        """
        Dieser Eventhandler bekommt ein Event übergeben. An dieses Event
        wurde die aktuelle Zeit als Attribut ``current_time`` angehängt.
        Diese wird jetzt im StaticText-Feld angezeigt.
        """
        
        self.lab_status.SetLabel(event.current_time)


class MyApp(wx.PySimpleApp):
    
    def OnInit(self):
        
        self.myeventtimer = MyEventTimer()
        # Ab jetzt sendet self.myeventtimer jede Sekunde ein Event
        # bis die Anwendung abgebrochen wird. Gleichzeitig ist die Instanz
        # auch an die App gebunden. So kann auch vom Frame aus darauf zugegriffen
        # werden.
        
        # Frame anzeigen
        self.myframe = MyFrame()
        self.myframe.Show()
        
        return True


def main():
    app = MyApp()
    app.MainLoop()


if __name__ == "__main__":
    main()
Siehe auch: http://www.python-forum.de/post-68192.html

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
baesi
User
Beiträge: 5
Registriert: Mittwoch 7. November 2007, 21:29

Hallo

Besten Dank für das Beispiel. Es war genau die Lösung für mein Problem.
Leider habe ich eine zusätzliche Hürde zu überwinden und das gelingt mir leider nicht. Auch habe ich kaum Erfahrung mit Python


Ich muss ein automatisiertes Testsystem welches mit wxPython entwickelt worden ist erweitern. Diese Software wird von einem anderen Programm welches mit agilent vee entwickelt wurde (quellcode unbekannt) angesteuert.

Das GUI des Python Programmes soll nun zusätzlich eine Anzeige über den Zustand einer exterenen Hardware bekommen. Diese Anzeige soll ungefähr jede Sekunde aktualisiert werden währenddem in einem seperaten Thread ein sequentieller Test ablaufen soll. Also genau der Fall welcher durch das Beispiel abgedeckt ist. Wenn ich das ganze als Standalone Applikation laufen lasse funktioniert es auch. Sobald aber die Agilent VEE Applikation das Python Programm aufruft funktioniert es nicht mehr mit der Fehlermeldung das wx.GetApp() kein myeventtimer atribut hat. Ist ja auch logisch weil das app an das Agilent VEE Programm angebunden ist und nicht an mein Python Programm.

Trotz vielen Versuchen ist es mir nicht gelungen die Instanz an die Python (statt der Agilent VEE) Applikation zu binden. Kann mir eventuell jemand auf die Sprünge helfen. Besten Dank

Gruess

Baesi
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

baesi hat geschrieben:Trotz vielen Versuchen ist es mir nicht gelungen die Instanz an die Python (statt der Agilent VEE) Applikation zu binden. Kann mir eventuell jemand auf die Sprünge helfen.
Hallo Baesi!

Willkommen im Python-Forum!

Das musst du auch nicht. Binde den Timer einfach an den aktuellen Frame.

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-

import wx

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


class MyFrame(wx.Frame):
    
    def __init__(
        self, parent = None, title = "Example", size = wx.Size(250, 220)
    ):
        wx.Frame.__init__(self, parent, -1, title, size = size)
        
        panel = wx.Panel(self)
        self.panel = panel
        panel.SetBackgroundColour("green")
        self.current_color = "green"
        
        self.timer = wx.Timer()
        self.timer.Start(1000)
        self.timer.Bind(wx.EVT_TIMER, self.invert_color)
    
    
    def invert_color(self, event):
        
        if self.current_color == "green":
            self.panel.SetBackgroundColour("red")
            self.current_color = "red"
        else:
            self.panel.SetBackgroundColour("green")
            self.current_color = "green"
        self.panel.Refresh()


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


if __name__ == "__main__":
    main()
mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
baesi
User
Beiträge: 5
Registriert: Mittwoch 7. November 2007, 21:29

Hallo Gerold.

Danke noch einmal für Deine Unterstützung. Ich habs so implementiert wie Du es vorgeschlagen hast.

Was ist eigentlich der Nachteil, wenn ich den Timer ans Frame anstatt ans app anhänge wie in Deinem ursprünglichen Bsp?

Das Programm läuft nun eine Weile bis ich zu einem reprouzierbaremFehler komme, den ich schon mit einer einfacheren Timer Variante hatte. Das Programm bleibt immer stehen wenn ich mit einer Hardware via AgilentVEE kommuniziere:

Unhandled exception in thread started by
Traceback (most recent call last):
File "C:\Python23\lib\site-packages\wx-2.6-msw-unicode\wx\_controls.py", line 1977, in write
return _controls_.TextCtrl_write(*args, **kwargs)
TypeError: argument number 1: object does not support item assignment


Da es ohne die zusätzlichen Geräte Standalone läuft vermute ich es hängt mit Agilent VVE zusammen.

Ich habe jetzt eine etwas weniger schöne Lösung implementiert welche mir den Wert bei einem Mouseover zurückliefert.

Merci und Gruss
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Hallo baesi!

Ohne vollständigen Traceback kann man dazu nichs sagen.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
py_lo
User
Beiträge: 60
Registriert: Freitag 20. März 2009, 09:12

Hallöchen,

ich versuche mich gerade in wxpython einzuarbeiten, und habe dazu eine eigene Klasse für einen
grafischen Slider (für Mischpult / Lautstärkeregelung) kreiert.

Problem für mich ist immer noch das event-binding. Ich habe diesen Thread hier gefunden und versucht
das ganze bei mir einzubauen.

Problem ist, dass das Event von jeder Instanz der Klasse Slider ausgelöst wird,
ich jedoch mit dem Binding genau bestimmen möchte welcher Slider das Event nur auslösen soll.

Ich hab versucht den Quelltext etwas zu kürzen, allerdings wird noch ein "slide.bmp" im pfad erwartet,
ist halt noch nicht so schön... (am besten nicht allzu groß, so ca. 40x20 oder so)

Ansonsten wäre ich für andere allgemeine 'Verschönerungsvorschläge' sehr dankbar...
schlechter Stil , zu umständlich etc.

;-)

Code: Alles auswählen

import wx
import wx.lib.newevent  

# Event-Klasse und Eventbinder-Funktionen erstellen
(SliderScroll, EVT_SLIDER_SCROLL) = wx.lib.newevent.NewEvent()

import time
import random

class Slider(wx.Panel):
    def __init__(self,handler,parent,position,range,height,bmp,mask):
        wx.Panel.__init__(self,parent,-1,
                        pos=(position[0],position[1]),
                        size=(bmp.GetWidth(), bmp.GetHeight())
                        )
                        
        self.SetBackgroundColour('Black')
        self.bind = None
        self.handler = handler
        self.delta = (0,0)
        self.bmpWidth = bmp.GetWidth()
        self.bmpHeigth = bmp.GetHeight()
        self.posX = position[0]
        self.posY = position[1]
        self.range = range
        self.value = range[0]
        self.height = height
        self.bmp = bmp
        #mask = wx.Mask(self.bmp,mask)
        #self.bmp.SetMask(mask)
        self.pos = 0
                           
        self.Bind(wx.EVT_LEFT_DOWN,     self.OnLeftDown)
        self.Bind(wx.EVT_LEFT_UP,       self.OnLeftUp)
        self.Bind(wx.EVT_MOTION,        self.OnMouseMove)
        self.Bind(wx.EVT_PAINT,         self.OnPaint)
        self.InitBuffer()

    def OnPaint(self, event):
        dc = wx.BufferedPaintDC(self, self.buffer)
        #self.parent.Test()
        

    def InitBuffer(self):
        size = self.GetClientSize()
        self.buffer = wx.EmptyBitmap(size.width, size.height)
        dc = wx.BufferedDC(None, self.buffer)
        #dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
        dc.Clear()
        self.DrawBmp(dc)
        self.reInitBuffer = False
        print 'INIT'
        
    def DrawBmp(self, dc):
        dc.DrawBitmap(self.bmp,0,0,True)
        
        
    def OnLeftDown(self, evt):
        self.CaptureMouse()
        self.buffer_value = None
        x, y = self.ClientToScreen(evt.GetPosition())
        originx, originy = self.GetPosition()
        dx = x - originx
        dy = y - originy
        self.delta = ((dx, dy))

    def OnLeftUp(self, evt):
        if self.HasCapture():
            self.ReleaseMouse()
            #self.handler.Test(self.value)
            

    def OnMouseMove(self, evt):

        if evt.Dragging() and evt.LeftIsDown():
            
            x, y = self.ClientToScreen(evt.GetPosition())
            y = y - self.delta[1]
            
            if y > self.height + self.posY: y = self.height + self.posY
            if y < self.posY: y = self.posY
            perc = (y - self.posY) * 100 / self.height
            self.value = ((self.range[1]-self.range[0])*perc / 100) \
                            + self.range[0]
            #self.CalcValue(y)
            self.Move((self.posX,y))
            
            if self.value != self.buffer_value:
                self.buffer_value = self.value
                # Event-Instanz erstellen
                event = SliderScroll()            
                # Aktuellen Wert an die Event-Instanz binden
                event.value = self.value
                # Event ausloesen
                self.ProcessEvent(event)
                
            
    def GetValue(self):
        return self.value

    def SetValue(self,value):
        if value >= self.range[0] and value <= self.range[1]:
            self.value = value
            perc = ((value - self.range[0]) * 100) \
                    / (self.range[1] - self.range[0])
            y = ((self.height * perc) / 100) + self.posY
            self.Move((self.posX,y))
            
        
    
class MainFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, 'Frame Subclass',
        pos = (100,100),
        size=(640, 480),
        style=wx.DEFAULT_FRAME_STYLE | wx.STAY_ON_TOP
        )
        
        panel = wx.Panel(self,-1,size=(640,480))
        
        panel.SetBackgroundColour('Grey')
        button = wx.Button(panel, -1, "Close Me", pos=(200, 10),size=(50, 50))
        test = wx.Button(panel, -1, "SetValue", pos=(250, 60),size=(50, 50))
        valu = wx.Button(panel, -1, "GetValue", pos=(300, 10),size=(50, 50))
        try:
            bmp = wx.Bitmap('slide.bmp', wx.BITMAP_TYPE_BMP)
        except:
            pass
        
        self.a = Slider(self,panel,(40,60),(20,100),300,bmp,wx.WHITE)
        self.b = Slider(self,panel,(100,60),(-10,10),300,bmp,wx.WHITE)      
        
        app.Bind(EVT_SLIDER_SCROLL,self.update_status)
#        app.Bind(EVT_SLIDER_SCROLL,self.update_status, self.a)
# Gedacht hatte ich das wenn ich als 3. parameter self.a uebergebe - das das event dann
#nur noch davon ausgeloest wird, dies ist aber nicht der Fall

        
        self.Bind(wx.EVT_BUTTON, self.OnCloseMe, button)
        self.Bind(wx.EVT_BUTTON, self.Setv, test)
        self.Bind(wx.EVT_BUTTON, self.Getv, valu)
        
    def update_status(self,event):
        print 'value',event.value
        
    def Getv(self,event):
        print self.a.GetValue()
        
    def Setv(self,event):
        self.a.SetValue(40)
        self.b.SetValue(0)

    def OnCloseMe(self, event):
        self.Close(True)

if __name__ == '__main__':
    app = wx.PySimpleApp()
    MainFrame().Show()
    app.MainLoop()
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Bitte poste solche Sachen ins wxPython-Unterforum, da dieses Problem auch nix mit Events von Timern zu tun hat.
Außerdem existieren schon ScrollEvents für einen Slider http://docs.wxwidgets.org/stable/wx_wxs ... l#wxslider

Zum Problem:
1. es gibt functools.partial / lambda
2. es gibt eine .GetId() methode
3. es gibt .GetEventObject()
the more they change the more they stay the same
py_lo
User
Beiträge: 60
Registriert: Freitag 20. März 2009, 09:12

habe ich auch schon bemerkt dass das das falsche Unterforum war... hatte den
Tread über google gefunden, und nicht drauf geachtet - sry. :oops:

Meine Frage zielte mehr darauf ab wie ich mit Bind >>>

Code: Alles auswählen

app.Bind(EVT_SLIDER_SCROLL,self.update_status)
das Event direkt mit dem über die Klasse Slider erstellten Objekt verknüpfen kann,
so wie ich es mit buttons auch mache.

Sprich der dritte Parameter von Bind wäre die ID, allerdings wird dann die
Funktion nicht mehr aufgerufen.

Code: Alles auswählen

app.Bind(EVT_SLIDER_SCROLL,self.update_status, self.a)
self.a ist da eine Instanz der Klasse Slider.
Ich verstehe einfach nur nicht warum es so wie ich es realisieren wollte
nicht funktionieren kann? Da fehlt mir zugegeben etwas der Durchblick...

Und das es schon Scrollevents für Slider gibt mag ja sein, ich wollte aber einen
selbsgemachten - 1. zum Üben und 2. weil ich nicht gefunden habe das es einen
'Ownerdraw' - Flag für den wxslider gibt.
Antworten