Zwei Befehle für Button

Fragen zu Tkinter.
Philipp68
User
Beiträge: 34
Registriert: Freitag 23. Juni 2017, 10:14

Hallo,
ich habe einen Button, der die Aktivität einer LED darstellen soll.
Anfangs ist die Farbe des Buttons grün (LED an), nachdem ich einmal den Button geklickt habe, soll er rot werden (LED rot). Beim zweiten Mal, soll dieser wieder grün werden (um wieder LED anzuschalten). Mir geht es erstmal um die visuelle Darstellung, danach müssen noch Befehle für den Raspberry Pi hinzugefügt werden.

Code: Alles auswählen

# LED Circle
def LEDchange1():
    LED1.config(bg='red')
    # weitere Befehle

LED1 = Button(f3,command=LEDchange1,height=1,width = 2,bg='green')
LED1.place(x='435',y='278')
Der Button wird nach dem ersten Klick rot, nur weiß ich nicht, wie ich einen zweiten Klick (wieder grün) einfügen kann. Dachte an if - Anweisungen, wenn er rot ist und ich wieder klicke, dass er wieder grün wird.
Vielen Dank für die Hilfe!
Zuletzt geändert von Anonymous am Dienstag 4. Juli 2017, 13:50, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@Philipp68: Ich würde da einen `Checkbutton` empfehlen. Den kann man auch als normale Schaltfläche anzeigen lassen und auch die Farbe für die beiden Zustände angeben. Und vielleicht auch ein `Tkinter.BooleanVar` daran binden um den Zustand einfach ermitteln zu können.

Zusätzlich solltest Du Dir objektorientierte Programmierung (OOP) an- und diese komischen angehängten Zahlen an den Namen abgewöhnen.

Was ist die Ausrede für `place()`? :-)
Philipp68
User
Beiträge: 34
Registriert: Freitag 23. Juni 2017, 10:14

Hallo Blackjack,
danke für den Hinweis! :)
Die Zahl benutze ich, weil ich auf eine Bildvorlage (LED - Ring) Plätze für Vierecke(LED - Buttons) erstellt habe und es insgesamt 24 sind. Somit weiß ich, welcher Button wo liegt, da auf der Platine diese genau so angeordnet sind und ich diese später steuern möchte. Mit "place" rücke ich die buttons an die richtige Stelle. Werde mich Morgen es mit Checkbutton ausprobieren.
Lg
BlackJack

@Philipp68: Dann würde man die aber nicht einzeln an nummerierte Namen binden, sondern in einer Liste speichern. Und Du wirst ja auch keine 24 Funktionen schreiben die alle im Grunde das gleiche machen. Und sind die Schaltflächen im Kreis angeordnet? Dann berechnet man das doch und schreibt schreibt da nicht 24 mal Koordinaten hin die von Hand ausgerechnet oder geschätzt sind.
Philipp68
User
Beiträge: 34
Registriert: Freitag 23. Juni 2017, 10:14

Guten Morgen Blackjack,
ich habe mir eben deinen Vorschlag mit "Checkbutton" angeschaut und von der Optik gefällt es mir leider nicht so, mit einem Haken im Feld. Ich würde lieber bei "Button" bleiben (wenn das geht). Natürlich ist es sehr aufwendig per Hand die Position festzulegen. Ich habe mal ein Bild von der Oberfläche mit hochgeladen, damit man weiß, was ich vorhabe. Ich bin in Python ein absoluter Neuling und arbeite erst seit drei Wochen damit, deswegen entschuldige ich mich im Vorfeld für schlechte Ansätze etc..
Also von der Theorie her wäre es so, alle LED mit ihren Positionen und Befehlen in einer Liste aufzuzählen. Der rote Button soll später den Befehl an den Raspberry geben, dass die LED aus ist. Klicke ich diesen an, wird er grün geht die LED an. Ich wähle im Ring alle gewünschten LEDs aus. Zum Schluss möchte ich einen "Reset - Button" hinzufügen, der den Zustand aller LEDs zurücksetzt, also auf rot, falls mehrere falsch ausgewählt wurden.
Ich möchte keine Lösung haben, weil ich das gerne selber schaffen will, aber einen Ansatz oder eine Herangehensweise wäre sehr hilfreich, da ich gerade einfach überfordert bin.

Bild
BlackJack

@Philipp68: Wenn ich mich zum Checkbutton mal selbst zitieren darf: „Den kann man auch als normale Schaltfläche anzeigen lassen und auch die Farbe für die beiden Zustände angeben.“

