Home
Navigation
Impressum
Coder Welten - Programmierung und Optimierung
Coder Welten
 
 

 

 

Ein Notizbuch mit Python 3 und Tkinter

Ein kleines Schreibprogramm mit Editor

Es gibt mehr als einen Weg, sich als Einsteiger mit einer Programmiersprache ver­traut zu machen. Ohne Suchen und sehr viel Lesen wird es nie abgehen, ein Unterschied besteht jedoch in der Vorgehensweise. Am schnellsten lernt es sich bei einer konkreten Aufgabenstellung und der Umsetzung eines ersten eigenen Pro­jektes, nur zu groß sollte es nicht sein. Fehler werden sich dabei mit Sicher­heit einstellen, doch Fehler sind bekanntlich dafür da, dass ein jeder Mensch aus seinen gemachten Fehlern lernt und diese Aussage trifft auf Programmierer nicht weniger zu, als auf alle anderen menschlichen Lebensformen.

Nachfolgend ein Beispiel für ein kleines Notizbuch, welches im prozeduralen Pro­grammierstil umgesetzt wurde. Der prozedurale Stil hat den Vorteil, dass ein Einsteiger noch nicht unbedingt dem objektorientierten Programmierstil (OOP) ver­traut sein muss, um eine erste Anwendung zu realisieren. Dem objekt­orientierten Programmierstil kann sich der Einzelne später zuwenden, sozusagen in einem zweiten Schritt, nachdem er bereits bestens mit der Syntax, mit Variablen, State­ments, Schleifen, Tupeln, Dictionaries und Funktionen vertraut ist.
Es sei angemerkt, prozedurale Programmierung sollte in diesem Zusammenhang nicht mit dem Begriff Prozeduren in einem Topf geworfen werden. Eine Funktion ohne eine Return-Anweisung wird als Prozedur bezeichnet, wenn sie z.B. dazu beiträgt, einen Wert in ein Textfeld zu schreiben, statt diesen Wert nach einer Bearbeitung per return-Anweisung zurückzugeben.
Bei der prozedurale Programmierung wird hingegen nur ein Schritt nach dem anderen im Programmablauf umgesetzt, ohne diese Schritte bereits in Klassen zu gruppieren. Doch ganz so eindeutig und abgrenzend sind die einzelnen Bezeich­nungen oftmals nicht. Betrachten wir deshalb die nachfolgende Anwendung etwas näher.

Ansicht des Notizbuches
Ansicht des Notizbuches bei 520 x 360 Pixel mit einem 'Lorem ipsum'-Platzhaltertext.

Zu dem Funktionsumfang der Anwendung gehören zwei wesentliche Programme. Das erste von den beiden Programmen ist ein einfaches Schreibprogramm zum Schreiben, Erfassen und Speichern von neuen Einträgen. Beim zweiten Programm handelt es sich praktisch um einen kleinen Editor, der das Öffnen, Editieren und Speichern von geänderten Notizen oder Beiträgen ermöglicht. Beide Programme werden in einer Anwendung vereint, die so zu einer voll funktionsfähigen Soft­ware wird.

Wer sich bereits mit dem Öffnen, Einlesen und Speichern von Dateien auskennt, wird erkennen, dass beim Speichern von neuen Notizen für "open" der Modus "a" gewählt wurde. Bei einem Klick auf dem Button wird nun am Anfang eines jeden Monats eine Datei mit dem Namen

"notizen-im-[aktueller Monat]-[aktuelle Jahreszahl].txt"

angelegt und der neue Eintrag gespeichert. Besteht die Datei bereits, so wird der interne Dateizeiger am Ende der Datei platziert und die Datei um den neuen Eintrag erweitert.
Erwähnenswert ist nebenher die Schreibweise von ("1.0", END), da deren erster Teil je nach Quelle einmal mit und ein anderes Mal ohne Anführungszeichen notiert wird. Dazu sollte ein angehender Programmierer wissen, dass es sich um keine Gleitkommazahl handelt. Eher verhält es sich ähnlich wie in der Welt der Hühner, wo eine Angabe von 1.7 einer Hühnerschar von genau 8 Hühnern entsprechen würde, bestehend aus einem Hahn und sieben Hennen. Nur ist bei Python die Zeile 1 mit dem Zeichen 0 (Beginn) gemeint. Bei "2.3" würde Python z.B. ab der zweiten Zeile und dem dritten Zeichen mit dem Lesen oder Löschen beginnen. Ob die Notierung mit oder ohne Anführungszeichen erfolgt, Python und Tkinter werten beide Schreibweisen korrekt aus.

