Buttons in Frame funktionieren nicht, wenn ich Code auf mehrere Dateien aufteile.

Fragen zu Tkinter.
Antworten
Vinter
User
Beiträge: 8
Registriert: Sonntag 10. März 2019, 16:22

Hallo!

Ich bin in Sachen Python und tkinter noch Beginner, höchstwahrscheinlich ist mein Problem ein sehr leicht lösbares.

Folgendes Szenario: Ich habe hier einen Schrittmoter, der über eine Schrittmotorsteuerung mit einem Raspberry Pi verbunden ist. Dazu habe ich mir mit tkinter und Beispielen aus dem Netz ein Frame mit zwei Buttons gebastelt, die den Schrittmotor jeweils links- bzw. rechtsherum drehen. Der Code funktioniert und tut, was er soll.

Ich initialisiere erst die Schrittmotorsteuerung mit einigen Parametern und erzeuge mir dann ein Fenster mit den Buttons, class App:.

Folgender Code:

Code: Alles auswählen

from PI_Stepper_Class import PI_Stepper
from tkinter import *

if __name__ == "__main__":
    PI = PI_Stepper(port='/dev/ttyUSB0', baud=9600)
        
    print('Stepper ID: '+ PI.ID())
    
    #Initializing Stepper, see PI_Stepper_Class for further details
    PI.set_Param50('1', '0')
    print(PI.get_Param50('1'))
    
    PI.set_Param14('1', '1')
    print(PI.get_Param14('1'))
    
    PI.set_AxisLimitMode('1', '3')
    print(PI.get_AxisLimitMode('1'))
    
    PI.set_Current('1', '200')
    print(PI.get_Current('1'))
    
    PI.set_Acceleration('1', '10000')
    print(PI.get_Acceleration('1'))
    
    PI.set_Servo('1', '1')
    print(PI.get_Servo('1'))
    
    PI.set_ReferenceMode('1', '0')
    print(PI.get_ReferenceMode('1'))
    
    PI.Error()
    


class App:

    def __init__(self, master):
        
        #Sets a frame with dimensions of 300x200 pixels
        master.geometry("300x200")
        
        
        fm = Frame(master)
        Button(fm, text='-', repeatdelay=500, repeatinterval=75, command=self.btn_click_negative).pack(side=LEFT, fill=BOTH, expand=1)
        Button(fm, text='+', repeatdelay=500, repeatinterval=75, command=self.btn_click_positive).pack(side=LEFT, fill=BOTH, expand=1)
        Button(fm, text='Quit', fg='red', command=root.destroy).pack(side=LEFT, fill=BOTH, expand=1)
        fm.pack(fill=BOTH, expand=YES)
        
    def btn_click_negative(self):
        PI.move_R( 1, 1 * (-640))
            
    def btn_click_positive(self):
        PI.move_R( 1, 1 * (640))
             
        
root = Tk()

app = App(root)

root.mainloop()
Wenn ich jetzt allerdings anfange, meinen Code auf mehrere Dateien aufzuteilen, funktioniert es nicht mehr.

In meiner main.py initialisiere ich den Stepper und erzeuge mir etwas weiter unten das Fenster als Objekt der Display-Klasse.

Code: Alles auswählen

from cPIStepper import PI_Stepper
from cDisplay import Display
from tkinter import *

debug = True

if __name__ == "__main__":

    PI = PI_Stepper(port='/dev/ttyUSB0', baud=9600)

    PI.set_Param50('1', '0')
    PI.set_Param14('1', '1')
    PI.set_AxisLimitMode('1', '3')
    PI.set_Current('1', '200')
    PI.set_Acceleration('1', '10000')
    PI.set_Servo('1', '1')
    PI.set_ReferenceMode('1', '0')
    
    if debug == True:
    
        PI.get_Param50('1')
        PI.get_Param14('1')
        PI.get_AxisLimitMode('1')
        PI.get_Current('1')
        PI.get_Acceleration('1')
        PI.get_Servo('1')
        PI.get_ReferenceMode('1')
        PI.get_Position('1')
        PI.get_Error()
        
        
    root = Tk()
    Dis = Display(root, PI)
    root = mainloop()
Und dies hier ist die leicht abgewandelte Display-Klasse aus dem (funktionierenden Code) von zu Anfang, cDisplay.py

Code: Alles auswählen

from tkinter import *

