Document anlegen nach Eingabe mit tkinter

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
TheBetze
User
Beiträge: 5
Registriert: Freitag 18. Dezember 2020, 14:36

Guten Tag,

ich bin neu hier und habe eine Frage, weil ich grade nicht weiter kommen in meiner Programmierung:

Nach dem Eingabefeld und klicken auf den Button, sollen die Eingabe in ein Dokument gespeichert werden.
Ordner für die Datei wird auch angeleht, aber nicht die Word Datei. Als Stand-Alone Version ohne graphische Oberfläche funktioniert ist.

das erste "document" ist dunkel grau.

So sieht der Code aus

#Ablageort und Ordner Erstellung
import os
path = 'C:\\..\newFolder'
os.mkdir(path)

# Abfrage Grunddaten
from tkinter import *
fenster = Tk()
def show_entry_fields():
Name=e1.get()
Straße_Hausnummer = e2.get()
PLZ_ST=e3.get()
return Name, Straße_Hausnummer, PLZ_ST

# Schreiben & Speichern
from docx import Document
from mailmerge import MailMerge
document = Document()
template = ('Brief.docx')
document = MailMerge(template)

document.merge(
Auftraggeber=Name,
AG_Straße_HsNr=Straße_Hausnummer,
AG_PLZ_City=PLZ_ST,
)

document.write('C:\\Users\\newFolder\\Auftrag.docx')

Woran liegt es? Ich muss vor dem Block von tkinter schreibe, was beim drücken des "Buttons" passieren soll. wie gesagt, ohne tkinter klappt es.

Wäre dankbar für eure Tipps.

Gruß aus Düsseldorf
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

der Code ist doch so gar nicht komplett. Es werden weder die Widgets in der GUI angelegt noch der Mainloop der GUI gestartet.

Und bitte den Code in einem Codeblock posten, damit das vernünftig lesbar ist. Den bekommst du, indem du den vollständigen Editor öffnest und dann auf die </> Schaltfläche klickst.

Gruß, noisefloor
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@TheBetze: Noch ein paar Anmerkungen zum Quelltext:

Importe gehören alle an den Anfang der Datei, damit man leicht sieht wovon das Modul abhängt.

Sternchen-Importe sind Böse™. Da holt man sich gerade bei `tkinter` fast 200 Namen ins Modul von denen nur ein kleiner Bruchteil verwendet wird. Auch Namen die gar nicht in `tkinter` definiert werden, sondern ihrerseits von woanders importiert werden. Das macht Programme unnötig unübersichtlicher und fehleranfälliger und es besteht die Gefahr von Namenskollisionen.

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. `show_entry_fields()` braucht also `e1`, `e2`, und `e3`. Wobei diese Namen besch…eiden sind. Keine kryptischen Abkürzungen und keine Nummerierung von Namen.

Für jede nicht-triviale GUI braucht man eine eigene Klasse.

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

`show_entry_fields()` ist als Name falsch weil da nichts gezeigt wird, sondern die Eingabefelder werden ausgelesen und eine Datei wird erstellt, bzw. soll erstellt werden. Das passiert nicht, weil da mitten drin ein ``return`` steht bevor der, damit tote Code, folgt, der eine Datei schreiben würde.

Das erzeugte `Document`-Objekt wird nirgends verwendet, denn der Name an den das gebunden wird, wird überhaupt nicht verwendet bis zwei Zeilen später an völlig anderes Objekt an diesen Namen gebunden wird.

Pfade oder Pfadteile sollten nicht mehrfach im Code stehen, sondern am Anfang als Konstante(n) definiert werden.

Statt `os`/`os.path` sollte man das `pathlib`-Modul verwenden.

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from pathlib import Path

from mailmerge import MailMerge

PATH = Path("C:\\Users\\newFolder")


def create_document(name_entry, street_and_number_entry, postcode_entry):
    #
    # TODO Ist der Dateiname ohne Pfad hier robust genug?
    #
    document = MailMerge("Brief.docx")
    document.merge(
        Auftraggeber=name_entry.get(),
        AG_Straße_HsNr=street_and_number_entry.get(),
        AG_PLZ_City=postcode_entry.get(),
    )
    document.write(PATH / "Auftrag.docx")  # TODO Benötigt eventuell `str()`.


