Tabelle in einem Toplevel benutzen (pack / grid)

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Trinity.95
User
Beiträge: 4
Registriert: Freitag 24. Mai 2019, 17:29

Hallo Python Forum!
Ich habe vor kurzem angefangen in python zu programmieren und bin auf ein Problem gestoßen zu dem Ich gerade keine Lösung parat habe und auch im Internet leider nichts gefunden habe. Nachdem Ich bei stackoverflow an meine nervlichen Grenzen gekommen bin da ich anscheinend den Code nicht richtig formatiert habe, schreibe ich jetzt hier.

Ich will eine kleine GUI schreiben die mir über die Formel 1 Saison 18 Daten ausgibt.

Dazu lese ich Dateien ein und will mir diese dann in der GUI wieder ausgeben.

Ich versuche gerade eine Fahrerliste mit deren Platzierung und Punktestand in einer Tabelle anzeigen zu lassen, welche ich auch in einem seperatem Fenster zum laufen gebracht habe. Als ich jedoch das ganze in meinen "Haupt-Code" einbringen wollte bekam ich die Fehlermeldung dass das leider nicht geht. Mehr dazu später.

Mein Ziel ist es eine Tabelle in einem neuem Fenster, neben meinem Hauptfenster, anzeigen zu lassen womit ich toplevel benutzen will.

Hier erstmal der Code. Ich versuche noch viel und experimentiere, daher ist das ganze etwas unaufgeräumt.

In dem ersten Abschnitt habe ich die Klasse Fahrer erstellt um so eventuell später leichter auf diese Liste zuzugreifen.


from PIL import Image, ImageTk
import tkinter as tk
import pandas as pd
import folium
import webview


# Fahrer soll beinhalten: Name, Fahrzeug, Rennstall, Punkte
# Dient zur Vereinfachung beim Zugriff auf einen Fahrer mit seiner jeweiligen Punktzahl und Positionierung am Saison-Ende
class Fahrer:
def __init__(self,pl,na,pu):
self.__Platzierung = pl
self.__Name = na
self.__Punkte = pu

def setPlatzierung(self, platz_neu):
self.__Platzierung = platz_neu
def getPlatzierung(self):
return self.__Platzierung

def setName(self, name_neu):
self.__Name = name_neu
def getName(self):
return self.__Name

def setPunkte(self, punkte_neu):
self.__Punkte = punkte_neu
def getPunkte(self):
return self.__Punkte


# Definieren einer Funktion die über einen Button dann ein neues Fenster öffnet

"""
Kürzen!
"""

class MyButton(Button):

def createWindow_result():

window = Toplevel()
window.geometry("800x600")
window.title("Renn-Ergebnisse 2018")

driver = []; r=1; c=0; laenge = int(len("Fahrer 2018.txt"))

with open("Fahrer 2018.txt","r") as file:
for line in file:
line_splitted = line.strip().split(",")
platz = line_splitted[0]
name = line_splitted[1]
punkte = line_splitted[2]
driver += [Fahrer(platz,name,punkte)]

for i in range(20):
Label(text="Platzierung", relief=RIDGE, width=10).grid(row=0,column=0)
Label(text="Fahrer Name", relief=RIDGE, width=25).grid(row=0,column=1)
Label(text="Punkte", relief=RIDGE, width=10).grid(row=0,column=2)
Label(text=driver.getPlatzierung(), relief=RIDGE, width=10).grid(row=r, column=c)
Label(text=driver.getName(), relief=RIDGE, width=25).grid(row=r, column=c+1)
Label(text=driver.getPunkte(), relief=RIDGE, width=10).grid(row=r,column=c+2)
r =r + 1

def createWindow_details():
window = tk.Toplevel(root)
window.geometry("800x600")
window.title("Rennspezifische Daten")

def createWindow_pkt():
window = tk.Toplevel(root)
window.geometry("800x600")
window.title("Punkte-Tabelle")

def createWindow_map():
#window = tk.Toplevel(root)
#window.geometry("800x600")
#window.title("Karte")
webview.create_window("Karte","https://upload.wikimedia.org/wikipedia/ ... d-2019.svg")
#Karte einfügen?