class Display:

    def __init__(self, master, Stepper):
        
        #Sets a frame with dimensions of 300x200 pixels
        master.geometry("300x200")
        
        #Testing for Stepper-Instance
        print('-------------')
        Stepper.get_Acceleration('1')
        print('-------------')
        
        fm = Frame(master)
        Button(fm, text='-', repeatdelay=500, repeatinterval=75, command=self.btn_click_negative(Stepper)).pack(side=LEFT, fill=BOTH, expand=1)
        Button(fm, text='+', repeatdelay=500, repeatinterval=75, command=self.btn_click_positive(Stepper)).pack(side=LEFT, fill=BOTH, expand=1)
        Button(fm, text='Quit', fg='red', command=master.destroy).pack(side=LEFT, fill=BOTH, expand=1)
        fm.pack(fill=BOTH, expand=YES)
        
        
    def btn_click_negative(self, Stepper):
        Stepper.move_R( 1, 10 * (-640))
            
    def btn_click_positive(self, Stepper):
        Stepper.move_R( 1, 10 * (+640))
Das Programm verhält sich folgendermaßen. Ich starte das Programm über main.py, die Schrittmotorsteuerung wird korrekt initialisiert. Dann erzeugt er den Frame und springt dafür in die Datei cDisplay.py.

An der Stelle

Code: Alles auswählen

        #Testing for Stepper-Instance
        print('-------------')
        Stepper.get_Acceleration('1')
        print('-------------')
gibt er korrekt die Beschleunigung des übergebenen PI_Stepper-Objektes aus. Das ist für mich ein Hinweis, dass die Übergabe des Objektes funktioniert hat.
Weiter unten jedoch kommt er an die Stelle, an denen ich die Buttons definiert habe und hier passiert etwas seltsames:

Beim Starten des Programms löst er einmalig die Funktionen btn_click_negative(self, Stepper) bzw btn_click_positive(self, Stepper) aus, reagiert aber anschließend nie wieder auf die Buttonsklicks.

Wenn ich die Funktionen lediglich als btn_click_negative(self) deklariere (und entsprechend nicht das Objekt an die Funktion übergebe) bemängelt er in der Funktion, dass das Stepper-Objekt nicht existiert.

Es wäre toll, wenn jemand eine Idee hat, warum die Buttons korrekt funktionieren, wenn der Code in nur einer Datei liegt, aber nicht, wenn ich das Ganze auf zwei Dateien aufteile.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Beim Knöpfeerzeugen übergibst Du auch nicht mehr eine Funktion, sondern das Ergebnis des Funktionsaufrufs `btn_click_negative`.

Statt also das Stepper-Objekt an die Methode zu übergeben, solltest Du es als Attribut der Klasse definieren:

Code: Alles auswählen

import tkinter as tk

class Display:
    def __init__(self, master, stepper):
        self.stepper = stepper
        master.geometry("300x200")
        fm = tk.Frame(master)
        tk.Button(fm, text='-', repeatdelay=500, repeatinterval=75, command=self.btn_click_negative).pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
        tk.Button(fm, text='+', repeatdelay=500, repeatinterval=75, command=self.btn_click_positive).pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
        tk.Button(fm, text='Quit', fg='red', command=master.destroy).pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
        fm.pack(fill=tk.BOTH, expand=tk.YES)

    def btn_click_negative(self):
        self.stepper.move_R( 1, 10 * (-640))
            
    def btn_click_positive(self):
        self.stepper.move_R( 1, 10 * (+640))
Es macht übrigens wenig Sinn, solche kleinen Programmteile in einzelne Dateien aufzuteilen, vor allem nicht, wenn sie so heißen, wie EINE Klasse, die einzige die darin definiert wird.
Bei PI_Stepper_Class oder cPIStepper sieht das auch sehr verdächtig aus.
Module schreibt man übrigens komplett klein, wie auch Variablen oder Funktionen. Nur Klassen schreibt man mit großem Anfangsbuchstaben, damit man gleich erkennen kann, was eine Klasse ist. Methoden sollten auch nicht so nichtsagend sein wie set_Param50. Was soll denn die 50 bedeuten? Wenn man Zahlen als Strings übergeben muß, sieht das stark nach einem Designfehler aus. get-Methoden, wo man nichts mit den Rückgabewerten macht, ist noch so eine Stolperstelle, da stimmt irgendwas nicht.
Wie sieht denn diese Stepper-Klasse aus?
Antworten