Listbox auslesen

Fragen zu Tkinter.
Antworten
gahleitner
User
Beiträge: 35
Registriert: Montag 1. Mai 2017, 09:57

Hallo!
Bei mir wird nach selektierung in der Listbox und Drücken eines Buttons eine Funktion aufgerufen, was auch gut funktioniert. Allerdings nur beim letzten Eintrag in der Listbox. In den anderen Fällen bekomme ich einen KeyError in untenstehender playMacro-Zeile. Wie kann ich auch die anderen zum Laufen bringen? Wäre euch sehr dankbar, wenn ihr mir helfen könntet. Benutze Python 2.7
Mit: playMacro(macros[listbox.get(listbox.curselection())]) wird die folgende Funktion aufgerufen:

Code: Alles auswählen

def playMacro(macro):
    global WAIT_BETWEEN_ACTIONS
    for action in macro:
        time_delta = action[0]
        action_type = action[1]
        
        if WAIT_BETWEEN_ACTIONS:
            time.sleep(time_delta)## Geschwindigkeit time_delta
            
        if action_type in ("key up", "key sys up", "key down", "key sys down"):
            key = int(action[2])
            if action_type[-1] == "p": # key up
                ReleaseKey(key)
            else:
                PressKey(key)

        elif action_type == "mouse move":
            desired_position = (int(action[2]), int(action[3]))
            user32.SetCursorPos(*desired_position)

        else:
            current_mouse_position = getMousePosition()
            relative_position = (int(action[2]) - current_mouse_position[0], int(action[3]) - current_mouse_position[1])

            mouse_change = 0x0002 if action_type == "mouse left down" else \
                           0x0004 if action_type == "mouse left up" else \
                           0x0020 if action_type == "mouse middle down" else \
                           0x0040 if action_type == "mouse middle up" else \
                           0x0008 if action_type == "mouse right down" else \
                           0x0010

            MouseEvent(0x0001 + mouse_change, *relative_position)
BlackJack

@gahleitner: Du zeigts den Code der ausgeführt werden würde wenn der Code der zeitlich davor läuft, den Du aber *nicht* zeigst, keine Ausnahme auslösen würde. Zeig besser den Code der den Fehler auslöst und zum Beispiel den Inhalt der Abbildung und den Wert des Schlüssels der nicht enthalten ist.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

gahleitner hat geschrieben:In den anderen Fällen bekomme ich einen KeyError in untenstehender playMacro-Zeile. Wie kann ich auch die anderen zum Laufen bringen?
Vielleicht könntest Du ja schreiben, wie dieser Fehler genau heißt und in welcher Zeile er passiert :?:
BlackJack

Noch ein paar Anmerkungen zum Quelltext:

``global`` ist Böse™. Zumal hier auch unnötig, zum einen weil WAIT_BETWEEN_ACTIONS gar kein neuer Wert zugewiesen wird, zum anderen weil das ja hoffentlich auch nie passiert, denn die Namensschreibweise suggeriert, dass es sich um eine Konstante handelt. Was dann allerdings wieder etwas komisch erscheint, dass man so etwas tatsächlich global und fest vorgeben möchte. Ich würde da ja ein Argument draus machen.

Tupel werden in der Regel für Werte benutzt bei denen die Position des Wertes die Bedeutung festlegt. Wenn die Position keine feste Bedeutung hat, verwendet man Listen.

Bei der Ermittlung von `mouse_action` wird nicht explizit auf 'mouse right up' geprüft, das heisst alles mögliche, mit dem man vielleicht gar nicht rechnet, wird als loslassen der rechten Maustaste interpretiert. Die verschachtelten bedingten Ausdrücke sind auch nicht wirklich schön und eine ungewöhnliche Lösung. Das würde man mit einem Wörterbuch umsetzen. Wobei man diese Technik auch generell auf den `action_type` anwenden könnte.

Zwischenstand (ungetestet):

Code: Alles auswählen

ACTION_TYPE_TO_MOUSE_CHANGE = {
    'mouse left down': 0x0002,
    'mouse left up': 0x0004,          
    'mouse middle down': 0x0020,
    'mouse middle up': 0x0040,               
    'mouse right down': 0x0008,
    'mouse right up': 0x0010,
}