def createWindow_pic():
window = tk.Toplevel(root)
window.geometry("800x600")
window.title("Strecken Layout")





#Karte mit Folium und pandas

locations = pd.DataFrame({
"lat":[-37.85,26.03,31.34,40.37,41.57,43.73,45.50,43.25,47.22,52.07,49.33,47.58,50.43,45.62,1.29,43.40,34.85,30.13,19.40,-23.70,24.47],
"lon":[144.96,50.51,121.22,49.85,2.26,7.42,-73.53,5.79,14.76,-1.01,8.57,19.24,5.97,9.28,103.86,39.97,136.54,-97.63,-99.08,-46.7,54.60],
"name":[
"Albert Park Circut","Bahrain International Circuit","Shanghai Audi International Circuit","Baku City Circuit","Circuit de Barcelona","Grand Prix de Monaco",
"Circuit Gilles-Villeneuve","Circuit Automobile Paul Ricard","Red Bull Ring","Silverstone Circuit","Hockenheim Ring","Hungaro Ring","Spa-Francorchamps","Autodrome Nazionale Monza",
"Marina Bay Circuit","Sochi Autodrome","Suzuka International","Circuit of the Americas","Autodrome Hermanos Rodriguez","Autodorome José Carlos Pace","Yas Marina Circuit"]
})

m = folium.Map(location=[53.55, 10.0], tiles="Stamen Toner", zoom_start=8)


tooltip = "Anzeigen"

for i in range(0,len(locations)):
folium.Marker([locations.iloc['lat'], locations.iloc['lon']], popup=locations.iloc['name'], tooltip=tooltip, icon=folium.Icon(color="green", icon="info-sign")).add_to(m)


m.save("Karte.html")


In diesem zweiten Abschnitt ist dann letztendlich der tkinter Code:

from tkinter import*
from PIL import Image, ImageTk

root = Tk()
root.geometry("800x600")
root.title("Projekt F1 2018 (Mark Salmon)")

rahmen = Frame(root,relief="ridge",borderwidth=3, bg="white")
rahmen.pack(fill="both", expand = 1)

willkommen = Label(rahmen, bg="white",text="Willkommen zu meinem Projekt im SoSe 2019 über die Saison 2018 in der Formel 1!")
willkommen.config(font = ("times",15,"italic"))
willkommen.pack()


# Formel 1 Logo einfügen
load = Image.open("F1Logo.png")
render = ImageTk.PhotoImage(load)
img = Label(image=render)
img.image = render
img.place(x=100, y=400, height=100,width=600)



# Text Einleitung für Weiterleitung neben Buttons
ergebnisse = Label(rahmen, text="Renn-Ergebnisse Saison 2018", bg="white")
details = Label(rahmen, text="Rennspezifische Daten", bg="white")
pkt_tabelle = Label(rahmen, text="Punkte Tabelle mit Fahrer Auswahl", bg="white")
folium_map = Label(rahmen, text="Rennstrecken Karte", bg="white")
pic = Label(rahmen, text="Renstrecken Layout", bg="white")

#Buttons für Weiterleitung, command einfügen für Weiterleitung
pick1 = MyButton(rahmen, cursor= "target", width=8, text="Start 1 ->", command=MyButton.createWindow_result)
pick2 = MyButton(rahmen, cursor= "target", width=8, text="Start 2 ->", command=MyButton.createWindow_details)
pick3 = MyButton(rahmen, cursor= "target", width=8, text="Start 3 ->", command=MyButton.createWindow_pkt)
pick4 = MyButton(rahmen, cursor= "target", width=8, text="Start 4 ->", command=MyButton.createWindow_map)
pick5 = MyButton(rahmen, cursor= "target", width=8, text="Start 5 ->", command=MyButton.createWindow_pic)

# Buttons für Weiterleitung mit place
pick1.place(x=20, y=80, width=120, height=25)
pick2.place(x=20, y=120, width=120, height=25)
pick3.place(x=20, y=160, width=120, height=25)
pick4.place(x=20, y=200, width=120, height=25)
pick5.place(x=20, y=240, width=120, height=25)

