Workshop: Gui Entwicklung in tkinter

Fragen zu Tkinter.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Das wäre dann ein sichtbares Beispiel für Deine InputGUI. Natürlich toggelt da jetzt nichts.

AppInput.py

Code: Alles auswählen

# -*- coding: utf-8 -*-

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

#import DynTkInter as tk # for GuiDesigner


#============= imports call Code ===================
import testcode

# Application definition ============================

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.minsize(300, 300)
        # widget definitions ===================================
        self.InputContentGUI = InputGUI(self,name='#0_InputContentGUI')
        self.InputContentGUI.pack(expand=1, fill='both')

class InputGUI(tk.Frame):

    def __init__(self,master,**kwargs):
        tk.Frame.__init__(self,master,**kwargs)
        self.myclass = 'InputGUI'
        self.call_code = 'testcode.InputCode'
        self.config(relief='solid', bd=4)
        # widget definitions ===================================
        self.sub_frameA = tk.Frame(self,name='#1_sub_frameA',height=40, bg='#ffffd3')
        self.sub_frameB = tk.Frame(self,name='#2_sub_frameB',height=40, bg='#e4f3fc')
        self.sub_frameC = tk.Frame(self,name='#3_sub_frameC',relief='sunken', width=50, bg='#cbeed8')
        self.sub_frameA.pack(fill='x')
        self.sub_frameB.pack(fill='x')
        self.sub_frameC.pack(expand=1, fill='both', side='left')
        # call Code ===================================
        testcode.InputCode(self)

if __name__ == '__main__':
    #Application().mainloop('guidesigner/Guidesigner.py') # for GuiDesigner
    Application().mainloop()
Für die OutputGUI hast Du kein eigenes Modul spendiert. Warum nicht, machen wir es doch schön symmetrisch.

Dann ist das:

AppOutput.py

Code: Alles auswählen

# -*- coding: utf-8 -*-

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

#import DynTkInter as tk # for GuiDesigner


#============= imports call Code ===================
import testcode

# Application definition ============================

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.minsize(400, 400)
        # widget definitions ===================================
        self.OutputContentGUI = OutputGUI(self,name='#0_OutputContentGUI')
        self.OutputContentGUI.pack(fill='both', expand=1)

class OutputGUI(tk.Frame):

    def __init__(self,master,**kwargs):
        tk.Frame.__init__(self,master,**kwargs)
        self.myclass = 'OutputGUI'
        self.call_code = 'testcode.OutputCode'
        self.config(bd=4, bg='#ffeb00', relief='solid')
        # call Code ===================================
        testcode.OutputCode(self)

if __name__ == '__main__':
    #Application().mainloop('guidesigner/Guidesigner.py') # for GuiDesigner
    Application().mainloop()
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Der Toolbar Code ist sehr einfach:

testcode.py

Code: Alles auswählen

from communication import eventbroker, publish, subscribe

class InputCode:

    def __init__(self,container):
        self.container = container
        subscribe('AUSGABE_EIN',self.lift_eingabe)

    def lift_eingabe(self,ausgabe_ein):
        if not ausgabe_ein:
            self.container.lift()

class OutputCode:

    def __init__(self,container):
        self.container = container
        subscribe('AUSGABE_EIN',self.lift_ausgabe)

    def lift_ausgabe(self,ausgabe_ein):
        if ausgabe_ein:
            self.container.lift()

def ToolbarCode(self):
    self.schalter1.connect(eventbroker,'AUSGABE_EIN',False)
Es braucht hier nur der Schalter verbunden werden.

Nachdem der led_switch fertig ist:

led_switch.py

Code: Alles auswählen

# -*- coding: utf-8 -*-

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

#import DynTkInter as tk # for GuiDesigner

