Python & Excel

Fragen zu Tkinter.
Antworten
Gerryman
User
Beiträge: 5
Registriert: Sonntag 19. März 2023, 18:43

Hallo Zusammen,

ich bin neu hier im Forum und gerade dabei Python in/für die Arbeit (aber auch für mich selbst, weil ich Spaß daran habe) zu lernen.
Bisher habe ich nur in der Techikerzeit Grundkenntnisse zu 'C' und die letzten Jahre umfangreiche Kenntnisse in 'VBA' gesammelt und mal kurz in Java reingelurt.

Die letzten Wochen habe ich über Umwege alles in der Arbeit installiert bekommen (IT Sicherheit ist ein großes Thema bei uns) und kann auch nötige Pakete installieren, hab schon einige Tuorials gemacht, YT Videos angesehen, gefühlt 100 Internetseiten durchforstet und einiges gelernt.

Bei uns, in einer sehr großen Firma, wird natürlich viel mit Excel gearbeitet und das ist auch meine Ausgangssituation.

Ich möchte aus Monatlichen Excel-Dateien Daten auslesen, erstmal in eine Access DB schreiben und später mit den Werten weiter arbeiten und rechnen können.
Mein Projekt ist noch ziemlich am Anfang und ausser einer einfachen Gui (mit Tkinter) hab ich noch nicht viel hinbekommen.
Excel Daten einlesen mit Pandas funktioniert auch irgendwie.
Allerdings bekomm ich eine akzeptablen Visualiserung dieser Daten nicht wirklich hin...