# Erläuterung für Weiterleitungsbutton mit place neben button von oben
ergebnisse.place (x=120, y=80, width=300, height=25)
details.place (x=104, y=120, width=300, height=25)
pkt_tabelle.place(x=137, y=160, width=300, height=25)
folium_map.place (x=96, y=200, width=300, height=25)
pic.place (x=96, y=240, width=300, height=25)

#Schließen vom Fenster mit eingebauter destroy Funktion. Bei quit läuft das Programm im Hintergrund weiter.
butt_quit = MyButton(rahmen,fg="red", cursor="pirate", width=15,text="Quit", command=root.destroy)
butt_quit.pack(side="bottom")


root.mainloop()

Und jetzt noch meine Fehlermeldung mit der Ich zurzeit leider nicht weiter weiß und auf euer Wissen und eure Erfahrung baue. :)


Exception in Tkinter callback
Traceback (most recent call last):
File "D:\Anaconda\lib\tkinter\__init__.py", line 1705, in __call__
return self.func(*args)
File "<ipython-input-27-0ebe02e80fe8>", line 57, in createWindow_result
Label(text="Platzierung", relief=RIDGE, width=10).grid(row=0,column=0)
File "D:\Anaconda\lib\tkinter\__init__.py", line 2226, in grid_configure
+ self._options(cnf, kw))
_tkinter.TclError: cannot use geometry manager grid inside . which already has slaves managed by pack

Was mir damit gesagt werden soll weiß ich. Nur leider habe ich keinen Weg diese zu lösen ohne weitere 4 Stunden reinzustecken.

Ich freue mich auf eure Antwort.

MfG

Trinity
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Trinity.95: Fangen wir doch gleich mal damit an das Du den Quelltext hier so formatieren solltest, das man ihn auch sinnvoll lesen kann. :-D Dafür gibt es Code-Tags, im vollständigen Beitragseditor auch über die Schaltfläche die mit </> beschriftet ist, einfügbar.

Im Traceback sieht man, dass Du ein `Label` erstellst. Wo denkst Du denn wo das dargestellt wird?
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Trinity.95: Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funtkion die `main()` heisst.

Objektorientierung musst Du anscheinend noch lernen. Beide Klassen würde man so nicht schreiben.

`Fahrer` hat lauter triviale Getter/Setter-Methoden die man so in Python nicht schreibt. Die doppelten führenden Unterstriche bei Attributen gehören da auch nicht hin. Da würde man *einen* Unterstrich verwenden um das Attribut als Implementierungsdetail zu kennzeichnen. Attribute die sowohl einen Getter als auch einen Setter haben, sind aber de facto Teil der öffentlichen API.

Namen schreibt man in Python klein_mit_unterstrichen. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase).

Abkürzungen die nicht allgemein bekannt sind, sollte man vermeiden. Man sollte sich nicht schämen Code laut vorzulesen. Wenn man eine Methodensignatur wie ``def __init__(self,pl,na,pu):`` laut ausspricht, denken die Leute man hätte einen Schlaganfall. Das sind besch…eidene Argumentnamen. Das ist Teil der öffentlichen API – anhand dieser Namen sollte ein Leser so ungefähr wissen was er da übergeben kann/soll. Da bleibt in Python dann das hier übrig:

Code: Alles auswählen

class Fahrer:
    
    def __init__(self, platzierung, name, punkte):
        self.platzierung = platzierung
        self.name = name
        self.punkte = punkte
Da die Attribute nicht gesetzt werden, kann man den Typ auch als `collections.namedtuple()` erstellen:

Code: Alles auswählen

Fahrer = namedtuple('Fahrer', 'platzierung name punkte')
Wenn man setzbare Attribute benötigt, oder Methoden auf dem Typ, bietet sich das externe `attrs`-Package an um so einen Typ einfacher zu erstellen.

Die Klasse `MyButton` macht keinen Sinn. Das fällt sofort auf wenn man mal versucht einen sinnvollen Namen dafür zu finden. Mir fällt keiner ein.