def play_macro(macro, wait_between_actions):
    for action in macro:
       
        if wait_between_actions:
            time.sleep(action[0])
           
        action_type = action[1]
        if action_type in ['key up', 'key sys up', 'key down', 'key sys down']:
            key = int(action[2])
            (ReleaseKey if action_type.endswith('up') else PressKey)(key)
        elif action_type == 'mouse move':
            user32.SetCursorPos(int(action[2]), int(action[3]))
        else:
            current_mouse_position = getMousePosition()
            relative_position = (
                int(action[2]) - current_mouse_position[0],
                int(action[3]) - current_mouse_position[1]
            )
            MouseEvent(
                0x0001 + ACTION_TYPE_TO_MOUSE_CHANGE[action_type],
                *relative_position
            )
Falls die `action`-Werte unter Deiner Kontrolle erstellt werden, schreit das übrigens geradezu nach eine objektorientierten Lösung in der eine `action` selbst weiss wie sie ausgeführt werden muss, anstelle dieser Dispatcher-Funktion anhand einer Zeichenkette. Dann könnte die Funktion beispielsweise so aussehen:

Code: Alles auswählen

def play_macro(macro, wait_between_actions):
    for action in macro:
        if wait_between_actions:
            time.sleep(action.delay)
        action.execute()
gahleitner
User
Beiträge: 35
Registriert: Montag 1. Mai 2017, 09:57

Hallo!
Ich möchte hier noch den Teil des Codes schreiben, wo der Code ausgelöst wurde. Ich dachte, es reicht die Zeile, wo der Code fehlerhaft ist.

Code: Alles auswählen

def dialogRunMacro():
        global macros
        if listbox.curselection():
            canvas_is_recording.delete(ALL)
            canvas_is_recording.create_polygon(10,10,35,25,10,40, fill="blue")
            root.update()
            playMacro(macros[listbox.get(listbox.curselection())])
            canvas_is_recording.delete(ALL)
            canvas_is_recording.create_rectangle(10,10,40,40, fill = "#161616")

    button_run_macro = Button(root,text = "Makro ausführen", command = dialogRunMacro)
    button_run_macro.grid(row=5,column=1, sticky=W+E)
Der KeyError wird in erstem Code gefunden:
Fehlermeldung:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python2.7\lib\lib-tk\Tkinter.py in __call__ return self.func(*args)
File "D:\Python\11.py",line 397, in dialoRunMacro
playMacro(macros[listbox.get(listbox.curselection())])
KeyError:"neu"

Der Name "neu" ist dabei der Name des Makros, das aufgerufen wird. Seltsam, daß es nur eine Fehlermeldung gibt, wenn nicht das letzte (das makro, das als letztes in die Listbox importiert wurde) Makro aufgerufen wird. Beim letzten funktioniert es einwandfrei.
@BlackJack: Ich möchte später noch die Geschwindigkeit des Makros anpassen - vielleicht mit einem Slider. Ist dies auch möglich, wenn ich action.delay anstatt time_delta benutze? Bezüglich action.execute: Wie weiß action, wie es ausgeführt werden muß - den Teil darunter brauche ich auch noch, oder? Es sollen ja auch Tastatureingaben aufgezeichnet werden.
PS: Sorry, daß ich soviel frage - ich bin noch Anfänger in Python und diesen Teil habe ich aus dem Internet.
BlackJack

@gahleitner: Das reicht immer noch nicht an Code, denn es bringt immer noch nicht mehr Erkenntnis darüber warum der Schlüssel nicht vorhanden ist. Dazu müsste man ja wissen warum Du denkst das er vorhanden sein sollte. Er ist es halt nicht. Warum — keine Ahnung. Den Code kennen wir ja nicht. Der Code an dieser Stelle ist ja nicht fehlerhaft, oder zumindest nicht zwingend. An der Stelle fällt halt nur auf das Code der davor abläuft und den Schlüssel hinzufügen sollte, das offensichtlich nicht getan hat. Warum auch immer.