def main():
    PATH.mkdir()  # TODO Eventuell ``exist_ok=True`` hinzufügen.

    fenster = tk.Tk()

    ...


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
TheBetze
User
Beiträge: 5
Registriert: Freitag 18. Dezember 2020, 14:36

noisefloor hat geschrieben: Samstag 19. Dezember 2020, 12:29 Hallo,

der Code ist doch so gar nicht komplett. Es werden weder die Widgets in der GUI angelegt noch der Mainloop der GUI gestartet.

Und bitte den Code in einem Codeblock posten, damit das vernünftig lesbar ist. Den bekommst du, indem du den vollständigen Editor öffnest und dann auf die </> Schaltfläche klickst.

Gruß, noisefloor
Hallo, ich habe nicht alles geposter, da es grundsätzlich funktioniert.

Daher habe ich nicht das ende mit mainloop gepostet.
TheBetze
User
Beiträge: 5
Registriert: Freitag 18. Dezember 2020, 14:36

Vielen Dank, versuche ich mal.

Das hofft mir hoffentlich weiter.

Danke
__blackjack__ hat geschrieben: Samstag 19. Dezember 2020, 13:13 @TheBetze: Noch ein paar Anmerkungen zum Quelltext:

Importe gehören alle an den Anfang der Datei, damit man leicht sieht wovon das Modul abhängt.

Sternchen-Importe sind Böse™. Da holt man sich gerade bei `tkinter` fast 200 Namen ins Modul von denen nur ein kleiner Bruchteil verwendet wird. Auch Namen die gar nicht in `tkinter` definiert werden, sondern ihrerseits von woanders importiert werden. Das macht Programme unnötig unübersichtlicher und fehleranfälliger und es besteht die Gefahr von Namenskollisionen.

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. `show_entry_fields()` braucht also `e1`, `e2`, und `e3`. Wobei diese Namen besch…eiden sind. Keine kryptischen Abkürzungen und keine Nummerierung von Namen.

Für jede nicht-triviale GUI braucht man eine eigene Klasse.

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

`show_entry_fields()` ist als Name falsch weil da nichts gezeigt wird, sondern die Eingabefelder werden ausgelesen und eine Datei wird erstellt, bzw. soll erstellt werden. Das passiert nicht, weil da mitten drin ein ``return`` steht bevor der, damit tote Code, folgt, der eine Datei schreiben würde.

Das erzeugte `Document`-Objekt wird nirgends verwendet, denn der Name an den das gebunden wird, wird überhaupt nicht verwendet bis zwei Zeilen später an völlig anderes Objekt an diesen Namen gebunden wird.

Pfade oder Pfadteile sollten nicht mehrfach im Code stehen, sondern am Anfang als Konstante(n) definiert werden.

Statt `os`/`os.path` sollte man das `pathlib`-Modul verwenden.

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from pathlib import Path

from mailmerge import MailMerge

PATH = Path("C:\\Users\\newFolder")


def create_document(name_entry, street_and_number_entry, postcode_entry):
    #
    # TODO Ist der Dateiname ohne Pfad hier robust genug?
    #
    document = MailMerge("Brief.docx")
    document.merge(
        Auftraggeber=name_entry.get(),
        AG_Straße_HsNr=street_and_number_entry.get(),
        AG_PLZ_City=postcode_entry.get(),
    )
    document.write(PATH / "Auftrag.docx")  # TODO Benötigt eventuell `str()`.


def main():
    PATH.mkdir()  # TODO Eventuell ``exist_ok=True`` hinzufügen.

    fenster = tk.Tk()

    ...


if __name__ == "__main__":
    main()
TheBetze
User
Beiträge: 5
Registriert: Freitag 18. Dezember 2020, 14:36

Hallo zusammen,

ich habe mir eure Sachen angeguckt und die Tips beherzigt.

Hierzu ist noch zu sagen, dass Rechnungxxx-20 eine Vorlage ist und diese mit dem Input in den Ordner newFolder gespeicherz werden soll.

Leider bekomme ich immer noch folgende Fehlermeldung:

TypeError: create_document() missing 3 required positional arguments: 'name_entry', 'street_and_number_entry', and 'postcode_entry'

Vielleicht sieht ja jemand auf Anhieb, warum die 3 argumente gefehlen und daher nicht geschrieben werden können.

