Warum geht winfo_children nicht?

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

Also mainloop() geht ja. Aber winfo_children() liefert eine leere Liste. Wieso geht das nicht?

Code: Alles auswählen

import tkinter
tcl = tkinter.Tcl(None,None,"test",1)

result = tcl.eval('''

pack [entry ".input a"]
pack [entry ".input b"]
pack [button ".a * b" -text "a * b"] -fill x
pack [label ".ergebnis von a * b"] -fill x

''')

print(tcl.winfo_children())

tcl.mainloop()
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Naja, leider legt tcl/tk keine Interfaces für tkinter an. Man kann also danach nicht mittels tkinter darauf zugreifen.
BlackJack

@Alfons Mittelmeyer: Wenn man `Tcl` das Flag zum verwenden von Tk mitgibt, kann man auch gleich ein `Tk`-Objekt erstellen.

Man kann auf die Widgets genau so zugreifen wie man sie erzeugt hat — über die Schnittstellen die `tkinter` zu Tcl/Tk bietet. Schön ist das natürlich nicht, aber es geht immerhin:

Code: Alles auswählen

from functools import partial
import tkinter as tk


def do_multiplication(root):
    try:
        operand_a = int(root.tk.call('.input a', 'get'))
        operand_b = int(root.tk.call('.input b', 'get'))
    except ValueError:
        result = ''
    else:
        result = operand_a * operand_b
    root.tk.call('.ergebnis von a * b', 'configure', '-text', result)


def main():
    root = tk.Tk()
     
    root.createcommand('doMultiplication', partial(do_multiplication, root))
    root.eval('''
        pack [entry {.input a}]
        pack [entry {.input b}]
        pack [button {.a * b} -text {a * b} -command doMultiplication] -fill x
        pack [label {.ergebnis von a * b}] -fill x
    ''')
    print(root.winfo_children())
    print(root.tk.call('winfo', 'children', '.'))

    root.mainloop()



if __name__ == '__main__':
    main()
Da auch der ``winfo children .``-Aufruf Ergebnisse liefert, könnte man sich eine Wrapper-Klasse schreiben, die vielleicht das eine oder andere einfacher macht.

Auch wenn man in Tcl Namen mit Leerzeichen und Sonderzeichen verwenden kann, würde ich das aber auch dort nicht tun, denn es ist mehr Tipparbeit bei der man sich leicht mal vertippen kann und die nicht von den üblichen Autovervollständigungen von Editoren unterstützt wird. Mal als Vergleich:
[codebox=tcl file=Unbenannt.txt]proc doMultiplication {} {
set operandA [{.input a} get]
set operandB [{.input b} get]
catch {{.ergebnis von a * b} configure -text [expr {$operandA * $operandB}]}
}

pack [entry {.input a}]
pack [entry {.input b}]
pack [button {.a * b} -text {a * b} -command doMultiplication] -fill x
pack [label {.ergebnis von a * b}] -fill x[/code]

Und mit konventionelleren Bezeichnern:
[codebox=tcl file=Unbenannt.txt]proc doMultiplication {} {
catch {.result configure -text [expr {[.inputA get] * [.inputB get]}]}
}

pack [entry .inputA]
pack [entry .inputB]
pack [button .button -text {a * b} -command doMultiplication] -fill x
pack [label .result] -fill x[/code]
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack: Danke, das ist eine sehr nützliche Information. Damit wäre es möglich auch tcl/tk Widgets in meinen GuiDesigner zu übernehmen. Bisher geht es nur bei tkinter GUI, wenn man dort einträgt:

import DynTkInter as tk

Weil es dann DynTkInter Widgets werden.

Aber über die children Liste könnte man auch tkinter Widgets nachträglich in DynTkInter übernehmen.
Besser wäre das allerdings bei tcl/tk, weil diese Widgets bereits Namen haben und ich dann nicht jeden Button 'button' nennen muss, weil ich keinen Namen habe.

DynTkInter verzeichnet außer dem Namen auch noch die Art des Layouts, also etwa pack, grid oder place.

Das ließe sich ja über grid_info, pack_info oder place_info ungleich leere Liste herausbringen. Allerdings nicht herausbringen kann man wohl die Reihenfolge in der pack erfolgte. Annehmen könnte man dann höchstens eine pack Reihenfolge synchron zur Create Widget Reihenfolge. Gibt es eine Möglichkeit, die pack Reihenfolge nachträglich herauszufinden?
BlackJack