# Application definition ============================

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.minsize(800, 100)
        # 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)

        # fitting canvas to frame height and enable switching =====
        self.canvas_height_before = 100
        self.bind('<Configure>',self.width_height,'+')
        self.on = False
        self.eventbroker = None
        self.message = None
        self.canvas.bind('<Button-1>',self.switch)

    # for connection and state at beginning ============
    def connect(self,eventbroker,message,on = False):
        self.on = on
        self.eventbroker = eventbroker
        self.message = message
        self.switch_update()
    
    def width_height(self,event):
        frame_height = event.height
        frame_width = event.width
        frame_size = min(event.height,event.width)
        bd = frame_size/14
        canvas_height = frame_size-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)
        x = (frame_width - frame_size)/2
        y = (frame_height - frame_size)/2
        self.canvas.place(x = x, y = y)

    # show and signal on status =========================
    def switch_update(self):
        self.canvas.itemconfig('led',fill = 'lightgreen' if self.on else '#ff7431')
        self.canvas['relief'] = 'sunken' if self.on else'raised'
        if self.eventbroker:
            self.eventbroker.publish(self.message,self.on)

    def switch(self,event):
        self.on = not self.on
        self.switch_update()

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('guidesigner/Guidesigner.py') # for GuiDesigner
    Application().mainloop()
Kann er leicht in die Toolbar eingebunden werden:

AppToolbar.py

Code: Alles auswählen

# -*- coding: utf-8 -*-

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

#import DynTkInter as tk # for GuiDesigner


#============= imports baseclass ===================
import led_switch

#============= imports call Code ===================
import testcode

# Application definition ============================

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.minsize(400, 100)
        # widget definitions ===================================
        self.ToolbarGUI = Toolbar(self,name='#0_ToolbarGUI')
        self.ToolbarGUI.pack(fill='both', expand=1)

class Toolbar(tk.Frame):

    def __init__(self,master,**kwargs):
        tk.Frame.__init__(self,master,**kwargs)
        self.myclass = 'Toolbar'
        self.call_code = 'testcode.ToolbarCode'
        # widget definitions ===================================
        self.schalter1 = Schalter1(self,name='#1_schalter1')
        self.schalter1.place(relwidth='0.2', height='-20', x='10', y='10', relheight='1')
        # call Code ===================================
        testcode.ToolbarCode(self)

class Schalter1(led_switch.Switch):

    def __init__(self,master,**kwargs):
        led_switch.Switch.__init__(self,master,**kwargs)
        self.myclass = 'Schalter1'
        self.baseclass = 'led_switch.Switch'

if __name__ == '__main__':
    #Application().mainloop('guidesigner/Guidesigner.py') # for GuiDesigner
    Application().mainloop()
Der Schalter tut schon, man sieht es am Konsolen Output. Dann bräuchte man nur noch alles zusammenbauen
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Oh, es gibt da noch ein Problem. Dieser Eventbroker ist nicht für mehre Empfänger ausgerüstet. Da müßte man ihn zuvor aufrüsten. Ich wollte ihn einfach halten und dabei vermeiden, dass etwa bei Widgets Löschen und wieder aufbauen immer mehr Callbacks entstehen. Bei meinem eigenen Eventbroker ist die destroy Methode aller Containerwidgets überschrieben, so dass bei destroy alle Message Abonnements für sie gelöscht werden. Aber das geht hier zu weit.

Entweder man bleibt bei diesem Eventbroker und nimmt einen Verteiler, der eine Message in mehrere andere umschaufelt, aber besser ist, man rüstet dieses beim Eventbroker nach.
stefanxfg
User
Beiträge: 85
Registriert: Sonntag 2. April 2017, 14:11

Also zunächst einmal Danke. Ich habe erst einmal meinen Button im Toolbar mit einem Command ausgestattet und in dann auf Publish gesetzt. Der Rest läuft, wie du bereits geschrieben hast.

Ich glaube mich zu erinnern, dass beim Eventbroker durchaus mehrmalige Betätigung auch schon zu Fehlern geführt hatte.

Wenn er nun nicht mehrere Empfänger bedienen kann, dann kann ich nicht einfach den Button im Toolbar drücken, die Eingaben im InputGUI ausblenden und die Ausgaben im OutputGUI einblenden, weil dass dann 2 Empfänger sind. Vielleicht hast du ja doch noch eine ruhige Minute und kannst ihn mal dazu aufrüsten. :wink:
Ich würde mich freuen.