Bei der gezeigten Anordnung bietet es sich IMHO an die Positionen zu berechnen. Dann lässt sich das einfacher anpassen und man muss auch nicht die ganzen Koordinaten per Hand eingeben. Man könnte beispielsweise die Grösse des Bildes als Grundlage nehmen.

Mit `functools.partial()` kann man Funktionen für die `command`-Argumente erzeugen denen man die Nummer der Schaltfläche mitgeben kann.

Den GUI-Teil für den Ring würde ich in einer eigenen Widget-Klasse zusammenfassen. Und ich würde die Schaltflächen nicht in einem `Frame` mit `place()` setzen sondern in einem `Canvas` mit `create_window()`, denn dort beschreiben die Koordinaten den Mittelpunkt, was mit weniger eigener Rechnerei verbunden sein dürfte. (Man kann aber auch einen anderen Ankerpunkt als den Mittelpunkt angeben auf den sich die Koordinaten beziehen sollen.)

Da Du Dich für die GUI sowieso in objektorientierte Programmierung einarbeiten musst, solltest Du vielleicht auch erst einmal mit der Programmlogik anfangen. Also erst die ganze Funktionalität modellieren und implementieren und dann, wenn das fertig und getestet ist, die GUI da drauf setzen. In dieser Reihenfolge ist es einfacher Programmlogik und GUI sauber zu trennen. Die Programmlogik sollte nichts von der Darstellung wissen und der GUI-Code nichts über die Interna der Programmlogik, also beispielsweise Pin-Nummern oder so etwas.
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Nur ein Gedanke, mehr nicht!
Und eine interaktive Grafik?
Die wäre vom Format treuer und sollte sich auch steuern lassen.
In HTML hätte ich so etwas versucht mit einer Image-Map zu basteln, weiß jetzt aber nicht, was Tkinter und Canvas für Möglichkeiten bieten, doch die Koordinaten bekommst Du ja genauer mit einem Eventhandler abgefasst und könntest dann punktgenau die Farben ändern.

Code: Alles auswählen

def aendere_farbe(event):
    weite = event.width
    hoehe = event.height
    position_x = event.x
    position_y = event.y
http://effbot.org/tkinterbook/tkinter-e ... ndings.htm
Philipp68
User
Beiträge: 34
Registriert: Freitag 23. Juni 2017, 10:14

Hallo Backjack,
wir arbeiten an diesem Projekt zu viert und ich bin nur für die Oberfläche, die die LEDs steuern soll, zuständig. Jemand anderes entwickelt die Platine und jemand anderes programmiert den Pi. Da unser Betreuer noch nicht die Hardware erhalten hat, will ich auch keine Zeit verlieren und möchte mich schon mit der GUI befassen und der Programmierung. Es geht mir hier mehr um einen Grundaufbau, der später erweitert und sicher angepasst werden muss. Ich habe vorher viel in Matlab geschrieben, nur tue ich mich mit Python schwer. Ich studiere auch keine Informatik und bin in auch völlig unerfahren, wenn es um Interaktion zwischen Hard - und Software geht. Werde mich noch weiter belesen und mich bei Fragen melden! Bis hierhin vielen Dank :)

Hallo Melewo,
darüber habe ich mich mit einem Freund auch schon unterhalten. Jedoch würde ich es lieber mit Python umsetzen. Trotzdem vielen Dank für deinen Beitrag :)
BlackJack

@Philipp68: In dem Fall könntest Du die Programmlogik als Dummyobjekt implementieren das die Schnittstelle der tatsächlichen Programmlogik bietet. Gegen irgend eine Schnittstelle musst Du die GUI ja programmieren. Habt ihr da nichts definiert? Derjenige der die Programmlogik implementiert muss ja auch wissen welche Schnittstelle die GUI erwartet.
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

@Philipp68: Ich doch auch, nur habe ich meins noch nicht fertig, weil ich durch andere Dinge abgelenkt werde. Und ich nehme da auch nichts anderes als Tkinter. Eine Grafik, mit Deiner runden Scheibe, so wie auf Deinem Bild und wenn Du damit fertig bist, bestimmst Du anschließend, was, wann in welchem Bildabschnitt passieren darf und was dabei ausgelöst werden soll.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Philipp68 hat geschrieben:Hallo,
ich habe einen Button, der die Aktivität einer LED darstellen soll.
Anfangs ist die Farbe des Buttons grün (LED an), nachdem ich einmal den Button geklickt habe, soll er rot werden (LED rot). Beim zweiten Mal, soll dieser wieder grün werden (um wieder LED anzuschalten). Mir geht es erstmal um die visuelle Darstellung
Meinst Du so?