Mal ein Ansatz einer sehr einfachen Wrapperklasse die Schlüsselzugriffe auf `cget` und `configure`-Aufrufe übersetzt und für Attributzugriffe aufrufbare Objekte liefert, die den Namen des Attributs als Subkommando und die Argumente 1:1 an das Widget in Tcl/Tk weiterreichen:

Code: Alles auswählen

from functools import partial
import tkinter as tk


class Widget(object):

    def __init__(self, root, tk_name):
        self.call = partial(root.tk.call, tk_name)

    def __getitem__(self, name):
        self.call('cget', '-' + name)

    def __setitem__(self, name, value):
        self.call('configure', '-' + name, value)

    def __getattr__(self, name):
        return partial(self.call, name)


def do_multiplication(root):
    operand_a_entry = Widget(root, '.input a')
    try:
        operand_a = int(operand_a_entry.get())
        operand_b = int(Widget(root, '.input b').get())
    except ValueError:
        result = ''
    else:
        result = operand_a * operand_b
    result_label = Widget(root, '.ergebnis von a * b')
    result_label['text'] = result
    # 
    # Alternativ:
    # 
    # result_label.configure('-text', result)

    # 
    # Komplett sinnlos, nur um noch eine andere Methode zu testen.
    # 
    operand_a_entry.insert(0, '+')
 
 
def main():
    root = tk.Tk()
     
    root.createcommand('doMultiplication', partial(do_multiplication, root))
    root.eval('''
        pack [entry {.input a}]
        pack [entry {.input b}]
        pack [button {.a * b} -text {a * b} -command doMultiplication] -fill x
        pack [label {.ergebnis von a * b}] -fill x
    ''')
    print(root.winfo_children())
    print(root.tk.call('winfo', 'children', '.'))
 
    root.mainloop()
 
  
if __name__ == '__main__':
    main()
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Ich habe auch noch einen Ansatz gefunden:

Code: Alles auswählen

from functools import partial
import tkinter as tk
import tk_convert 
 
def main():
    root = tk.Tk()
     
    root.eval('''
       pack [entry {.input_a}]
       pack [entry {.input_b}]
       pack [button {.a_x_b} -text {a * b}] -fill x
       pack [label {.result}] -fill x
   ''')

    root = tk_convert.convert(root)

    for child in root.winfo_children():

        if str(child) == '.input_a':
            input_a = child
        elif str(child) == '.input_b':
            input_b = child
        elif str(child) == '.a_x_b':
            a_x_b = child
        elif str(child) == '.result':
            result = child

    def multiply():
        try:
            result['text'] = int(input_a.get()) * int(input_b.get())
        except ValueError:
            pass

    a_x_b['command'] = multiply

    root.mainloop()
 
if __name__ == '__main__':
    main()
Ich konvertiere tk in tkinter. Allerdings muss man sich dann die Widgets aus der Children Liste herausklauben.

Besser is es nach der Konvertierung den GuiDesigner zu laden:

Code: Alles auswählen

import DynTkInter as tk
import tk_convert 
 
def main():
    root = tk.Tk()
     
    root.eval('''
       pack [entry {.input_a}]
       pack [entry {.input_b}]
       pack [button {.a_x_b} -text {a * b}] -fill x
       pack [label {.result}] -fill x
   ''')

    root = tk_convert.convert(root)
    root.mainloop('guidesigner/Guidesigner.py')
 
main()
Und es damit dann so abspeichern:

Code: Alles auswählen

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

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

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.geometry('166x86+1625+683')
        # widget definitions ===================================
        self.input_a = tk.Entry(self)
        self.input_b = tk.Entry(self)
        self.a_x_b = tk.Button(self,text='a * b')
        self.result = tk.Label(self)
        self.input_a.pack()
        self.input_b.pack()
        self.a_x_b.pack(fill='x')
        self.result.pack(fill='x')

if __name__ == '__main__':
    Application().mainloop()
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben: Man kann auf die Widgets genau so zugreifen wie man sie erzeugt hat — über die Schnittstellen die `tkinter` zu Tcl/Tk bietet. Schön ist das natürlich nicht, aber es geht immerhin
Das mit dem tk.call war wirklich ein hervorragender Tip.

Ich verstehe zwar diesen Code noch nicht, weil ich mich mit tcl/tk noch nicht beschäftigt habe:

Code: Alles auswählen