Übrigens, warum exportiert der GUIDesigner mit Leerzeichen und nicht mit Tabs?


Viele Grüße Stefan
stefanxfg
User
Beiträge: 85
Registriert: Sonntag 2. April 2017, 14:11

Naja und jetzt geht mir ein Licht auf, was ich schon vorher falsch gemacht hatte. Ich hatte immer zur Hauptklasse gesendet und empfangen.

Also werde ich es nun richtig machen und jede einzelne Klasse mit Widget zum Sender machen. Also z.B. beim Toolbar gibt es das Fileframe, welche die Buttons "newfile" und "openfile" beinhaltet. Hierzu ein Sender und nicht aus der darüber befindlichen Klasse,welche als Container alle Untercontainer der einzelnen Toolbarleisten in sich herbergt.
Ob ich das beim Input als Empfänger genauso mache, weiß ich noch nicht. Liegt möglicherweise am Umstand, was man machen möchte.
BlackJack

@stefanxfg: Eingerückt wird mit vier Leerzeichen pro Ebene. Nicht mit Tabs. Siehe den Style Guide for Python Code.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

stefanxfg hat geschrieben:Ich glaube mich zu erinnern, dass beim Eventbroker durchaus mehrmalige Betätigung auch schon zu Fehlern geführt hatte.
Mehrmaliges publish sicher nicht. Aber wenn zwei Empfänger eines subscribe auf dieselbe Message machen, dann klaut sie der zweite vom ersten. Da gibt es aber eine Konsolenausgabe dafür. Der Grund nur einen Callback zu erlauben, ist dass man bei Widget Löschen und wieder neu subscribe zu machen, keine ungüpltige callbacks mehr übrig bleiben.
stefanxfg hat geschrieben:Übrigens, warum exportiert der GUIDesigner mit Leerzeichen und nicht mit Tabs?
Damit man den Code hier in das Forum stellen kann. Statt Tabs nimmt man hier vier Leerzeichen.
stefanxfg hat geschrieben:Wenn er nun nicht mehrere Empfänger bedienen kann, dann kann ich nicht einfach den Button im Toolbar drücken, die Eingaben im InputGUI ausblenden und die Ausgaben im OutputGUI einblenden, weil dass dann 2 Empfänger sind. Vielleicht hast du ja doch noch eine ruhige Minute und kannst ihn mal dazu aufrüsten. :wink:
Ich würde mich freuen.

Viele Grüße Stefan
Schon geschehen, aber noch nicht auf GitHub

Code: Alles auswählen

import sys
def output(param):
    sys.stdout.write(param+'\n')

class EventBroker():
    def __init__(self):
        self._dictionary_ = {}
 
    def subscribe(self,message_id,callback_or_alias):

        is_string = True

        try: # python2
            is_string = isinstance(callback_or_alias,basestring)
        except NameError: #python 3
            is_string = isinstance(callback_or_alias,str)
            
        if is_string:
            if message_id not in self._dictionary_:
                self._dictionary_[message_id] = set()
            self._dictionary_[message_id].add(callback_or_alias)
            
        else:
            if message_id in self._dictionary_:
                output('EventBroker: callback already defined for message id: {}\nThe callback before will be overwritten'.format(message_id))

            self._dictionary_[message_id] = callback_or_alias
 

    def publish(self,message_id,*args,**kwargs):
        if message_id not in self._dictionary_:
            output('EventBroker: no callback defined for message: {},*{},**{}'.format(message_id,args,kwargs))
        else:
            callback = self._dictionary_[message_id]
            if not isinstance(callback ,set):
                return callback(*args,**kwargs)
            for element in callback:
                if element in self._dictionary_:
                    callback = self._dictionary_[element]
                    if isinstance(callback,set):
                        output("EventBroker: for message id '{}' is alias '{}' allowed, but no furter aliases for this alias".format(message_id,element))
                    else:
                        callback(*args,**kwargs)