Code: Alles auswählen

import tkinter as tk

class ToggleButton(tk.Button):

    def __init__(self,master,is_on = True, on_color = 'lightgreen', off_color = 'red', height = 1, width = 1,**kwargs):
        tk.Button.__init__(self,master,bg = on_color if is_on else off_color, activebackground = on_color if is_on else off_color,height = height, width = width,**kwargs)

        self.is_on = is_on
        self.on_color = on_color
        self.off_color = off_color

        self['command'] = self.toggle

    def toggle(self):
        self.is_on = not self.is_on
        self['bg'] = self.on_color if self.is_on else self.off_color
        self['activebackground'] = self.on_color if self.is_on else self.off_color


class Application(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)

        LED1 = ToggleButton(self)
        LED1.place(x=50,y=50)

Application().mainloop()
Das mit dem command gefällt mir aber nicht ganz. Ich fände es besser, wenn der Button bereits beim Drücken die Farbe wechseln würde und nicht erst beim Loslassen.

Dann wäre zu überlegen, ob es überhaupt ein Button sein soll, der das Drücken animiert, oder ob bereits der Farbwechsel ausreicht, oder ob dann der Button oder Label im eingedrückten Zustand bleiben soll und erst bei nochmaligem Drücken auch wieder herausspringen soll.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Code: Alles auswählen

# -*- coding: utf-8 -*-
 
try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk
 
 
# Application definition ============================
 
class Application(tk.Tk):
 
    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.minsize(50, 50)
        self.geometry("500x500")
        # widget definitions ===================================
        self.switch = Switch(self,name='#0_switch')
        self.switch.pack(fill='both', expand=1)
 
class Switch(tk.Frame):
 
    def __init__(self,master,**kwargs):
        tk.Frame.__init__(self,master,**kwargs)
        self.myclass = 'Switch'
        # widget definitions ===================================
        self.canvas = Canvas(self,name='#1_canvas')
        self.canvas.place(x=0,y=0)
 

        def width_height(event):
            frame_height = event.height
     
            bd = frame_height/14
            canvas_height = frame_height-4*bd
            scale_factor = canvas_height/self.canvas_height_before
            self.canvas_height_before = canvas_height
            self.canvas.config(width = canvas_height,height = canvas_height,bd = bd,highlightthickness=bd)
            self.canvas.scale('led',0,0,scale_factor,scale_factor)
     
        def switch(event):
            self.on = not self.on
            self.canvas.itemconfig('led',fill = 'lightgreen' if self.on else '#ff7431')
            self.canvas['relief'] = 'sunken' if self.on else'raised'
     
        self.canvas_height_before = 100
        self.bind('<Configure>',width_height,'+')
        self.on = False
        self.canvas.bind('<Button-1>',switch)

 
class Canvas(tk.Canvas):
 
    def __init__(self,master,**kwargs):
        tk.Canvas.__init__(self,master,**kwargs)
        self.config(height='100', relief='raised', highlightbackground='#e8e9ee', insertwidth='0', bg='#615e58', bd='10', selectborderwidth='0', highlightthickness='10', width='100', highlightcolor='lightgray')
        self.start_width = 100
        # widget definitions ===================================
        coords = (30,30,110,110)
        item = self.create_oval(*coords)
        self.itemconfig(item,width = '0.0',fill = '#ff7431',tags = 'led')

 
if __name__ == '__main__':
    Application().mainloop()
BlackJack

Wenn es doch nur so einen Button schon fertig gäbe… Aber hey, es *gibt* ihn ja: `Checkbutton`! Aber klar, man kann den natürlich auch in mehreren Varianten selber programmieren.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:Wenn es doch nur so einen Button schon fertig gäbe… Aber hey, es *gibt* ihn ja: `Checkbutton`! Aber klar, man kann den natürlich auch in mehreren Varianten selber programmieren.
Was ist beim Checkbutton anders? Der hat genauso wenig eine off color und on color wie sonstige Wdgets. Nichts ist da also anders.

Nur man kann sich da diese Zeile erparen: self.is_on = not self.is_on
BlackJack