Im zweiten Teil, dem Bereich zum Editieren bestehender Einträge, wird eine ausge­wählte Datei mit mode "r" geöffnet, in das Textfeld geladen und mit mode "w" wieder gespeichert. Eigentlich ist "r" nur zum Lesen von Dateien gedacht und nicht zum Schreiben oder zum Editieren. Doch nach dem der Inhalt aus der Datei ins Textfeld geschrieben wurde, wird die Datei ohnehin sofort wieder geschlos­sen. Wenn der Editor mit seiner Arbeit fertig und zufrieden ist, wird mit "w" der Datei­zeiger beim Speichern auf den Anfang der Datei gesetzt und somit der Inhalt der Datei gegen den bearbeiteten Content vom Textfeld ausgetauscht.
Als letzter Schritt wird der Wert der Variablen "dname" auf None gesetzt, wo­durch verhindert wird, dass bei einem versehentlichen zweiten Klick auf Speichern die Datei nicht mit dem Inhalt vom nunmehr leeren Textfeld erneut überschrieben wird. Ohne diese letzte Zeile könnte hingegen der komplette Inhalt einer Datei unbe­absichtigt gelöscht werden.
Weiterhin sei erwähnt, das Textfenster wächst ab einer gewissen Fenstergröße nicht mehr analog mit dem Fenster mit. Es ließe sich jedoch entsprechend forma­tieren, wenn dabei berücksichtigt wird, dass die Berechnung nicht in Pixel, sondern in Zeilenhöhe und in Zeichenbreite entsprechend der verwendeten Schrift­größe er­folgen müsste. Siehe weitere Hinweise und Möglichkeiten.

Allgemeine Hinweise

Bei den Bezeichnern von selbstdefinierten Funktionen sind zwei Schreibweisen ver­breitet, wobei von den Entwicklern von Python die zweite favorisiert wird und die erste eigentlich nicht mehr benutzt werden sollte. Dem steht die Großschreibung von Substantiven gegenüber, die im deutschen Sprachraum mehr an die natürliche Schreibweise angelehnt ist und somit leichter lesbar.

def camelCase():
def camel_case():

Wer ein Script einreichen oder in einem Forum vorstellen möchte, sollte sich hinge­gen an die zweite Schreibweise halten sowie Variablen immer klein und Konstanten durchgehend mit Großbuchstaben schreiben.

Weiterhin sollten Variablen nur in Ausnahmefällen "global" gemacht werden. Spä­testens ab der dritten globalen Variable sollte ein Einsteiger sich mit OOP und Klassen beschäftigen, da in einer Klasse Variablen als Instanzattribute initialisiert werden können, so dass diese in einer Klasse allgemein zur Verfügung stehen.
Weiterhin sollte ein Einsteiger bedenken, nicht nur bei den beiden in den Funk­tionen global gemachten Variablen handelt es sich um globale Variablen, sondern strenggenommen alle außerhalb einer Funktion definierten Variablen stehen im Script global zur Verfügung. Wird nun der Wert einer globalen Variable verändert, beginnt beim Auftreten von Fehlern die Suche, wo diese Veränderung welchen Effekt bewirkte, was bei umfangreicheren Anwendungen mindestens eine Fehler­suche erschweren könnte.
Somit gesehen ist das Script auf dieser Seite zwar durchaus funktionsfähig, doch wer sich mit der Absicht trägt, es noch weiter auszubauen, sollte sich in OOP ein­arbeiten und der zweiten Version von diesem Notizbuch zuwenden bzw. lieber diese zweite Version benutzen.