Wie gesagt ich habe schon eine kleine Beutzeroberfläche mit ein paar Buttons, die mich zum wählen einer Datei auffordern, die dann auch geöffnet und gelesen wird. Ich kann sogar daten gezielt in Excel wieder speichern.
Diese Daten habe ich dann schon im Terminal anzeigen lassen, in einem Canvas und in einem Pandas DataFrame hatte ich sie auch schon. Mein Wunsch wäre es, dass ich eine annähernd gleiche Ansicht wie eine Excel Tabelle angezeigt bekomme. (Mit einzellenen Zellen, oder zumindest Reihen und Spalten! Über eine sehr rudimentäre Tabelle, in der auch noch Daten stehen, die ich nicht benötige und die mir nicht wirklich gefällt, komme ich aber nicht hinaus.
Darin könnte ich dann kurz überprüfen ob alles richtig eingelesen und plausibel ist...
Danach möchte ich die Daten eben in Access speichern und weiter damit arbeiten, aber das kommt später.

Ich lande immer wieder bei Jupiter, was mich für meine Zwecke nicht weiter bringt oder bei Pandas, wobei ich nicht weiß, ob das für meine überhaupt Zwecke gedacht ist.

Das ganze Programm soll natürlich lokal/oder im Netzwerk auf einem Windows PC laufen.

Für ein paar Tipps (Anleitungen, Internetseite, YT, oder ob ich völlig auf dem Holzweg bin, wäre ich sehr dankbar.

Vielen Dank schonmal
Gerry
Benutzeravatar
Dennis89
User
Beiträge: 1123
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

ich muss kurz nachfragen, weil ich mir nicht sicher bin ob ich dich richtig verstehe.
Du bekommst das mit dem anzeigen der ausgelesenen Werte hin, aber das Problem ist, das Werte dabei sind, die dich nicht interessieren?
Heißt das, dass du Werte ausliest die dich nicht interessieren oder wie kann man sich das vorstellen?

Wenn du schon Code hast, dann darfst du den hier gerne zeigen und man kann gemeinsam den Fehler finden.

Den Code bitte in Code-Tags posten, im vollständigen Editor ist dass der </> - Button.
Oder mit [ code]HIER CODE EINFÜGEN[/code] und das Leerzeichen in der ersten Klammer entfernen.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
sparrow
User
Beiträge: 4164
Registriert: Freitag 17. April 2009, 10:28

Ich finde es sehr schrecklich, dass Firmen Datenhaltung in Excel machen.
Aber ich werfe mal etwas ganz anderes in den Raum: Wenn du eh nur Daten in der Microsoft-Welt von Excel nach Access schmeißen willst. Warum genau brauchst du dann Python?
Gerryman
User
Beiträge: 5
Registriert: Sonntag 19. März 2023, 18:43

Guten Morgen,

danke schon mal für die Rückmeldung.

Die Zeit die angezeigt wird, hat keine Funktion, war nur ein Test für mich.
Genauso wie die Buttons, die aktuell noch nicht viel machen, oder die selbe Funktion haben....

Es wird das ganze Tabellenblatt eingelesen, worüber ich schon mal froh bin. Ich würde natürlich gern etwas selektiver vorgehen wollen.
Inzwischen hab ich zumindest herausgefunden, wie ich die Anzahl der angezeigten Zeilen einstelle, aber wenn es mehr sind, gibts noch kein Scrollbalken!
Wenn ich jetzt viele leere Zellen habe, steht überall "None" drin.

Hier meine Code.

Code: Alles auswählen

# globale Variable
tree = None

# import Pakete
from tkinter import PhotoImage
from tkinter import filedialog
from openpyxl import load_workbook
from datetime import datetime
import tkinter as tk
import tkinter.ttk as ttk
import pandas as pd

# importieren von Klassen

############## Hauptfenster erstellen ##############
root = tk.Tk()                                  # Hauptfenster aufrufen
root.title("BestMonkeyMoments")                 # Programmnamen definieren
root.geometry('1100x700')                        # größe des Hauptfensters definieren (breite x höhe)
icon = PhotoImage(file='images/monkey.png')     # der Pfad zum Icon/Bild
root.iconphoto(True, icon)                      # Set the icon for the window

Canvas = tk.Canvas
Button = tk.Button
LabelFrame = tk.LabelFrame

############## Button Größe ####################

ButW = 20
ButH = 1

############## Funktion erstellen ##############
################################################

# Funktion zum Öffnen des Excel-Files und Auslesen des Inhalts
def aktionSF1():
    # Öffne den Dateiauswahl-Dialog
    file_path = filedialog.askopenfilename(title= "Bitte Kammlinien-Datei wählen: ", initialdir="/Kammlinie", filetypes=[("Excel Files", "*.xlsx")])
    if not file_path:
        return
    Data = pd.read_excel(file_path)

    # Erstellen der Tabelle mit den gewünschten Abmessungen
    tree = ttk.Treeview(table_frame, height=50) # height = anzahl Zeilen die angezeigt werden
    tree["columns"] = list(Data.columns)
    tree["show"] = "headings"
    for column in tree["columns"]:
        tree.heading(column, text=column)
        tree.column(column, width=25)
    # Hinzufügen der Daten zur Tabelle
    for index, row in Data.iterrows():
        tree.insert("", "end", values=list(row))
    # Anzeigen der Tabelle im Frame
    tree.pack(side="top", fill="both", expand=True)

def aktionSF2():
    print(1)

def aktionSF3():
    print(2)

def aktionSF4():
    print(3)

def aktionSF5():
    print(4)

############## Frame generieren ##############

Frame1 = LabelFrame(root, text=" Input Programm ", font =('Arial', 14), width=290, height=200, padx=5, pady=5)
Frame1.place(x=10,y=10)

Frame2 = LabelFrame(root, text=" Stammdaten-Pflege ", font =('Arial', 14), width=290, height=200, padx=5, pady=5)
Frame2.place(x=10,y=210)

Frame3 = LabelFrame(root, text=" Simulation ", font =('Arial', 14), width=290, height=200, padx=5, pady=5)
Frame3.place(x=10,y=410)

Frame4 = LabelFrame(root, text=" Anzeige ", font =('Arial', 14), width=300, height=300, padx=5, pady=5)
Frame4.place(x=250,y=10)


############ Inhalt Frame 4 ####################
dataframe_widget = tk.Frame(Frame4, width=500, height=600)

############ Erstellen des Frames für die Tabelle mit den gewünschten Abmessungen
table_frame = ttk.Frame(Frame4, width=400, height=400)
table_frame.pack(side="top", fill="both", expand=True, padx=10, pady=10)



############## Buttons Frame 1 ##############

fr1But1 = Button(Frame1, text="Kammlinie", anchor="w", command=aktionSF1, width=ButW, height=ButH)
fr1But1.grid(row=0, column=0)

fr1But2 = Button(Frame1, text="Werkskalender", anchor="w", command=aktionSF2, width=ButW, height=ButH)
fr1But2.grid(row=1, column=0)

fr1But3 = Button(Frame1, text="Produktionsvorschau", anchor="w", command=aktionSF3, width=ButW, height=ButH)
fr1But3.grid(row=2, column=0)

############## Buttons Frame 2 ##############

fr2But1 = Button(Frame2, text="Derivate", anchor="w", command=aktionSF1, width=ButW, height=ButH)
fr2But1.grid(row=0, column=0)

fr2But2 = Button(Frame2, text="Zus. Prod. Sys", anchor="w", command=aktionSF2, width=ButW, height=ButH)
fr2But2.grid(row=1, column=0)

fr2But3 = Button(Frame2, text="Speicherverwaltung", anchor="w", command=aktionSF3, width=ButW, height=ButH)
fr2But3.grid(row=2, column=0)

fr2But4 = Button(Frame2, text="Restriktionen", command=aktionSF4, anchor="w",width=ButW, height=ButH)
fr2But4.grid(row=3, column=0)

############## Buttons Frame 3 ##############

fr3But1 = Button(Frame3, text="Play", anchor="w", command=aktionSF1, width=ButW, height=ButH)
fr3But1.grid(row=0, column=0)

fr3But2 = Button(Frame3, text="Typauswahl", anchor="w", command=aktionSF2, width=ButW, height=ButH)
fr3But2.grid(row=1, column=0)

fr3But3 = Button(Frame3, text="Zeitraum", anchor="w", command=aktionSF3, width=ButW, height=ButH)
fr3But3.grid(row=2, column=0)

fr3But4 = Button(Frame3, text="Restriktionen", command=aktionSF4, anchor="w",width=ButW, height=ButH)
fr3But4.grid(row=3, column=0)

fr3But5 = Button(Frame3, text="Historie", anchor="w", command=aktionSF5, width=ButW, height=ButH)
fr3But5.grid(row=4, column=0)

fr3But6 = Button(Frame3, text="Beenden", anchor="w", command=root.destroy, width=ButW, height=ButH)
fr3But6.grid(row=4, column=0)

############## Aktuelle Uhrzeit beim Programmstart ##############
now = datetime.now()
dt_string = now.strftime("%H:%M:%S")
print(dt_string)

############## Endlosschleife bis Quit ##############
root.mainloop()


sparrow hat geschrieben: Sonntag 19. März 2023, 21:05 Ich finde es sehr schrecklich, dass Firmen Datenhaltung in Excel machen.
Aber ich werfe mal etwas ganz anderes in den Raum: Wenn du eh nur Daten in der Microsoft-Welt von Excel nach Access schmeißen willst. Warum genau brauchst du dann Python?
Genau kann ich es dir nicht erklären, aber es liegt wohl an der Größe der Firma, den vielen unterschiedlichen Abteilungen in denen jeder eigene Programme und zutiefst unterschiedliche Daten hat.
Excel ist einfach, bieten die nötigen alltäglichen Funktionen und fast jeder kann damit umgehen...

Das Programm soll langfristig diverse Berechnungen anstellen und mathematischen Optimierung durchführen. (Spart Zeit und Geld)
Hatten kürzlich einen Bacheloranden, der seine Arbeit darüber geschrieben hat und im Vergleich der Programmiersprachen Python, aufgrund der Möglichkeiten und vorhandenen Pakete für die beste Lösung vorgeschlagen hat. Ob das so ist und wird, wird sich zeigen.

Chef will es so und ich freu mich was dazu zu lernen 8)
Wie gesagt, alles noch am Anfang und mein Optimismus die gewünschten Ziele zu erreichen hält sich noch in Grenzen!

Deshalb bin ich über jede Hilfe dankbar.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Gerryman: Der Quelltext fängt gleich mit einem „no go“ an: Globale Variablen verwendet man nicht. Bei GUI-Programmierung kommt man deshalb bei nicht-trivialen GUI-Programmen deshalb nicht um objektorientierte Programmierung (OOP) herum. Also eigene Klassen schreiben zu können.

Wobei das globale `tree` bisher nur innerhalb einer Funktion benutzt wird, also problemlos ein lokaler Namen hätte sein können in diesem Beispiel.

Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht.

``as`` beim importieren ist zum umbennenen, `ttk` wird aber gar nicht umbenannt.

Die Zuweisungen an `Canvas`, `Button`, und `LabelFrame` sind ungewöhnlich und sollten so nicht gemacht werden. Bei `PhotoImage` und `filedialog` wird der normale Weg genommen — das sollte auch bei `Canvas` & Co so gemacht werden. Und `Frame` wird dann auf einem dritten Weg angesprochen — als einziges über das Modul. Das kann man auch machen, dann sollte man das mit den anderen Namen aber auch machen. Beides ist okay, aber nicht beide Wege im gleichen Programm. (`Canvas` wird nirgends verwendet.)

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

Funktionen und Methoden bekommen alles was sie ausser Konstanten benötigen, als Argument(e) übergeben. `aktionSF1()` braucht `table_frame` als Argument, da das nicht als globale Variable existieren darf.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

Namen sollten keine kryptischen Abkürzungen enthalten oder gar nur daraus bestehen. Der Name soll dem Leser vermitteln was der Wert dahinter im Programm bedeutet, nicht zum rätseln zwingen.

Man nummeriert keine Namen. Dann will man sich entweder bessere Namen überlegen, oder gar keine Einzelnamen/-werte verwenden, sondern eine Datenstruktur. Oft eine Liste.

Man gibt in aller Regel keine Fenstergrösse vor, denn dir ergibt sich automatisch aus dem Fensterinhalt. Nicht wenn man `place()` verwendet, aber das verwendet man auch nicht. `Frame4` ist da auch komisch platziert — ”rechts” von den ganzen Frames die 290 Pixel breit sind an X-Position 250 — was dann ja von den Frames auf der linken Seite teilweise überdeckt wird. Das funktioniert nur weil die 290 letztlich ignoriert werden, weil in den Frames auf der linken Seite die Elemente nicht mit `place()` platziert werden und damit die Frames nicht 290 Pixel breit sind. Also falls das der Fall ist. Könnte natürlich auch sein, dass die durch den Inhalt deutlich breiter als 290 Pixel werden. Man weiss das halt nicht. Deshalb ist `place()` für so etwas unbrauchbar.

Der Code zum aufbau der GUI sollte besser in der Reihenfolge sein wie die GUI aufgebaut ist. Das ist selbst dann manchmal nicht so leicht nachzuvollziehen, aber noch schlechter wenn beides so gar nicht korrespondiert. Einige der Frames brauchen dann auch keinen speziellen Namen, sondern man kann einfach etwas generisches wie `frame` immer wieder verwenden, und wird auf diese Weise die Nummerierung dieser Namen los. Bei den Buttons muss man auch gar keine Namen verwenden, wenn man auf das `Button`-Objekt nie wieder zugreifen will.

Für Zeichenketten mit spezieller Bedeutung für die es Konstanten im `tkinter`-Modul gibt, sollte man diese Konstanten verwenden.

`dataframe_widget` wird definiert, aber nirgends verwendet.

Die Buttons "Historie" und "Beenden" sind in der gleichen Grid-Zelle platziert!

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from datetime import datetime
from functools import partial
from tkinter import filedialog, ttk

import pandas as pd


def open_kammlinien_file(table_frame):
    file_path = filedialog.askopenfilename(
        title="Bitte Kammlinien-Datei wählen",
        initialdir="/Kammlinie",
        filetypes=[("Excel Files", "*.xlsx")],
    )
    if file_path:
        data = pd.read_excel(file_path)
        tree = ttk.Treeview(table_frame, height=50)
        tree["columns"] = list(data.columns)
        tree["show"] = "headings"
        for column in tree["columns"]:
            tree.heading(column, text=column)
            tree.column(column, width=25)

        for _, row in data.iterrows():
            tree.insert("", tk.END, values=list(row))

        tree.pack(side=tk.TOP, fill=tk.BOTH, expand=True)


def main():
    root = tk.Tk()
    root.title("BestMonkeyMoments")
    icon = PhotoImage(file="images/monkey.png")
    root.iconphoto(True, icon)

    button_frames_frame = tk.Frame(root)
    button_frames_frame.pack(side=tk.LEFT)

    frame = tk.LabelFrame(
        button_frames_frame, text="Input Programm", padx=5, pady=5
    )
    frame.pack(side=tk.TOP)
    open_kammlinie_button = tk.Button(frame, text="Kammlinie")
    open_kammlinie_button.grid(row=0, column=0)
    tk.Button(frame, text="Werkskalender", command=partial(print, 1)).grid(
        row=1, column=0
    )
    tk.Button(
        frame, text="Produktionsvorschau", command=partial(print, 2)
    ).grid(row=2, column=0)

    frame = tk.LabelFrame(
        button_frames_frame, text="Stammdaten-Pflege", padx=5, pady=5
    )
    frame.pack(side=tk.TOP)
    tk.Button(frame, text="Derivate", command=partial(print, 0)).grid(
        row=0, column=0
    )
    tk.Button(frame, text="Zus. Prod. Sys", command=partial(print, 1)).grid(
        row=1, column=0
    )
    tk.Button(
        frame, text="Speicherverwaltung", command=partial(print, 2)
    ).grid(row=2, column=0)
    tk.Button(frame, text="Restriktionen", command=partial(print, 3)).grid(
        row=3, column=0
    )

    frame = tk.LabelFrame(
        button_frames_frame, text="Simulation", padx=5, pady=5
    )
    frame.pack(side=tk.TOP)
    tk.Button(frame, text="Play", command=partial(print, 0)).grid(
        row=0, column=0
    )
    tk.Button(frame, text="Typauswahl", command=partial(print, 1)).grid(
        row=1, column=0
    )
    tk.Button(frame, text="Zeitraum", command=partial(print, 2)).grid(
        row=2, column=0
    )
    tk.Button(frame, text="Restriktionen", command=partial(print, 3)).grid(
        row=3, column=0
    )
    tk.Button(frame, text="Historie", command=partial(print, 4)).grid(
        row=4, column=0
    )
    tk.Button(frame, text="Beenden", command=root.destroy).grid(
        row=5, column=0
    )

    frame = tk.LabelFrame(root, text="Anzeige", padx=5, pady=5)
    frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
    table_frame = ttk.Frame(frame, width=400, height=400)
    table_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=10, pady=10)

    open_kammlinie_button["command"] = partial(
        open_kammlinien_file, table_frame
    )

    print(f"{datetime.now():%H:%M:%S}")
    root.mainloop()