Vielen Dank und einen schönen 4. Advent.

Code: Alles auswählen

from mailmerge import MailMerge
from docx import Document

PATH = Path('C:\\Users\\newFolder')

def create_document(name_entry, street_and_number_entry, postcode_entry):
    document = MailMerge('Rechnung xxx-20.docx')
    document.merge(
        Auftraggeber=name_entry.get(),
        AG_Straße_HsNr=street_and_number_entry.get(),
        AG_PLZ_City=postcode_entry.get(),
    )
    document.write('C:\\User\\newFolder\\Rechnung.docx')

#-----------------------------------------------------------------------------------------------------------------------
def main():
    PATH.mkdir()
    fenster = tk.Tk()
    
    tk.Label(fenster,text='Auftraggeber').grid(row=0)
    tk.Label(fenster,text='Straße und Hausnummer').grid(row=1)
    tk.Label(fenster,text='PLZ & Stadt').grid(row=2)

    name_entry = tk.Entry(fenster)
    street_and_number_entry = tk.Entry(fenster)
    postcode_entry = tk.Entry(fenster)

    name_entry.grid(row=0,column=1)
    street_and_number_entry.grid(row=1,column=1)
    postcode_entry.grid(row=2,column=1)

    tk.Button(fenster,text='Schließen', command=fenster.quit).grid(row=11,column=0)
    tk.Button(fenster,text='Eingabe', command=create_document).grid(row=11,column=1)

    fenster.mainloop()

if __name__ == '__main__':
    main()
    
    
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

das liegt daran, dass deine Funktion 'create_document' diese drei Argumente bei ihrem Aufruf erwartet. Das heißt, wenn du die die Funktion aufrufst, dann musst du dem Aufruf die Argumente mit übergeben. Du willst, dass die Funktion aufgerufen wird, wenn der "Eingabe"-Button gedrückt wird. Das sieht bei dir so aus:

Code: Alles auswählen