eventbroker = EventBroker()
publish = eventbroker.publish
subscribe = eventbroker.subscribe


if __name__ == '__main__':

# =========================== Test ==============================
# for aliases are only strings allowed

    def receiver_A():

        def got(info):
            output('receiver_A got info: ' + info)

        subscribe('BROADCAST','BROADCAST_A')
        subscribe('BROADCAST_A',got)

    def receiver_B():

        def got(info):
            output('receiver_B got info: ' + info)

        subscribe('BROADCAST','BROADCAST_B')
        subscribe('BROADCAST_B',got)

    receiver_A()
    receiver_B()

    publish('BROADCAST','NEWS')
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Nach Korrektur dieses Codes:

testcode.py

Code: Alles auswählen

from communication import eventbroker, publish, subscribe

class InputCode:

    def __init__(self,container):
        self.container = container

        subscribe('AUSGABE_EIN','LIFT_EINGABE')
        subscribe('LIFT_EINGABE',self.lift_eingabe)

    def lift_eingabe(self,ausgabe_ein):
        if not ausgabe_ein:
            self.container.lift()

class OutputCode:

    def __init__(self,container):
        self.container = container
        subscribe('AUSGABE_EIN','LIFT_AUSGABE')
        subscribe('LIFT_AUSGABE',self.lift_ausgabe)

    def lift_ausgabe(self,ausgabe_ein):
        if ausgabe_ein:
            self.container.lift()

def ToolbarCode(self):
    self.schalter1.connect(eventbroker,'AUSGABE_EIN',False)
wurden dann bei diesem Anfangsbeispiel: viewtopic.php?f=18&t=40677

mit dem GuiDesigner die Text Felder herausgelöscht und stattdessen Frames hineingesetzt und für diese die entsprechenden Eintragungen gemacht. Das ist dann das Resultat:

Code: Alles auswählen

# -*- coding: utf-8 -*-

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

#import DynTkInter as tk # for GuiDesigner


#============= imports baseclass ===================
import AppOutput
import AppToolbar
import AppInput

# === general grid table definition =================
def grid_general_rows(container,rows,**kwargs):
    for row in range(rows):
        container.rowconfigure(row,**kwargs)

def grid_general_cols(container,columns,**kwargs):
    for column in range(columns):
        container.columnconfigure(column,**kwargs)

# Application definition ============================

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.minsize(500, 400)
        # widget definitions ===================================
        self.frame_guinotes = GuiNotes(self,name='#0_frame_guinotes')
        self.frame_menunotes = FrameMenunotes(self,name='#1_frame_menunotes')
        self.frame_toolbarnotes = FrameToolbarnotes(self,name='#2_frame_toolbarnotes')
        self.label_PS = tk.Label(self,name='#3_label_PS',bd='4', anchor='w', pady='4', fg='#a90000', relief='solid', font='TkFixedFont 10 bold', text='Welches Layout soll man nehmen?\nUnd soll man einen GuiBuilder nehmen?\nWenn ja, welchen?', padx='4', justify='left')
        self.frame_menunotes.pack(fill='x')
        self.frame_toolbarnotes.pack(fill='x')
        self.frame_guinotes.pack(expand=1, fill='both')
        self.label_PS.pack(fill='x', pady=3)

class GuiNotes(tk.Frame):

    def __init__(self,master,**kwargs):
        tk.Frame.__init__(self,master,**kwargs)
        self.myclass = 'GuiNotes'
        self.config(bg='#ceff00', highlightthickness=7, highlightbackground='#a90000', highlightcolor='#a90000')
        # general grid definition ==============================
        grid_general_rows(self,1, minsize = 100, pad = 0, weight = 1)
        grid_general_cols(self,1, minsize = 100, pad = 0, weight = 1)
        # widget definitions ===================================
        self.InputContentGUI = Input(self,name='#4_InputContentGUI')
        self.InputContentGUI.grid(row=0, sticky='nesw')
        self.OutputGUI = Output(self,name='#5_OutputGUI')
        self.OutputGUI.grid(row=0, sticky='nesw')