Verbesserte Version: Notizbuch mit Python 3 und Tkinter (Version 2.0)

In Version 2.0 sind noch weitere Änderungen enthalten, die im vorausgehenden Text nicht erwähnt wurden. So haben wir uns in der neueren Version mehr an den Leitfaden für Python-Code (PEP 8) gehalten, von dem im Listing auf dieser Seite noch nicht übermäßig viel zu sehen ist.

Code – Version 1.0:

# ---------------------------------------------------------------------
# Description: Ein universelles Notizbuch
# Autor:       Horst Müller
# Version:     1.0 Release Candidate
# Datum:       08. Mai 2017
# ---------------------------------------------------------------------
from tkinter import Tk, Frame, Button, Label, Text, PhotoImage, Scrollbar, END
from tkinter.filedialog import askopenfilename, asksaveasfile
import time
import os

# --------------------------------------------------------------------
# Globale Variable und Konstanten vor dem Deklarieren und Definieren 
# auf Vorhandensein prüfen, z.B. mit print(globals()) an dieser Stelle.
# ---------------------------------------------------------------------
FONT  = "cambria"
dname = None

# ---------------------------------------------------------------------
# Deutsche Monatsnamen für Datum und monatliche Dateinamen benutzen.
# Beispiel Datum: 06. Mai 2017
# Beispiel Verzeichnis plus Dateiname: reminder\notizen-im-mai-2017.txt
# ---------------------------------------------------------------------
deutsche = {
    "01" : "Januar",
    "02" : "Februar",
    "03" : "Maerz",
    "04" : "April",
    "05" : "Mai",
    "06" : "Juni",
    "07" : "Juli",
    "08" : "August",
    "09" : "September",
    "10" : "Oktober",
    "11" : "November",
    "12" : "Dezember"
    }

wotag = time.strftime("%d")    # Wochentage 01 bis 31
monat = time.strftime("%m")    # Monate     01 bis 12
jahre = time.strftime("%Y")    # Jahre allgemein

sidebar = "{0:s}. {1:s} {2:s}".format(wotag, deutsche[monat], jahre)
ablage  = "reminder"           # Verzeichnis fuer die Speicherung der Notizen
datname = "notizen-im-{0:s}-{1:s}.txt".format(deutsche[monat].lower(), jahre)
abspfad = os.path.abspath(".")
pfadnam = os.path.join(abspfad, ablage, datname)

# ---------------------------------------------------------------------
# Ein Fenster erzeugen, einen Fenstertitel plus Icon hinzufuegen, sowie
# die Größe und Aufteilung festlegen.
# ---------------------------------------------------------------------
fenster = Tk()
fenster.title("Mein Notizbuch")
fenster.wm_iconbitmap("logo.ico")
fenster.geometry("640x420")   # Für Screenshots wurden 520x360 benutzt.

# Aufteilung in linken und rechten Frame
frame_l = Frame(fenster)
frame_r = Frame(fenster)

# --- Alle Funktionen -------------------------------------------------

# Datei anlegen, falls noch nicht vorhanden und neue Notizen speichern.
def speicherNeue():
    with open(pfadnam, "a") as datei:
        datei.write(textfld.get("1.0", END))
        textfld.delete("1.0", END)
        textfld.insert(END, "Eintrag erfolgreich!")

# Funktion für das Einfügen eines Datums als Option.
def setzeDatum():
    textfld.insert(END, "Notizen vom " + sidebar + "\n\n")

# Bestehende Datei zum Lesen oder zum Editieren öffnen.
def oeffneNotizen():
    global dname
    dname = askopenfilename(filetypes=[("Text Datei", "*.txt")])
    if dname is not None and dname != "":
        with open(dname, "r") as datei:
            textfld.delete("1.0", END)
            textfld.insert(END, datei.read())

# Die alte Datei wird nach dem Editieren überschrieben.
def speicherNotizen():
    global dname
    if dname is not None:
        with open(dname, "w") as datei:
            datei.write(textfld.get("1.0", END))
            textfld.delete("1.0", END)
            textfld.insert(END, "Änderung erfolgreich!")
            dname = None