tk.Button(fenster,text='Eingabe', command=create_document().grid(row=11,column=1)
Es wird also nur die Funktion ohne Argumente aufgerufen.
Da du in der 'main'-Funktion die drei Argumente definierst, könnte dein Aufruf so aussehen:

Code: Alles auswählen

tk.Button(fenster,text='Eingabe', command=create_document(name_entry, street_and_number_entry, postcode_entry)).grid(row=11,column=1)
Schau mal ob sich dein Code dann so verhält wie du es dir vorstellst. Wenn ich mich nicht täusche, dann funktioniert das noch nicht wie gewünscht, aber eins nach dem anderen.

Ach und dir fehlt noch:

Code: Alles auswählen

import tkinter as tk
from pathlib import Path
Grüße und dir auch einen schönen 4. Advent
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Dennis89: Das wird nicht funktionieren weil das `command`-Argument etwas aufrufbares erwartet was *keine* Argument übergeben bekommt. Du übergibst da `None`, denn Du rufst `create_document()` auf und übergibst dessen Rückgabewert als `command`.

Das ist die Stelle in der GUI-Programmierung wo man entweder Closures oder eine Klasse braucht. Hier würde ein Closure mit `functools.partial()` noch funktionieren/ausreichen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
TheBetze
User
Beiträge: 5
Registriert: Freitag 18. Dezember 2020, 14:36

Wie soll das dann aussehen?
__blackjack__ hat geschrieben: Sonntag 20. Dezember 2020, 13:51 @Dennis89: Das wird nicht funktionieren weil das `command`-Argument etwas aufrufba res erwartet was *keine* Argument übergeben bekommt. Du übergibst da `None`, denn Du rufst `create_document()` auf und übergibst dessen Rückgabewert als `command`.

Das ist die Stelle in der GUI-Programmierung wo man entweder Closures oder eine Klasse braucht. Hier würde ein Closure mit `functools.partial()` noch funktionieren/ausreichen.
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

@__blackjack__ ich habe es vor dem posten in einer vereinfachten Form getestet:

Code: Alles auswählen

import tkinter as tk


def create_document(name, street, postcode):
    print(name, street, postcode)    

def main():
    fenster = tk.Tk()
    
    tk.Label(fenster,text='Auftraggeber').grid(row=0)
    tk.Label(fenster,text='Straße und Hausnummer').grid(row=1)
    tk.Label(fenster,text='PLZ & Stadt').grid(row=2)

    name = 'Mustername'
    street = 'Straße'
    postcode = '12345'

    tk.Button(fenster,text='Schließen', command=fenster.quit).grid(row=11,column=0)
    tk.Button(fenster,text='Eingabe', command=create_document(name, street, postcode)).grid(row=11,column=1)

if __name__ == '__main__':
    main()
Dadurch war die Fehlermeldung weg, allerdings funktionieren die Buttons nicht. Das habe ich damit gemeint, dass der Code bestimmt nicht funktioniert wie es sich der TE vorstellt.

Ist diese Richtung die richtige? :

Code: Alles auswählen

#!/usr/bin/env python3

import tkinter



class App(tkinter.Frame):

    def __init__(self, master):
        super().__init__(master)
        self.pack()
        self.create_widgets()

    def create_widgets(self):
        """Diese Funktion erstellt die Bedienelemente"""

        self.window = tkinter.Tk()
        tkinter.Label(self.window,text='Auftraggeber').grid(row=0)
        tkinter.Label(self.window,text='Straße und Hausnummer').grid(row=1)
        tkinter.Label(self.window,text='PLZ & Stadt').grid(row=2)

        self.name_entry = tkinter.Entry(self.window)
        self.street_and_number_entry = tkinter.Entry(self.window)
        self.postcode_entry = tkinter.Entry(self.window)

        self.name_entry.grid(row=0,column=1)
        self.street_and_number_entry.grid(row=1,column=1)
        self.postcode_entry.grid(row=2,column=1)

        tkinter.Button(self.window,text='Schließen', command=self.quit_and_close).grid(row=11,column=0)
        tkinter.Button(self.window,text='Eingabe', command=self.create_document).grid(row=11,column=1)
    
    def create_document(self):
        name = self.name_entry.get()
        street = self.street_and_number_entry.get()
        postcode = self.postcode_entry.get()
        print(name, street, postcode)
        
    def quit_and_close(self):
        self.window.destroy()


def main():
    root = tkinter.Tk()
    app = App(root)
    app.mainloop()

if __name__ == "__main__":
    main()

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@Dennis89: Du benutzt in Deinem ersten Code auch `command` falsch, weil Du `create_document` aufrufst.

Bei Deiner Klasse erzeugst Du ein weiteres Tk-Exemplar, aber es darf davon nur eines geben, ist auch komisch, innerhalb eines Frames eine neues Fenster zu erzeugen.
Ein Frame sollte sich auch nicht selbst packen, das gehört zum Aufrufer, denn so kannst Du ja nur pack benutzen und nicht grid.
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo Sirius3
Danke für die Hinweise. Dann könnte ich diese Lösung noch anbieten:

Code: Alles auswählen

#!/usr/bin/env python3

import tkinter

class App(tkinter.Frame):

    def __init__(self, master):
        super().__init__(master)
        self.create_widgets()

    def create_widgets(self):
        
        tkinter.Label(text='Auftraggeber').grid(row=0)
        tkinter.Label(text='Straße und Hausnummer').grid(row=1)
        tkinter.Label(text='PLZ & Stadt').grid(row=2)

        self.name_entry = tkinter.Entry()
        self.street_and_number_entry = tkinter.Entry()
        self.postcode_entry = tkinter.Entry()

        self.name_entry.grid(row=0,column=1)
        self.street_and_number_entry.grid(row=1,column=1)
        self.postcode_entry.grid(row=2,column=1)

        tkinter.Button(text='Schließen', command=self.quit_and_close).grid(row=11,column=0)
        tkinter.Button(text='Eingabe', command=self.create_document).grid(row=11,column=1)
    
    def create_document(self):
        name = self.name_entry.get()
        street = self.street_and_number_entry.get()
        postcode = self.postcode_entry.get()
        print(name, street, postcode)
        
    def quit_and_close(self):
        quit()


def main():
    root = tkinter.Tk(className = "Dokumente erstellen")
    app = App(root)
    app.mainloop()

if __name__ == "__main__":
    main()
Hoffe jetzt passt es und es hilft dem TE. Anosnt bin ich gerne bereit es weiter anzupassen.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Dennis89: Der `className` ist komisch. Ich vermute mal Du wolltest da eigentlich mit der `title()`-Methode den Fenstertitel setzen. Denn das der Klassenname bei einigen Systemen als Fenstertitel auftaucht ist eher Zufall.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Das was in der Methode create_widgets kannst Du gleich in __init__ hineinschreiben.
Jetzt benutzt Du den Frame gar nicht mehr, warum erzeugst Du dann einen?
Und alle Buttons und Labels werden einem globalen Fenster zugeordnet, das macht man nicht, da muß mindestens master benutzt werden, wenn Du eh den Frame nicht verwendest, sonst gehört das alles in den Frame, das wäre das richtigste.
Woher kommt `quit` in quit_and_close? Die Methode ist sowieso überflüssig, da Du master.destroy gleicht als command an den Schließen-Button übergeben kannst.
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

@__blackjack__ Ja du hattest Recht, das war meine Absicht. Habe es jetzt mit 'title' geändert, dann gefällt ihm auch die Großbuchstaben.

@Sirius3 Ich habe es versucht so abzuändern, das ich den Frame wieder verwende. ich hoffe das ist so jetzt richtig. Ansonsten wäre etwas Erklärung hilfreich, sonst endet es bei mir mit planlosen Code-abändern.
Die Funktion zum Schliessen habe ich weggelassen und direkt dem Button übergeben. Woher das 'quit' kommt weis ich nicht, habe ich mal im Internet gefunden, als ich das erste Mal was mit tkinter versucht habe, so wie das 'destroy' auch.

Code: Alles auswählen

import tkinter

class App(tkinter.Frame):

    def __init__(self, master):

        tkinter.Frame.__init__(self, master)
        
        tkinter.Label(text='Auftraggeber').grid(row=0)
        tkinter.Label(text='Straße und Hausnummer').grid(row=1)
        tkinter.Label(text='PLZ & Stadt').grid(row=2)

        self.name_entry = tkinter.Entry()
        self.street_and_number_entry = tkinter.Entry()
        self.postcode_entry = tkinter.Entry()

        self.name_entry.grid(row=0,column=1)
        self.street_and_number_entry.grid(row=1,column=1)
        self.postcode_entry.grid(row=2,column=1)

        tkinter.Button(text='Schließen', command=self.master.destroy).grid(row=11,column=0)
        tkinter.Button(text='Eingabe', command=self.create_document).grid(row=11,column=1)
    
    def create_document(self):
        name = self.name_entry.get()
        street = self.street_and_number_entry.get()
        postcode = self.postcode_entry.get()
        print(name, street, postcode)


def main():
    root = tkinter.Tk()
    root.title('Dokumente erstellen')
    app = App(root)
    app.mainloop()

if __name__ == "__main__":
    main()
Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Das erste Argument von Label oder Button ist immer das Widget, in dem es dargestellt wird; dieses erste Argument fehlt bei Dir immer, so dass intern das globale Hauptfenster benutzt wird.

Code: Alles auswählen

import tkinter as tk

class App(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master)

        tk.Label(self, text='Auftraggeber').grid(row=0, column=0)
        self.name_entry = tk.Entry(self)
        self.name_entry.grid(row=0,column=1)

        tk.Label(self, text='Straße und Hausnummer').grid(row=1, column=0)
        self.street_and_number_entry = tk.Entry(self)
        self.street_and_number_entry.grid(row=1,column=1)

        tk.Label(self, text='PLZ & Stadt').grid(row=2, column=0)
        self.postcode_entry = tk.Entry(self)
        self.postcode_entry.grid(row=2,column=1)

        tk.Button(self, text='Schließen', command=self.master.destroy).grid(row=11,column=0)
        tk.Button(self, text='Eingabe', command=self.create_document).grid(row=11,column=1)
    
    def create_document(self):
        name = self.name_entry.get()
        street = self.street_and_number_entry.get()
        postcode = self.postcode_entry.get()
        print(name, street, postcode)


def main():
    root = tk.Tk()
    root.title('Dokumente erstellen')
    app = App(root)
    app.pack()
    root.mainloop()

if __name__ == "__main__":
    main()
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Vielen Dank für die Erklärung.
Ich hoffe das nicht nur ich was gelernt habe, wollte dieses Thema nicht übernehmen :)

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Antworten