Es wird nichts gemacht was eine Ableitung von `tkinter.Button` irgendwie sinnvoll machen würde und die Klasse ist ansonsten einfach nur ein Namensraum für Funktionen. Dafür sind Klassen aber nicht gedacht. Namensraum für Funktionen sind Module.

Die ganzen `createWindow_irgendwas()`-Funktionen würden besser `create_irgendwas_window()` heissen. Eine flüssig lesbare Beschreibung dessen was die jeweilige Funktion tut.

`createWindow_result` macht den Eindruck, als wäre da eigentlich ein Sternchen-Import aus `tkinter` – das macht man nicht. Wie man es normalerweise macht, hast Du ja bereits im Code stehen: Das `tkinter`-Modul als `tk` importieren und die Werte darin dann über den `tk`-Namen ansprechen.

Semikolons benutzt man in Python nicht wirklich. Das ist nett für Code-Golf oder in einer interaktiven Python-Shell, aber in normalen Programmen stopft man nicht mehrere Anweisungen in eine Zeile.

`laenge` wird definiert, aber nicht verwendet. Wüsste auch nicht wofür die Länge des Dateinamens gut sein sollte. `len()` liefert auch bereits eine ganze Zahl. Es macht keinen Sinn `int()` mit dier Länge aufzurufen.

`driver` sollte in der Mehrzahl benannt werden, denn hinter dem Namen verbirgt sich nicht *ein* Fahrer, sondern Mehrere.

Wenn man Textdateien öffnet, sollte man immer explizit die Kodierung angeben.

``+=`` um *ein* Element zu einer Liste hinzuzufügen ist unsinnig. Da wird eine Liste mit immer genau einem Element erstellt um dann alle Elemente dieser Liste, also immer bloss eines, zu der Fahrerliste hinzuzufügen. Danach wird die Liste in dem das eine Element steht, wieder verworfen. Um *ein* Element zu einer Liste hinzuzufügen gibt es die `append()`-Methode – dabei wird dann nicht eine unnötige, einelementige Liste erstellt und verworfen.

`line_splitted` kann man sich sparen wenn man die Werte gleich bei der Zuweisung auf die Einzelnamen aufteilt.

Man sollte beim einlesen auch gleich dafür sorgen, dass die Werte in den passenden Datentyp umgewandelt werden.

Die Titelzeile muss *vor* der Schleife für die Tabellenzeilen erzeugt werden und nicht immer und immer wieder für jeden Eintrag in der Tabelle. Du erzeugst da immer neue `Label` die alle übereinander gezeichnet werden.

`r` und `c` werden viel zu weit vor der Stelle definiert wo sie tatsächlich benötigt werden. Zudem sollte man die `row` und `column` nennen. Einbuchstabige Namen sind selten eine gute Idee. `c` wird auch nicht sinnvoll verwendet. Das sollte auch eine Laufvariable sein um Wiederholungen im Quelltext zu vermeiden: die Breiten der Spalten stehen zwei mal im Code.

Der Code geht fest davon aus, das es mindestens 20 Fahrer gibt, statt sich an den Elementen in der Liste zu orientieren. Zugriff per Index ist in Python auch ein „anti pattern“, weil man direkt über die Elemente von Sequenzen iterieren kann, ohne den Umweg über einen Laufindex. Falls man *zusätzlich* eine fortlaufende Nummer benötigt, gibt es die `enumerate()`-Funktion.

Die Funktion sähe dann so aus (ungetestet):

Code: Alles auswählen

def open_result_window():
    window = tk.Toplevel()
    window.geometry('800x600')
    window.title('Renn-Ergebnisse 2018')
    
    drivers = []
    with open('Fahrer 2018.txt', 'r', encoding='utf-8') as file:
        for line in file:
            platz, name, punkte = line.strip().split(',')
            drivers.append(Fahrer(int(platz), name, float(punkte)))

    headers = ['Platzierung', 'Fahrername', 'Punkte']
    column_widths = [10, 25, 10]
    for column, (header, column_width) in enumerate(
        zip(headers, column_widths)
    ):
        tk.Label(
            window, text=header, relief=tk.RIDGE, width=column_width
        ).grid(row=0, column=column)
    
    for row, driver in enumerate(drivers, 1):
        for column, (value, column_width) in enumerate(
            zip(driver, column_widths)
        ):
            tk.Label(
                window, text=value, relief=tk.RIDGE, width=column_width
            ).grid(row=row, column=column)
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Trinity.95
User
Beiträge: 4
Registriert: Freitag 24. Mai 2019, 17:29