proc piechart {w x y width height data} {
   set coords [list $x $y [expr {$x+$width}] [expr {$y+$height}]]
   set xm  [expr {$x+$width/2.}]
   set ym  [expr {$y+$height/2.}]
   set rad [expr {$width/2.+20}]
   set sum 0
   foreach item $data {set sum [expr {$sum + [lindex $item 1]}]}
   set start 270
   foreach item $data {
       foreach {name n color} $item break
       set extent [expr {$n*360./$sum}]
       $w create arc $coords -start $start -extent $extent -fill $color
       set angle [expr {($start-90+$extent/2)/180.*acos(-1)}]
       set tx [expr $xm-$rad*sin($angle)]
       set ty [expr $ym-$rad*cos($angle)]
       $w create text $tx $ty -text $name:$n  -tag txt
       set start [expr $start+$extent]
   }
   $w raise txt
}

pack [canvas .c -bg white]
piechart .c 50 50 150 150 {
   {SPD  199 red}
   {CDU  178 gray}
   {CSU   23 blue}
   {FDP   60 yellow}
   {Grüne 58 green}
   {Linke 55 purple}
}
Aber mit tk.call läßt er sich konvertieren - das heißt nicht der Code, sondern die dadurch erzeugte GUI:

Code: Alles auswählen

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

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

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.geometry('380x266+369+0')
        # widget definitions ===================================
        self.c = C(self)
        self.c.pack()

class C(tk.Canvas):

    def __init__(self,master,**kwargs):
        tk.Canvas.__init__(self,master,**kwargs)
        self.config(bg='white')
        # widget definitions ===================================
        coords = (50,50,200,200)
        item = self.create_arc(*coords)
        self.itemconfig(item,start = '270.0',extent = '125.0261780104712',fill = 'red')

        coords = (50,50,200,200)
        item = self.create_arc(*coords)
        self.itemconfig(item,start = '35.0261780104712',extent = '111.83246073298429',fill = 'gray')

        coords = (50,50,200,200)
        item = self.create_arc(*coords)
        self.itemconfig(item,start = '146.85863874345546',extent = '14.450261780104713',fill = 'blue')

        coords = (50,50,200,200)
        item = self.create_arc(*coords)
        self.itemconfig(item,start = '161.30890052356017',extent = '37.696335078534034',fill = 'yellow')

        coords = (50,50,200,200)
        item = self.create_arc(*coords)
        self.itemconfig(item,start = '199.00523560209422',extent = '36.43979057591623',fill = 'green')

        coords = (50,50,200,200)
        item = self.create_arc(*coords)
        self.itemconfig(item,start = '235.4450261780105',extent = '34.55497382198953',fill = 'purple')

        coords = (209,168)
        item = self.create_text(*coords)
        self.itemconfig(item,text = 'SPD:199',tags = 'txt')

        coords = (123,30)
        item = self.create_text(*coords)
        self.itemconfig(item,text = 'CDU:178',tags = 'txt')

        coords = (39,83)
        item = self.create_text(*coords)
        self.itemconfig(item,text = 'CSU:23',tags = 'txt')

        coords = (30,125)
        item = self.create_text(*coords)
        self.itemconfig(item,text = 'FDP:60',tags = 'txt')

        coords = (49,182)
        item = self.create_text(*coords)
        self.itemconfig(item,text = 'Grüne:58',tags = 'txt')

        coords = (96,215)
        item = self.create_text(*coords)
        self.itemconfig(item,text = 'Linke:55',tags = 'txt')

if __name__ == '__main__':
    Application().mainloop()
Naja, ich lese zuerst mit tk.call die tcl/tk GUI aus und erzeuge dann eine Datenstruktur.
Und damit erzeuge ich dann die tkinter GUI. Ist noch nicht ganz fertig:

Code: Alles auswählen

def build_gui(widget,gui_dict):

    # config ==================
    widget.config(**gui_dict['config'])

    # row and columncofigure ====

    for entry in gui_dict['rowconfigure']:
        widget.rowconfigure(entry[0],**entry[1])
    for entry in gui_dict['columnconfigure']:
        widget.columnconfigure(entry[0],**entry[1])

    # layout ===============
    layout = gui_dict['layout']
    info = gui_dict['layout_info']
    if layout == 'TOPWINDOW':
        widget.title(info['title'])
        widget.geometry(info['geometry'])
        widget.minsize(*info['minsize'])
        widget.maxsize(*info['maxsize'])
        widget.resizable(*info['resizable'])
    elif layout == 'grid':
        widget.grid(**info)
    elif layout == 'place':
        widget.place(**info)
    elif layout == 'pack':
        widget.pack(**info)

    # children =============
    for element in gui_dict['children']:
        class_name = element[1]['class']

        if class_name == 'Labelframe':
            class_name = 'LabelFrame'

        # name not empty for widgets
        if element[0]:
            name = element[0].split('.')[-1]
            child = eval("tk.{}(widget,name='{}')".format(class_name,name))
            # recursive call for children ===
            build_gui(child,element[1])

        # name empty and canvas items
        elif class_name == 'CanvasItem':
            if element[1]['type'] == 'arc':
                widget.create_arc(*element[1]['coords'],**element[1]['config'])
            if element[1]['type'] == 'text':
                widget.create_text(*element[1]['coords'],**element[1]['config'])