@Alfons Mittelmeyer: Doch der hat verschiedene Farben für an und aus.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Philipp68 hat geschrieben:Hallo,
ich habe einen Button, der die Aktivität einer LED darstellen soll.
Anfangs ist die Farbe des Buttons grün (LED an), nachdem ich einmal den Button geklickt habe, soll er rot werden (LED rot). Beim zweiten Mal, soll dieser wieder grün werden (um wieder LED anzuschalten)
Hier etwas zum herumspielen:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from functools import partial
from math import sin, cos, radians

try:
    # Tkinter for Python 2.xx
    import Tkinter as tk
except ImportError:
    # Tkinter for Python 3.xx
    import tkinter as tk

APP_TITLE = "LED-Rosette"
APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 500
APP_HEIGHT = 500

LEDS = 24
LED_GEOMETRY = 20
LED_ON = 'red'
LED_OFF = 'green'
LED_ANGLE_GAP = 360 / LEDS
RADIUS = 200


class LedRosette(tk.Canvas):
    def __init__(self, master=None):
        tk.Canvas.__init__(self, master)
        
        self.angle = 0 

        self.x_center = (APP_WIDTH / 2)
        self.y_center = (APP_HEIGHT / 2)

        self.leinwand = tk.Canvas(self, width=APP_WIDTH, height=APP_HEIGHT)
        self.leinwand.pack()

        for led_nr in range(LEDS):
            xpos, ypos = self.zeigerBewegung()
            button = tk.Button(self, bg=LED_OFF, activebackground=LED_OFF)
            button.place(x=0, y=0, width=LED_GEOMETRY, height=LED_GEOMETRY)
            button.bind('<Button-1>', partial(self.callback, led_nr))
            self.create_window(xpos, ypos, window=button)
            
    def zeigerBewegung(self):
        x = RADIUS * sin(radians(self.angle)) + self.x_center
        y = RADIUS * cos(radians(180 + self.angle)) + self.y_center
        self.angle += LED_ANGLE_GAP
        return x, y

    def callback(self, led_nr, event):
        print('LED-{}'.format(led_nr))
        widget = event.widget
        if widget['bg'] == LED_ON:
            widget['bg'] = LED_OFF
            widget['activebackground'] = LED_OFF
        else:
            widget['bg'] = LED_ON
            widget['activebackground'] = LED_ON
           
 
def main():
    app_win = tk.Tk()
    app_win.title(APP_TITLE)
    app_win.geometry("+{}+{}".format(APP_XPOS, APP_YPOS))
    app_win.option_add('*highlightThickness', 0)
    LedRosette(app_win).pack()
    app_win.mainloop()


if __name__ == '__main__':
    main()  
Gruss wuf :wink:
Take it easy Mates!
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@Alfons Mittelmeyer: Doch der hat verschiedene Farben für an und aus.
Ja, wo hat der die denn?

- background - ist es wohl nicht
- activebackground - ist es auch nicht
- disabledforeground erst recht nicht
- highlightcolor auch nicht
- highlightbackground auch nicht
- selectcolor ist es auch nicht

mit off sehe ich nur ein offrelief
aber eine Reaktion darauf ist auch nicht zu erkennen - zumindest nicht unter Linux

Der Unterschied zu einem Button ist, dass man da mit selectcolor auch noch die Farbe des Feldes wählen kann, in dem man sein Häkchen setzt, aber das ist auch nicht davon abhängig, ob ein Häkchen gesetzt ist oder nicht!!!
BlackJack

@Alfons Mittelmeyer: `selectcolor` ist es. Und ein Häkchen gibt es nicht mehr wenn man sich das als Schaltfläche anzeigen lässt.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@Alfons Mittelmeyer: `selectcolor` ist es. Und ein Häkchen gibt es nicht mehr wenn man sich das als Schaltfläche anzeigen lässt.
Ja stimmt, mit indicatoron auf 0 läßt sich das machen.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hier noch die Variante mit Checkbuttons:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from functools import partial
from math import sin, cos, radians

try:
    # Tkinter for Python 2.xx
    import Tkinter as tk
except ImportError:
    # Tkinter for Python 3.xx
    import tkinter as tk

APP_TITLE = "LED-Rosette"
APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 500
APP_HEIGHT = 500

LEDS = 24
LED_GEOMETRY = 20
LED_ON = 'red'
LED_OFF = 'green'
LED_ANGLE_GAP = 360 / LEDS
RADIUS = 200