Vielen Dank für deine ausführliche Erklärung!
Ich versuche alles was du geschrieben hast umzusetzen und setze mich heute gleich dran.

Vielen Dank nochmal :)
Trinity.95
User
Beiträge: 4
Registriert: Freitag 24. Mai 2019, 17:29

__blackjack__ hat geschrieben: Freitag 24. Mai 2019, 18:36 @Trinity.95: Fangen wir doch gleich mal damit an das Du den Quelltext hier so formatieren solltest, das man ihn auch sinnvoll lesen kann. :-D Dafür gibt es Code-Tags, im vollständigen Beitragseditor auch über die Schaltfläche die mit </> beschriftet ist, einfügbar.

Im Traceback sieht man, dass Du ein `Label` erstellst. Wo denkst Du denn wo das dargestellt wird?
Ich hab mich auch schon gefragt ob ich den Code übersichtlicher anzeigen lassen kann. Danke dass du mich darauf aufmerksam gemacht hast!
Trinity.95
User
Beiträge: 4
Registriert: Freitag 24. Mai 2019, 17:29

@__blackjack__
Ich habe mir den Code jetzt mal so eingefügt wie du ihn mir geschickt hattest und auch deine anderen Tipps bearbeitet.
Ich habe bis dato noch nie mit zip gearbeitet und mir das ganze gerade durchgelesen. Gute Idee! :)

Ich bekomme jetzt nur folgenden Fehler mit dem Ich nichts anzufangen weiß. Das wird auch erstmal meine letzte Frage sein um euch nicht zu nerven!

Ich habe in dieser Schleife:

Code: Alles auswählen

    for row, driver in enumerate(drivers, 1):
        for column, (value, column_width) in enumerate(
            zip(driver, column_widths)
        ):
            tk.Label(
                window, text=value, relief=tk.RIDGE, width=column_width
            ).grid(row=row, column=column)
Bei zip(driver, column_widths), driver in drivers umbenannt. Und bekomme dann aber in der ersten Zeile folgendes raus:

[<__main__.Fahrer object at 0x000001AF4139FC88>

Folgende Stelle dann für die weiteren Felder ähnlich.

Wenn Ich mich irre mit driver/drivers (wovon Ich ausgehe) und driver bei driver belasse bekomme ich folgende Fehlermeldung:

File "<ipython-input-40-5282f5e02ac3>", line 41, in create_result_window
for column, (value, column_width) in enumerate(zip(driver, column_widths)):
TypeError: zip argument #1 must support iteration

Schon mal im voraus vielen Dank für deine Hilfe. Es hilft mir sehr
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Trinity.95: Die Schleife funktioniert nur wenn ein einzelner `driver` iterierbar ist, also wenn das zum Beispiel ein `collections.namedtuple()`-Datentyp ist, denn das ist ja auch weiterhin ein Tupel.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
bb1898
User
Beiträge: 200
Registriert: Mittwoch 12. Juli 2006, 14:28

Trinity.95 hat geschrieben: Samstag 25. Mai 2019, 14:46 Bei zip(driver, column_widths), driver in drivers umbenannt. Und bekomme dann aber in der ersten Zeile folgendes raus:

[<__main__.Fahrer object at 0x000001AF4139FC88>

Folgende Stelle dann für die weiteren Felder ähnlich.
Nur noch eine kleine Ergänzung: Dies ist keine Fehlermeldung. Es ist die normale Darstellung eines Objekts (hier einer Instanz der Klasse Fahrer) als String, wenn man dafür nichts Eigenes definiert hat. Es zeigt aber, was hier in den Label-Text geschrieben wird: eben ein Fahrer-Objekt als Ganzes, dargestellt als String. Und nicht, wie Du wolltest, eine einzelne Eigenschaft dieses Objekts.
Antworten