if __name__ == "__main__":
    main()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Gerryman
User
Beiträge: 5
Registriert: Sonntag 19. März 2023, 18:43

Ok, vielen Dank erst einmal.
Es ist viel Input, auch wenn mir nicht alles sofort klar ist, leuchtet mir das meiste schon ein und der Code sieht auf jedenfalls schon mal eleganter aus! (und funktioniert)
Werd mir alles auf jeden fall zu herzen nehmen und versuchen es besser zu machen.

Das mit der Fenstergröße ist auch logisch. Was mir noch nicht so klar ist, die Positionierung der Frames und Buttons.
Der Unterschied place(), pack() & grid() ist mir glaub ich klar.
Ich hab mich nach langem versuchen eben dafür entschieden, weil ich die Buttons usw. immer an der selben stelle haben möchte und nicht Anfangs oben links, dann auf einmal in der Mitte links weil das Dataframe größer wird... Die Koordinaten sind vermutlich durch viel Spielerei durcheinander gekommen.

Ich nehme gern noch Tipps zum fixieren und positionieren an
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Gerryman: Wenn der Frame links immer oben bleiben soll, könnte man den beim `pack()` mit der `anchor`-Option entweder mit `N` oder `NW` oben, beziehungsweise oben links verankern.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich wuerde auch erstmal die (oder gar in...) Frage stellen, warum es eine GUI sein muss. Damit machst du gleich zwei grosse Faesser auf, die nicht-trivial sind: deine eigentlich gewuenschte Datentransformation, und das erstellen einer GUI, mit der man das steuert. Ein Kommandozeilentool (oder Jupyter Notebook) ist diesbezueglich viel einfacher geschrieben, und der Fokus kann auf der Transformationslogik liegen. Erst wenn die wirklich steht, und es dann immer noch wirklich gewuenscht oder besser ist, da eine GUI fuer zu schreiben, wuerde ich damit anfangen. Ansonsten geht da viel Zeit fuer etwas drauf, dass schwer und von vielen Unwaegbarkeiten gepraegt ist.
Antworten