class Input(AppInput.InputGUI):

    def __init__(self,master,**kwargs):
        AppInput.InputGUI.__init__(self,master,**kwargs)
        self.myclass = 'Input'
        self.baseclass = 'AppInput.InputGUI'

class Output(AppOutput.OutputGUI):

    def __init__(self,master,**kwargs):
        AppOutput.OutputGUI.__init__(self,master,**kwargs)
        self.myclass = 'Output'
        self.baseclass = 'AppOutput.OutputGUI'

class FrameMenunotes(tk.Frame):

    def __init__(self,master,**kwargs):
        tk.Frame.__init__(self,master,**kwargs)
        self.config(highlightbackground='blue', highlightthickness=7, highlightcolor='blue')
        # widget definitions ===================================
        self.notes_menu = tk.Text(self,name='#6_notes_menu',font='TkFixedFont 12 bold', pady=8, width=1, fg='blue', padx=8, height=1)
        self.notes_menu.delete(1.0, tk.END)
        self.notes_menu.insert(tk.END,'Oben soll ein Menü sein')
        self.notes_menu.pack(expand=1, fill='both')

class FrameToolbarnotes(tk.Frame):

    def __init__(self,master,**kwargs):
        tk.Frame.__init__(self,master,**kwargs)
        self.config(relief='sunken', highlightbackground='#008900', highlightthickness=7, highlightcolor='#008900')
        # widget definitions ===================================
        self.ToolbarGUI = Toolbar(self,name='#7_ToolbarGUI')
        self.ToolbarGUI.pack(expand=1, fill='both')

class Toolbar(AppToolbar.Toolbar):

    def __init__(self,master,**kwargs):
        AppToolbar.Toolbar.__init__(self,master,**kwargs)
        self.myclass = 'Toolbar'
        self.baseclass = 'AppToolbar.Toolbar'
        self.config(height=60)

if __name__ == '__main__':
    #Application().mainloop('guidesigner/Guidesigner.py') # for GuiDesigner
    Application().mainloop()
Lediglich eine kleine Nachpesserung war nötig. In AppToolbar.py war das Padding mittels y und height etwas zu hoch eingestellt, wodurch dann der LED Switch zu klein wurde.

Code: Alles auswählen

        self.schalter1.place(relwidth='0.2', height='-10', x='10', y='5', relheight='1')
Hier war zuerst y = 10 und height = -20 und das war etwas zuviel. Wenn man einmal von meinem LED Switch absieht, war das bißchen in testcode.py der ganze zu schreibende Code.

Ist klar, dass man den ganzen generierten Code nicht händisch schreiben sollte, wenn man alles bequem und live erlebend mit dem GuiDesigner erledigen kann.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Noch eine wichtige Anmerkung:

wenn man statt den normalen tkinter Importen das importiert

Code: Alles auswählen

import DynTkInter as tk # for GuiDesigner
Und statt dem normalen mainloop dieses aufruft:

Code: Alles auswählen

Application().mainloop('guidesigner/Guidesigner.py') # for GuiDesigner
Dann startet der GuiDesigner mit zur Gui. Und da ist noch etwas wichtig. Wenn man das nur bei der Applikation macht und bei den anderen Modulen die normalen imports lässt, ist alles schön und funktionierend im GuiDesigner zu sehen. Bearbeiten und speichern kann man dann allerdings nur die Applikation. Denn diese ist dann in DynTkInter und für den GuiDesigner da. Das andere, obwohl sichtbar und funktionierend, nicht für den GuiDesigner existent.

Das ist dann sehr gut, wenn man nur die Applikation bearbeiten und speichern möchte, nicht aber die in Komponenten aufgeteilte Gui wieder zu einer zusammengefügt haben will.

Die Gui zusammenfügen kann man, wenn man auch für die Komponenten die Importe mit DynTkInter macht.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Der neue Eventbroker ist jetzt auf GitHub im Verzeichnis Utilities
Antworten