Na, den Canvas mach ich noch ziemlich fertig, aber Menüs, PanedWindows und Notebooks fehlen noch
Zuletzt geändert von Anonymous am Donnerstag 3. August 2017, 23:50, insgesamt 1-mal geändert.
Grund: Quelltext in Codebox-Tags gesetzt.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Besondere Behandlungen brauchten die PanedWindows. Und auch das Notebook. Mit dem Notebook bin ich fertig. Es fehlt dann nur noch das Menü.

Hier ist ein Beispiel für das Notebook:

[codebox=tcl file=Unbenannt.txt]#!/usr/bin/wish

ttk::notebook .n -width 100 -height 100
ttk::frame .n.f1;
ttk::frame .n.f2;
.n add .n.f1 -text "TabOne"
.n add .n.f2 -text "TabTwo"
pack [label .n.f1.f2 -background red -foreground white -text "TabOne"]
pack [label .n.f2.f2 -background red -foreground white -text "TabTwo"]
pack .n[/code]

Ein solcher Code adressiert die Widgets mit ihrem kompletten Pfad. Telie der GUI lassen sich daher nicht umverlagern, indem man sie nach woanders verlegt.

In DynTkInter sieht es dann so aus:

Code: Alles auswählen

ttk.Notebook('n',**{'height': 100, 'width': 100})
goIn()
ttk.Frame('f1')
goIn()
Label('f2',**{'text': 'TabOne', 'fg': 'white', 'bg': 'red'})
widget('f2').pack()
goOut()
ttk.Frame('f2')
goIn()
Label('f2',**{'text': 'TabTwo', 'fg': 'white', 'bg': 'red'})
widget('f2').pack()
goOut()
widget('f1').page(text='TabOne')
widget('f2').page(text='TabTwo')
goOut()
widget('n').pack()
DynTkInter arbeitet mit einer Verzeichnisstruktur. Man ist in einem current Directory oder curent master und verwendet dann nur die Kurznamen, die nur dort gültig sind. Mit goIn (enspricht einem cd in das current Widget) wechselt man dort hinein und wechselt den Master. Mit goOut (entspricht cd ..) wechselt man wieder zurück. Der Master wird nirgendwo referenziert. Wo man ihn bräuchte, nämlich etwa add für eine Notebookpage, bei dem man normalerweise für einen Master ein Child angibt, gibt es hier page für das child, welches das Child beim Master als page registriert,

Da nirgendwo komplette Pfade angeben sind und auch nirgendwo ein Master referenziert wird, können Codeteile beliebig umverlagert werden. Natürlich einen Frame darf man nicht in ein Menü verlagern, das würde crashen.

Entsprechend strukturierter tcl/tk Code, bei dem Prozeduren und Variablen eingesetzt werden, ist aber auch umverlagerbar. Umverlagerbar ist auch entsprechend strukturierter tkinter Code. Das ist daraus erzeugter tkinter Code:

Code: Alles auswählen

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

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

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        # widget definitions ===================================
        self.n = N_1(self)
        self.n.pack()

class N_1(ttk.Notebook):

    def __init__(self,master,**kwargs):
        ttk.Notebook.__init__(self,master,**kwargs)
        self.config(height=100, width=100)
        # widget definitions ===================================
        self.f1 = F1(self)
        self.f2 = F2(self)
        self.add(self.f1,text='TabOne')
        self.add(self.f2,text='TabTwo')

class F1(ttk.Frame):

    def __init__(self,master,**kwargs):
        ttk.Frame.__init__(self,master,**kwargs)
        # widget definitions ===================================
        self.f2 = tk.Label(self,text='TabOne', fg='white', bg='red')
        self.f2.pack()

class F2(ttk.Frame):

    def __init__(self,master,**kwargs):
        ttk.Frame.__init__(self,master,**kwargs)
        # widget definitions ===================================
        self.f2 = tk.Label(self,text='TabTwo', fg='white', bg='red')
        self.f2.pack()

if __name__ == '__main__':
    Application().mainloop()
Durch diese Strukturierung in Klassen, dem init, dem Aufruf der Basisklasse und der Kommentarzeile und den imports für python2 und python3, wird er allerdings ein wenig länglich.
Antworten