# Manuelle Wahl des Speicherortes und des Dateinamens.
def waehlePfadName():
    datei = asksaveasfile(mode = "a",
                     filetypes = [("Text Datei", "*.txt")])
    if datei:
        datei.write(textfld.get("1.0", END))
        datei.close()
        textfld.delete("1.0", END)
        textfld.insert(END, "Gespeichert!")

# --- Labels für die linke Sidebar ------------------------------------

# Label für linke Seite oben (lob gleich links oben)
sidelob = Label(frame_l,
                  text = "Notizen",
                    fg = "#c7a621",
                  font = (FONT, 12, "bold"),
                  pady = 8)

# Label für linke Seite mittig (lmi gleich links mittig)
sidelmi = Label(frame_l,
                  text = "Ältere Notizen",
                    fg = "#c7a621",
                  font = (FONT, 9, "bold"),
                  pady = 8)

# Label für linke Seite unten (lun gleich links unten)
fraktal = PhotoImage(file = "neuronen.gif")
sidelun = Label(frame_l, image = fraktal)

# --- Alle Buttons ----------------------------------------------------

# Datei anlegen, falls noch nicht vorhanden und neue Notizen speichern.
anlegen = Button(frame_l,
                   text = "Neue speichern",
                   font = (FONT, 10),
                command = speicherNeue)

# Button für das Einfügen eines Datums als Option.
aktuell = Button(frame_l,
                   text = "Datum einfügen",
                   font = (FONT, 10),
                command = setzeDatum)

# Bestehende Datei zum Lesen oder zum Editieren öffnen.
oeffnen = Button(frame_l,
                   text = "Öffnen",
                   font = (FONT, 10),
                command = oeffneNotizen)

# Speichern normal
sp_norm = Button(frame_l,
                   text = "Speichern",
                   font = (FONT, 10),
                command = speicherNotizen)

# Speichern unter ... als mögliche Auswahl anbieten.
sp_ausw = Button(frame_l,
                   text = "Speichern unter ...",
                   font = (FONT, 10),
                command = waehlePfadName)

# --- Textfeld mit Scrollbar ------------------------------------------

# Ein Textfeld mit Scrollbar erstellen und formatieren.
textfld = Text(frame_r, pady = 10, padx = 10, wrap = "word")
scrollb = Scrollbar(frame_r)
scrollb.config(command = textfld.yview)
textfld.config(yscrollcommand = scrollb.set)

# --- Der Aufbau des Fensters -----------------------------------------

# Linke Seite packen
frame_l.pack(side = "left")
sidelob.pack()
anlegen.pack(fill = "x", padx = 8)
aktuell.pack(fill = "x", padx = 8)
sidelmi.pack(fill = "x", padx = 8)
oeffnen.pack(fill = "x", padx = 8)
sp_norm.pack(fill = "x", padx = 8)
sp_ausw.pack(fill = "x", padx = 8)
sidelun.pack(pady = 24)

# Rechte Seite packen
frame_r.pack(side = "left")
scrollb.pack(side = "right", fill = "y")
textfld.pack(pady = 4, padx = 4)

# Die Hauptschleife
fenster.mainloop()

Das Script auf dieser Seite wurde mit Python 3.6 und Windows 10 getestet. Falls Sie es selbst unter Windows testen oder benutzen möchten, muss eine neuere Python Version auf Ihrem Computer installiert werden, falls noch nicht vorhan­den. Weiterhin muss in dem Verzeichnis, in dem sie dieses Script ablegen, ein Ordner mit der Bezeichnung "reminder" abgelegt werden oder Sie müssen den bei der Variablen "datname " angegebenen Pfad entsprechend anpassen.
Abschließend sei noch erwähnt, dass wir keine Gewähr für die Fehlerfreiheit des Scripts übernehmen können.

 

Copyright © Verlag Horst Müller - Stendal - 2006 - Impressum - Datenschutz - Nutzungsbedingungen