Und wenn ich mir diese ”Funktion” anschaue, mit dem ``global`` und das die *nichts* übergeben bekommt, aber *alles mögliche* einfach so verwendet weil das irgendwo in der ”Umgebung” auf magische Weise existiert, müsstest Du einerseits wahrscheinlich den gesamten Code zeigen, andererseits wird den sich wahrscheinlich keiner durchlesen wollen wenn da alles mit allem irgendwie zusammenhängen könnte und man tatsächlich alles lesen und verstehen muss. Das ``global`` wäre auch hier übrigens wieder nicht nötig gewesen.

Die `update()`-Methode auf Widgets sollte man möglichst nicht selbst aufrufen. Das kann, wenn man nicht genau weiss was da passieren kann, zu hängenden Programmen führen. Du machst das hier ja auch nur weil die `playMacro()`-”Funktion” auch etwas macht was sie nicht dürfte: sie blockiert die GUI weil sie länger läuft und eventuell sogar `time.sleep()` verwendet, was in GUI-Funktionen/-Methoden ein no-go ist. Man würde das abspielen eines Makros mit der `after_idle()`-Methode auf Widgets ”starten” und dort dann Verzögerungen mit der `after()`-Methode realisieren.

Der Code zum `Canvas` für das Abspielsymbol riecht nach Code-Wiederholung, weil ja wahrscheinlich irgendwo das Symbol wenn nichts abgespielt wird, noch mal im Code irgendwo bei der Initialisierung steht. Dieses Symbol wäre ein guter Kandidat um in einer Klasse gekapselt zu werden. Objektorientierte Programmierung (OOP) hat das Programm ja sowieso bitter nötig um die ganzen globalen Variablen loszuwerden.

Bei `action.delay` ging ich davon aus, dass die Zeit beim Aufzeichnen des Makros dort gespeichert wird. Nicht das die Verzögerung global für alle Aktionen eines Makros gilt. Dann würde man das natürlich nicht in den Aktionen speichern.

Die `action` weiss wie sie ausgeführt wird, weil man das programmiert hat. Natürlich braucht man den Code dafür, aber eben nicht in einer Funktion die alle möglichen Aktionen kennt und weiss wie man sie ausführt, sondern in den Aktionen selbst, damit die Funktion (oder Methode) die Aktionen ausführt nicht so viel Wissen haben muss, und man die Aktionen auch einfacher austauschen, oder neue hinzufügen kann.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

gahleitner hat geschrieben: File "D:\Python\11.py",line 397, in dialoRunMacro
playMacro(macros[listbox.get(listbox.curselection())])
KeyError:"neu"
Der Fehler sollte doch klar sein:

- macros ist ein Dictionary
- listbox.get(listbox.curselection()) liefert den Namen 'neu'
- und der Fehler: der Eintrag für 'neu' ist noch nicht im Dictionary enthalten

PS: schau Dir Fehlermeldungen an, überlege, was die Fehlermeldung bedeutet und dann hast Du den Fehler. Ohne darüber nachzudenken den Code durchzuforsten bringt wenig.
gahleitner
User
Beiträge: 35
Registriert: Montag 1. Mai 2017, 09:57

Der Eintrag ist ja in der Listbox vorhanden, bei mehreren Einträgen funktioniert immer nur der letzte - ich dachte immer mit listbox.get wird sowieso der ganze Inhalt der Listbox ausgelesen. Gibt es eine Möglichkeit, alle Einträge einer Listbox auszulesen?
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi gahleitner

Probier es einmal mit listbox.get(0, last='end')

Gruss wuf :wink:
Take it easy Mates!
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

gahleitner hat geschrieben:Der Eintrag ist ja in der Listbox vorhanden, bei mehreren Einträgen funktioniert immer nur der letzte - ich dachte immer mit listbox.get wird sowieso der ganze Inhalt der Listbox ausgelesen. Gibt es eine Möglichkeit, alle Einträge einer Listbox auszulesen?
Ich habe doch geschrieben, wo der Fehler liegt. Du liest den Eintrag 'neu' aus der Listbox aus.
Aber dieser Eintrag ist nicht enthalten im dictionary: macros
Antworten