class CbLedRosette(tk.Canvas):
    def __init__(self, master=None):
        tk.Canvas.__init__(self, master)
        
        self.angle = 0 

        self.x_center = (APP_WIDTH / 2)
        self.y_center = (APP_HEIGHT / 2)

        self.canvas = tk.Canvas(self, width=APP_WIDTH, height=APP_HEIGHT)
        self.canvas.pack()
        
        self.dummy_img = tk.PhotoImage(width=1, height=1)
        self.button_states = [False]*LEDS
        
        for led_nr in range(LEDS):
            xpos, ypos = self.place_coords()
            button = tk.Checkbutton(self, bg=LED_OFF, #activebackground=LED_OFF,
                selectcolor='red', indicatoron=0, image=self.dummy_img,
                width=LED_GEOMETRY, height=LED_GEOMETRY, onvalue=1, offvalue=0)
            button.place(x=0, y=0)
            button.bind('<Button-1>', partial(self.callback, led_nr))
            self.create_window(xpos, ypos, window=button)
            
    def place_coords(self):
        x = RADIUS * sin(radians(self.angle)) + self.x_center
        y = RADIUS * cos(radians(180 + self.angle)) + self.y_center
        self.angle += LED_ANGLE_GAP
        return x, y

    def callback(self, led_nr, event):
        print('LED-{}'.format(led_nr))
        
        widget = event.widget
        if widget['bg'] == LED_ON:
            self.button_states[led_nr] = False
            widget['bg'] = LED_OFF
        else:
            self.button_states[led_nr] = True
            widget['bg'] = LED_ON
        print(self.button_states)
           
 
def main():
    app_win = tk.Tk()
    app_win.title(APP_TITLE)
    app_win.geometry("+{}+{}".format(APP_XPOS, APP_YPOS))
    app_win.option_add('*highlightThickness', 0)
    CbLedRosette(app_win).pack()
    app_win.mainloop()


if __name__ == '__main__':
    main()  
BEMERKUNG: Die Handhabung der Button-Farbe für die verschiedenen Zustände ist schwieriger als bei einem normalen Button. Auch die Festlegung der Checkbutton-Dimension benötigt eine Sonderbehandlung.

Hier nochmals die leicht abgeänderte Variante mit normalen Buttons:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from functools import partial
from math import sin, cos, radians

try:
    # Tkinter for Python 2.xx
    import Tkinter as tk
except ImportError:
    # Tkinter for Python 3.xx
    import tkinter as tk

APP_TITLE = "LED-Rosette"
APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 500
APP_HEIGHT = 500

LEDS = 24
LED_GEOMETRY = 20
LED_ON = 'red'
LED_OFF = 'green'
LED_ANGLE_GAP = 360 / LEDS
RADIUS = 200


class LedRosette(tk.Canvas):
    def __init__(self, master=None):
        tk.Canvas.__init__(self, master)
        
        self.angle = 0 

        self.x_center = (APP_WIDTH / 2)
        self.y_center = (APP_HEIGHT / 2)

        self.canvas = tk.Canvas(self, width=APP_WIDTH, height=APP_HEIGHT)
        self.canvas.pack()
        self.button_states = [False]*LEDS
        
        for led_nr in range(LEDS):
            xpos, ypos = self.place_coords()
            button = tk.Button(self, bg=LED_OFF, activebackground=LED_OFF)
            button.place(x=0, y=0, width=LED_GEOMETRY, height=LED_GEOMETRY)
            button.bind('<Button-1>', partial(self.callback, led_nr))
            self.create_window(xpos, ypos, window=button)
            
    def place_coords(self):
        x = RADIUS * sin(radians(self.angle)) + self.x_center
        y = RADIUS * cos(radians(180 + self.angle)) + self.y_center
        self.angle += LED_ANGLE_GAP
        return x, y

    def callback(self, led_nr, event):
        print('LED-{}'.format(led_nr))
        widget = event.widget
        if widget['bg'] == LED_ON:
            self.button_states[led_nr] = False
            widget['bg'] = LED_OFF
            widget['activebackground'] = LED_OFF
        else:
            self.button_states[led_nr] = True
            widget['bg'] = LED_ON
            widget['activebackground'] = LED_ON
        print(self.button_states)    
           
 
def main():
    app_win = tk.Tk()
    app_win.title(APP_TITLE)
    app_win.geometry("+{}+{}".format(APP_XPOS, APP_YPOS))
    app_win.option_add('*highlightThickness', 0)
    LedRosette(app_win).pack()
    app_win.mainloop()


if __name__ == '__main__':
    main()  
Gruss wuf :wink:
Take it easy Mates!
Antworten