Programm zieht viel RAM und wird langsam

Fragen zu Tkinter.
Antworten
rundll
User
Beiträge: 9
Registriert: Freitag 13. Juni 2014, 12:48

Hallo,

ich bin neu hier und in Python auch noch ziemlich am Anfang.
Habe ein Programm geschrieben, das quasi einen Kalender darstellen soll.

Dazu folgender Code:

Code: Alles auswählen

# -*- coding: utf-8 -*-
from tkinter import *
from Kalender import *

def clicked(arg):
    print("geklickt (Tag): " + str(arg))

root = Tk()
root.title("Kalender-Demonstration")
kalender = Kalender(clicked, root)
root.mainloop()
Die Datei Kalender.py:

Code: Alles auswählen

# -*- coding: utf-8 -*-
from tkinter import *
from KalenderLogik import *
from TagKachel import *
from HeaderKachel import *
from Arrow import *

class Kalender(Frame):
    def __init__(self, listener, master = None):
        Frame.__init__(self, master)
        self.pack()
        self.KalenderLogik = KalenderLogik()
        self.setToToday()
        self.listener = listener
        
    def rightArrowClicked(self, event):
        print("rechter Pfeil")
        self.KalenderLogik.increaseMonth()
        self.updateIt()
        
    def leftArrowClicked(self, event):
        print("linker Pfeil")
        self.KalenderLogik.decreaseMonth()
        self.updateIt()
        
    def createRightArrow(self):
        self.rightArrow = Arrow("right", self.rightArrowClicked, self)
        self.rightArrow.grid(row = 0, column = 6)
        
    def createLeftArrow(self):
        self.leftArrow = Arrow("left", self.leftArrowClicked, self)
        self.leftArrow.grid(row = 0, column = 0)
        
    def updateIt(self):
        print(self.KalenderLogik.getMonth())
        self.createKacheln()
        self.createHeaderKacheln()
        self.createMonthLabel()
        self.createRightArrow()
        self.createLeftArrow()
        
    def setBackgroundColor(self, day, color):
        self.TagKachelList[day-1].setBackgroundColor(color)
        
    def createMonthLabel(self):
        monthLabel = Canvas(self, width = 5*32, heigh = 30)
        monthLabel.create_rectangle(2, 2, (5*32)-2, 28, fill = "white")
        monthLabel.create_text((((5*32)-2)/2), 15, text = self.KalenderLogik.months[self.KalenderLogik.getMonth()-1] + " " + str(self.KalenderLogik.getYear()), font = 15)
        monthLabel.grid(row = 0, column = 1, columnspan = 5)
        
    def createHeaderKacheln(self):
        self.HeaderKachelList = []
        for i in range(0, 7):
            day = self.KalenderLogik.days[i]
            self.HeaderKachelList.append(HeaderKachel(day, self))
            self.HeaderKachelList[i].grid(row = 1, column = i)

    def createKacheln(self):
        spacer = 6
        self.TagKachelList = []
        for iDay in range(0, self.KalenderLogik.getNumberOfDaysInMonth(self.KalenderLogik.getMonth(), self.KalenderLogik.getYear())):
            self.TagKachelList.append(TagKachel(iDay+1, self))
            self.TagKachelList[iDay].grid(row = int((iDay+spacer) / 7) + 2, column = (iDay+spacer) % 7)

    def getDay(self):
        return self.KalenderLogik.getDay()
    def getMonth(self):
        return self.KalenderLogik.getMonth()
    def getYear(self):
        return self.KalenderLogik.getYear()

    def setDay(self, day):
        self.KalenderLogik.setDay(day)
        self.updateIt()
    def setMonth(self, month):
        self.KalenderLogik.setMonth(month)
        self.updateIt()
    def setYear(self, year):
        self.KalenderLogik.setYear(year)
        self.updateIt()

    def setToToday(self):
        self.KalenderLogik.setToToday()
        self.updateIt()
        
    def notify(self, message):
        self.listener(message)
        
    def setListener(self, listener):
        self.listener = listener
die Datei KalenderLogik.py:

Code: Alles auswählen

# -*- coding: utf-8 -*-
from time import *

class KalenderLogik:
    def __init__(self, master = None):
        self.days = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]
        self.months = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"]
        self.setToToday()
        
    def increaseMonth(self):
        if self.month in range(1, 12):
            self.month = self.month + 1
        
    def decreaseMonth(self):
        if self.month in range(2, 13):
            self.month = self.month - 1

    def setToToday(self):
        lt = localtime()
        self.year, self.month, self.day = lt[0:3]

    def getYear(self):
        return self.year
    def getMonth(self):
        return self.month
    def getDay(self):
        return self.day

    def setYear(self, year):
        self.year = year
    def setMonth(self, month):
        self.month = month
    def setDay(self, day):
        self.day = day
        
    def getNumberOfDaysInMonth(self, month, year):
        if month <= 7:
            if month == 2:
                if self.schaltjahr(year) == True:
                    return 29
                else:
                    return 28
            else:
                if month % 2 == 0:
                    return 30
                else:
                    return 31
        else:
            if month % 2 == 0:
                return 31
            else:
                return 30

    def schaltjahr(self, year):
        if year % 4 == 0:
            if year % 100 != 0:
                return True
            else:
                if year % 400 == 0:
                    return True
                else:
                    return False
Die Datei Kachel.py:

Code: Alles auswählen

# -*- coding: utf-8 -*-
from tkinter import *

class Kachel(Frame):
    def __init__(self, data, backgroundColor, textColor, master = None):
        Frame.__init__(self, master)
        self.data = data
        self.master = master
        self.backgroundColor = backgroundColor
        self.textColor = textColor
        self.Data = Canvas(self, width = 30, heigh = 30)
        self.drawStuff()
        self.Data.pack()
        
    def drawStuff(self):
        self.Data.create_rectangle(2, 2, 28, 28, fill = self.backgroundColor)
        self.Data.create_text(15, 15, text = self.data, fill = self.textColor, font = 15)

    def setBackgroundColor(self, color):
        self.backgroundColor = color
        self.drawStuff()
Die Datei HeaderKachel.py:

Code: Alles auswählen

# -*- coding: utf-8 -*-
from Kachel import *

class HeaderKachel(Kachel):
    def __init__(self, day, master = None):
        Kachel.__init__(self, day, "green", "red", master)
Die Datei TagKachel.py:

Code: Alles auswählen

# -*- coding: utf-8 *-*
from Kachel import *

class TagKachel(Kachel):
    def __init__(self, day, master = None):
        Kachel.__init__(self, day, "blue", "red", master)
        self.Data.bind("<Button-1>", self.callback)
    
    def callback(self, event):
        self.master.notify(self.data)
Die Datei Arrow.py:

Code: Alles auswählen

# -*- coding: utf-8 -*-
from tkinter import *

class Arrow(Frame):
    def __init__(self, direction, listener, master = None):
        Frame.__init__(self, master)
        self.Data = Canvas(self, width = 30, heigh = 30)
        self.Data.create_rectangle(2, 2, 28, 28, fill = "white")
        self.drawArrow(direction)
        self.Data.bind("<Button-1>", listener)
        self.Data.pack()
        
    def drawArrow(self, direction):
        if direction == "right":
            self.Data.create_text(15, 15, text = "->")
        if direction == "left":
            self.Data.create_text(15, 15, text = "<-")
Wenn ich das Programm ausführe, und ein paar mal auf die Arrows klicke, dann wird das Programm immer langsamer und man sieht im Systemmonitor, wie der Speicherverbrauch zunimmt.
Meine Frage: woran kann das liegen?
Ich benutze Python 3.3.1 unter Kubuntu.

Danke im Vorraus!

Gruß
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo und willkommen im Forum!

Ich glaube nicht, dass jemand hier so viel Code lesen möchte. Schon gar nicht mit so vielen *-Importen, da kann quasi sich alles gegenseitig überschreiben. Erstelle einfach mal eine Kopie deines Projekts und streiche alles unnötigen Programmteile. Das machst du dann so lange bis der Fehler verschwindet oder bis nur noch der Fehler übrig bleibt. In beiden Fällen hast du das Problem gefunden.
Das Leben ist wie ein Tennisball.
BlackJack

@rundll: Ich würde mal darauf tippen das Du die GUI-Elemente die nicht mehr benötigt werden auch explizit zerstören musst.

Das sieht alles sehr „unpythonisch” aus. Mehr nach Java. Eine Klasse pro Datei macht keinen Sinn, weil man damit das Modul als Namensraum entwertet. Und wenn man dann auch noch überall Sternchen-Importe verwendet, schmeisst man letztlich doch wieder alles zusammen, warum dann erst trennen?

Ein paar der Getter und Setter würde man in Python eher durch `property()` ausdrücken.

Für Kalender und Datumsrechnungen gibt es in der Standardbibliothek bereits die Module `calendar` und `datetime`, da muss man nicht alles noch mal selber erfinden.

Die Namensschreibweisen halten sich nicht an den Style Guide for Python Code. Konkrete Typen wie `List` gehören nicht in Namen und Typpräfixe ebenfalls nicht.
rundll
User
Beiträge: 9
Registriert: Freitag 13. Juni 2014, 12:48

Hallo,

vielen Dank für die Antworten.
wie und wann muss ich denn die Objekte explizit löschen?
Macht das nicht der Garbage Collektor von alleine? (Wie bei Java?)
Gibt es irgendwie eine Möglichkeit, sich quasi eine Liste mit allen existierenden Objekten zu einem Zeitpunkt ausgeben zu lassen?
So etwas wie ein Debuger?

Ich habe jetzt die from ... import * zu import ... angepasst.
Das Problem ist immer noch unverändert, allerings ist mir das jetzt so doch einleuchtend.
Das ganze ist bloß ein Projekt zum üben..... ist das erste mal, dass ich mit Python zu tun habe....
(Ja, davor leider Java :roll: )

Gruß
BlackJack

@rundll: Die automatische Speicherbereinigung kümmert sich nur um Python-Objekte die nicht mehr erreichbar sind oder verwendet werden. Wenn man ein Tk-Widget erzeugt übergibt man ja den `parent` und der kennt dadurch alle seine Kinder, also muss der ja auch irgendwo eine Referenz darauf enthalten, und die verschwindet nicht einfach so weil in Code ausserhalb von Tk keine Referenzen mehr existieren. Erst wenn man `destroy()` aufruft wird diese Beziehung gekappt und die Objekte können dann auch wirklich freigegeben werden (falls nicht noch woanders Referenzen bestehen).

Code: Alles auswählen

In [9]: root = tk.Tk()

In [10]: root.children
Out[10]: {}

In [11]: label = tk.Label(root, text='Hallo')

In [12]: root.children
Out[12]: {'157766380': <Tkinter.Label instance at 0x96752ec>}

In [13]: label = tk.Label(root, text='Welt')

In [14]: root.children
Out[14]: 
{'157766380': <Tkinter.Label instance at 0x96752ec>,
 '157766732': <Tkinter.Label instance at 0x967544c>}

In [15]: label.destroy()

In [16]: root.children
Out[16]: {'157766380': <Tkinter.Label instance at 0x96752ec>}
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

BlackJack hat geschrieben:Das ganze ist bloß ein Projekt zum üben..... ist das erste mal, dass ich mit Python zu tun habe....
(Ja, davor leider Java :roll: )
Wieso leider, Java ist eine tolle Sprache, Du solltest lediglich Python nicht wie Java programmieren:
http://dirtsimple.org/2004/12/python-is-not-java.html
Der Link ist 10 Jahre alt, aber offensichtlich immer noch aktuell.

Eine Programmiersprache, die nicht Deine Art des Programmierens ändert, ist es nicht wert gelernt zu werden.

Das kannst Du z.B. in Python alles weglassen. Diese Properties kapseln nichts, da Du an die Member-Variablen direkt dran kommst, alles ist public.

Code: Alles auswählen

    def getYear(self):
        return self.year
    def getMonth(self):
        return self.month
    def getDay(self):
        return self.day
 
    def setYear(self, year):
        self.year = year
    def setMonth(self, month):
        self.month = month
    def setDay(self, day):
        self.day = day
a fool with a tool is still a fool, www.magben.de, YouTube
rundll
User
Beiträge: 9
Registriert: Freitag 13. Juni 2014, 12:48

Hallo,

ok, ich konnte das Problem lokalisieren: Es ist wie von euch schon vermutet dass ich nicht "destroy()" aufgerufen habe.

Aber jetzt mit "destroy()" wird es zwar nicht langsam, und der Speicherverbrauch steigt auch nicht mehr an, aber jetzt flackert es ganz fies :K
Ich habe ja eine Liste von Elementen, die lösche ich alle.
Dann erstelle ich neue, mit anderen Werten...... wie kann man das Flackern verhindern?

Gruß
BlackJack

@rundll: Ich hoffe Du `destroy()`\st jetzt nicht alles was Du erstellt hast einzeln. Normalerweise steckt man alles was man austauschen möchte in ein Elternwidget und zerstört den/tauscht den aus. Kindelemente werden bei einem `destroy()` nämlich auch zerstört.

Und man sollte immer erst alles in diesem Elternwidget hinzufügen und layouten bevor man das Widget selbst layoutet, sonst passiert das alles sichtbar.
rundll
User
Beiträge: 9
Registriert: Freitag 13. Juni 2014, 12:48

Hallo,

ja, ich habe jetzt ein Elternwidget erstellt und dem alle Widgets hinzugefügt, die ich erneuern möchte.
Dann lösche ich den Elternwidget und erstelle einen neuen.
Leider sieht man es immer noch flackern.......
Was kann man denn nur tun?
Ich glaube es liegt daran, dass es recht lange dauert, um einen Elternwidget zu erstellen, weil er so viele Kinder hat.....
Ich habe aber auch schon so versucht:

Code: Alles auswählen

def updateIt(self):
        tmpBuffer = self.createKacheln()
        self.buffer.destroy()
        self.buffer = tmpBuffer
        self.buffer.grid(row = 2, column = 0, columnspan = 7)
Aber es flackert immer noch......

Gruß
BlackJack

@rundll: Ich würde erst das Neue ”layouten” und dann erst das Alte zerstören. Falls das nicht hilft, bräuchten wir wohl Quelltext zum nachvollziehen.
rundll
User
Beiträge: 9
Registriert: Freitag 13. Juni 2014, 12:48

Hallo,

ich habe es jetzt so versucht:

Code: Alles auswählen

def updateIt(self):
        tmpBufferAlt = self.buffer
        tmpBufferNeu = self.createKacheln()
        tmpBufferNeu.grid(row = 2, column = 0, columnspan = 7)
        
        self.buffer = tmpBufferNeu
        tmpBufferAlt.destroy()
und die createKacheln() Methode sieht so aus:

Code: Alles auswählen

def createKacheln(self):
        tmp = BufferClass.BufferClass(self)
        spacer = 6
        for iDay in range(0, self.kalenderLogik.getNumberOfDaysInMonth(self.kalenderLogik.getMonth(), self.kalenderLogik.getYear())):
            tmp.addWidget(Kachel.TagKachel(iDay+1, tmp))
            tmp.widgets[iDay].grid(row = int((iDay+spacer) / 7), column = (iDay+spacer) % 7)
        return tmp
Und die komplette Datei BufferClass:

Code: Alles auswählen

# -*- coding: utf-8 -*-
from tkinter import *

class BufferClass(Frame):
    def __init__(self, master = None):
        Frame.__init__(self, master)
        self.widgets = []

    def addWidget(self, widget):
        self.widgets.append(widget)

    def notify(self, data):
        self.master.notify(data)
Es flackert immer noch ein bisschen.... Aber das Programm wird nicht mehr so langsam wie am Anfang (als ich ohne destroy() versucht habe)
Vielen Dank für die tolle Hilfe hier! Das hilft als Anfänger sehr!
Kann man das Flackern komplett weg bekommen?
BlackJack

@rundll: Das sieht ja immer noch so aus als wenn Du jede Klasse in ein eigenes Modul steckst. Und statt Sternchenimport qualifizierst Du die Klassen jetzt komplett über das Modul? Das ist doch total bescheuert überall ``KlassenName.KlassenName`` stehen zu haben. Die Sternchenimporte von `tkinter` sollte man auch nicht machen. Das sind ca. 190 Namen die man da importiert und von denen ganz sicher nur ein sehr kleiner Bruchteil tatsächlich benötigt wird.

Die `BufferClass`-Klasse ist mir nicht so ganz klar. Wo wird das `widgets`-Attribut verwendet?

Das `Class` im Namen ist überflüssig, denn das es eine Klasse ist weis man auch ohne den Zusatz.

Der (Teil)Name `tmp` wird ein wenig oft benutzt. Bei `updateIt()` bringt der Zusatz keinen wirklichen Erkenntnisgewinn und in `createKacheln()` wäre als generischer Name `result` besser, oder vielleicht sogar ein Name der die konkretere Bedeutung dieses Wertes verrät.

Das Zusammenspiel zwischen `createKacheln()` und `BufferClass.widgets` ist fragil. Die Methode weiss da IMHO zu viel über den internen Zustand von `BufferClass`, zum Beispiel das ein gerade hinzugefügtes Widget *auch* über den Index `iDay` abfragbar ist.

Wozu ist `spacer` gut? Kann es sein das Du leere Spalten/Zeilen vom Gridlayout als grafischen Abstand missbrauchst?

Ganzzahlige Division kann man mit dem ``//``-Operator erreichen. Und wenn man Division *und* Modulo für die gleichen Werte braucht, bietet sich die `divmod()`-Funktion an.

Warum muss man `getNumberOfDaysInMonth()` eigentlich den Monat und das Jahr übergeben wenn diese beiden Informationen doch schon zum internen Zustand des Objekts gehören auf denen diese Methode aufgerufen wird? Das sieht unnötig umständlich aus.
BlackJack

rundll: Noch ein bisschen Code-Kritik zur `KalenderLogik`:

Das `master`-Argument bei der `__init__` wird nicht verwendet.

Attribute sollte in der `__init__()` alle gesetzt werden damit man sich nicht den kompletten Quelltext einer Klasse anschauen muss um herauszufinden welche Attribute sie hat. Ich würde also Tag, Monat, und Jahr dort an `None` binden auch wenn das durch den Aufruf von `setToToday()` gleich wieder an andere Werte gebunden wird.

Die Attribute `days` und `months` enthalten bei jedem Exemplar der Klasse die gleichen Werte, also wäre es sinnvoller die als Konstanten an die Klasse zu binden und nicht für jedes Exemplar neu zu erstellen.

Die Tests in `increaseMonth()` und `decreaseMonth()` sind potentiell ineffizient. Wenn `range`-Objekte den ``in``-Operator nicht explizit unterstützen, dann ist die Standardimplementierung eine lineare Suche. Ausserdem interessiert bei jedem der beiden Tests eigentlich nur der jeweilige Endpunkt in der Richtung in der man sich ”bewegen” will. Warum werden hier nicht auch Jahreswechsel unterstützt?

Die Methoden haben zudem das Problem das sie das Objekt in einen illegalen Zustand versetzen könne, denn wenn man den Monat ändert, kann es passieren das der `day`-Wert zusammen mit dem neuen Monat ungültig ist. Wird `day` überhaupt irgendwo verwendet?

Zu den trivialen Gettern/Settern wurde ja schon etwas gesagt: die können weg.

`getNumberOfDaysInMonth()` und `schaltjahr()` sind semantisch keine Methoden, die gehören so also auch nicht in die Klasse.

Die `schaltjahr()`-Funktion hat eine inkonsistente API, denn sie kann neben `True` und `False` auch `None` zurückgeben. Das ist unschön.

Wenn man bei einem ``if``/``else`` in beiden Zweigen ein literales `True` und `False` zurück gibt, dann ist das ``if``/``else`` überfüssig, denn die Bedingung beim ``if`` ergibt ja schon ein Wahrheitswert den man direkt zurückgeben kann.

Code: Alles auswählen

def schaltjahr(year):
    if year % 4 == 0:
        if year % 100 != 0:
            return True
        else:
            if year % 400 == 0:
                return True
            else:
                return False
    else:
        return False

# =>

def schaltjahr(year):
    if year % 4 == 0:
        if year % 100 != 0:
            return True
        else:
            return year % 400 == 0
    else:
        return False
Ähnliches gilt wenn es sich nicht um einen literalen Wahrheitswert handelt, sondern um einen Audruck der einen Wahheitswert darstellt. Das kann man mit logischen Verknüpfungen direkt als Ausdruck darstellen:

Code: Alles auswählen

def schaltjahr(year):
    if year % 4 == 0:
        return year % 100 != 0 or year % 400 == 0
    else:
        return False
Und auch das verbleibende ``if``/``else`` lässt sich eleminieren und die Funktion damit zum Einzeiler machen:

Code: Alles auswählen

def schaltjahr(year):
    return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
Man kann sie sogar komplett weg lassen, denn das ist genau das was die `calendar.isleap()`-Funktion aus der Standardbibliothek macht.

Bei `getNumberOfDaysInMonth()` hast Du Glück das Du den Rückgabewert von `schaltjahr()` gegen ``== True`` testest, denn bei einem ``!= False`` würde einem hier der potentielle `None`-Rückgabewert auf die Füsse fallen. Letztendlich ist an der Stelle aber das vergleichen mit einem literalen Wahrheitswert unsinnig, denn dabei kommt ja nur *wieder* ein Wahrheitswert heraus. Und zwar in diesem Fall der gleiche den man sowieso schon vor dem Vergleich hatte.

Die Funktion lässt sich übrigens auch auf einen Einzeiler eindampfen (Herleitung spare ich mir diesmal):

Code: Alles auswählen

def get_number_of_days_in_month2(year, month):
    return 30 + ((month % 2) ^ (month > 7)) - (month == 2) * (2 - isleap(year))
Und auch hier hätte man auch schon etwas fertiges im `calendar`-Modul gefunden. Dein Modul könnte man letztendlich so schreiben (ungetestet):

Code: Alles auswählen

from calendar import monthrange
from time import localtime


def get_number_of_days_in_month(year, month):
    return monthrange(year, month)[1]


class Calendar(object):

    SHORT_DAY_NAMES = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So']
    MONTH_NAMES = [
        'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli',
        'August', 'September', 'Oktober', 'November', 'Dezember'
    ]

    def __init__(self):
        self.year, self.month, self.day = None, None, None
        self.set_to_today()

    def increase_month(self):
        if self.month < 12:
            self.month += 1

    def decrease_month(self):
        if self.month > 1:
            self.month -= 1

    def set_to_today(self):
        self.year, self.month, self.day = localtime()[0:3]
Insgesamt gibt es im `calendar`-Modul mit der `Calendar`-Klasse aber noch viel mehr abgenommene Arbeit. Ich würde die Logik eher so schreiben:

Code: Alles auswählen

import calendar
from datetime import date as Date


class Calendar(object):

    SHORT_DAY_NAMES = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So']
    MONTH_NAMES = [
        'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli',
        'August', 'September', 'Oktober', 'November', 'Dezember'
    ]

    def __init__(self):
        self.year = None
        self.month = None
        self.set_to_current_month()
        self.calendar = calendar.Calendar()

    def increase_month(self):
        if self.month == 12:
            self.month = 1
            self.year += 1
        else:
            self.month += 1

    def decrease_month(self):
        if self.month == 1:
            self.month = 12
            self.year -= 1
        else:
            self.month -= 1

    def set_to_current_month(self):
        today = Date.today()
        self.year = today.year
        self.month = today.month

    def monthdays(self):
        return self.calendar.monthdayscalendar(self.year, self.month)
Die letzte Methode könnte man dann zum Beispiel so benutzen:

Code: Alles auswählen

    def create_tiles(self):
        frame = tk.Frame(self)
        for row, week in enumerate(self.calendar.monthdays()):
            for column, day in enumerate(week):
                if day:
                    tile = DayTile(frame, day)
                    tile.grid(row=row, column=column)
        frame.grid(row=42, column=23)  # Cell of the old Frame.
        self.month_frame.destroy()
        self.month_frame = frame
rundll
User
Beiträge: 9
Registriert: Freitag 13. Juni 2014, 12:48

Hallo,

vielen Dank für die ausführliche Antwort und Hilfe.
Ich muss das erst mal durcharbeiten, aber werde gerne die Ratschläge annehmen.

Ein paar Fragen habe ich aber noch:
In der Zeile:

Code: Alles auswählen

frame.grid(row=42, column=23)  # Cell of the old Frame.
(ziemlich am Ende).... warum kommt row=42 und column=23 ?
Und was genau macht diese Zeile?

Und dann hätte ich noch eine Frage zur darauffolgenden Zeile:

Code: Alles auswählen

self.month_frame.destroy()
Gibt das nicht ein Problem, wenn die Methode create_tiles() das erste mal aufgerufen wird?
(und self.month_frame noch nicht definiert/None ist?)

Gruß und Danke!
BlackJack

@rundll: Die Zeile layoutet den neuen Frame. Die Zahlen sind Beispiele, deshalb ja der Kommentar.

Stimmt, man müsste da Prüfen ob es schon einen Frame gibt oder nicht. Also wenn das mit `None` initialisiert ist, zum Beispiel:

Code: Alles auswählen

if self.month_frame:
    self.month_frame.destroy()
rundll
User
Beiträge: 9
Registriert: Freitag 13. Juni 2014, 12:48

Hallo,

Danke für die schnelle Antwort.
Jetzt ist es mir klar!

Ich melde mich wieder, wenn es etwas Neues gibt!

Gruß
BlackJack

Mal das ganze in einem Modul, mit zwei Klassen, in „pythonisch” statt „javaesque”, und ohne dauernd die halbe GUI neu zu erstellen und auszutauschen:

Code: Alles auswählen

from __future__ import print_function
try:
    import Tkinter as tk
except ImportError:
    import tkinter as tk
from calendar import Calendar, day_abbr, month_name
from datetime import date as Date
from functools import partial
try:
    from future_builtins import zip
except ImportError:
    pass
from itertools import chain, repeat
from locale import LC_ALL, setlocale
try:
    range = xrange
except NameError:
    pass


class Day(tk.Canvas, object):
    def __init__(
        self,
        master,
        day=0,
        active_color='white',
        disabled_color='grey',
        size='2c'
    ):
        self.active_color = active_color
        self.disabled_color = disabled_color
        tk.Canvas.__init__(
            self,
            master,
            width=size,
            height=size,
            bg=self.disabled_color,
            highlightbackground=self.disabled_color
        )
        self.create_text((1, 1), anchor='nw', tags='day_number')
        self._day = None
        self.day = day

    @property
    def day(self):
        return self._day

    @day.setter
    def day(self, day):
        self._day = day
        self.itemconfig('day_number', text=day if day else '')
        self['bg'] = self.active_color if day else self.disabled_color


class CalendarFrame(tk.Frame):

    def __init__(
        self, master, day_clicked_callback=lambda date: None, background='white'
    ):
        tk.Frame.__init__(self, master)
        self.day_clicked_callback = day_clicked_callback
        today = Date.today()
        self.year = today.year
        self.month = today.month
        self.calendar = Calendar()

        self.header_label = tk.Label(self, font=20)
        self.header_label.grid(row=0, column=1, columnspan=5)
        
        tk.Button(
            self, text='<-', command=self.goto_previous_month
        ).grid(row=0, column=0, sticky='news')
        tk.Button(
            self, text='->', command=self.goto_next_month
        ).grid(row=0, column=6, sticky='news')

        for day in self.calendar.iterweekdays():
            label = tk.Label(self, text=day_abbr[day], bg=background)
            label.grid(row=1, column=day, sticky='we', padx=1)

        self.cells = list()
        for row_number in range(6):
            row = list()
            for column_number in range(7):
                cell = Day(self, active_color=background)
                cell.grid(row=row_number + 2, column=column_number)
                cell.bind('<Button-1>', self._day_clicked)
                row.append(cell)
            self.cells.append(row)

        self.update()

    def _day_clicked(self, event):
        day = event.widget.day
        if day:
            self.day_clicked_callback(Date(self.year, self.month, day))

    def update(self):
        self.header_label['text'] = '{0} - {1}'.format(
            month_name[self.month], self.year
        )
        weeks = self.calendar.monthdayscalendar(self.year, self.month)
        for cells, week in zip(self.cells, chain(weeks, repeat([0] * 7))):
            for cell, day in zip(cells, week):
                cell.day = day

    def goto_previous_month(self):
        if self.month == 1:
            self.month = 12
            self.year -= 1
        else:
            self.month -= 1
        self.update()

    def goto_next_month(self):
        if self.month == 12:
            self.month = 1
            self.year += 1
        else:
            self.month += 1
        self.update()


def main():
    setlocale(LC_ALL, '')
    root = tk.Tk()
    calendar = CalendarFrame(root, partial(print, 'Tag:'))
    calendar.pack()
    root.mainloop()


if __name__ == '__main__':
    main()
rundll
User
Beiträge: 9
Registriert: Freitag 13. Juni 2014, 12:48

Hallo,

wow, jetzt funktioniert das perfekt!
Danke für das Beispiel, jetzt muss ich das genau analysieren :wink:
Wie hast du das so gut gelernt? Wielange hat das gedauert, bis du so gut mit Python umgehen konntest?
Hast du vor Python schon programmiert?
Bist du proffesioneller Programmierer?
(Ich mache das nur zum Hobby, bin Elektrotechniker)

Gruß
rundll
BlackJack

@rundll: Ich programmiere schon recht lange mit Python (>10 Jahre), habe auch schon recht lange davor in verschiedenen anderen Sprachen programmiert, und auch sonst gerne mal die ein oder andere Sprache ausprobiert. Programmieren gehört auch zum Job, hauptsächliche intern benutzte Webanwendungen, aber auch Kleinkram auf Kundenservern und -desktoprechnern. Versuche Python zu nehmen wo es geht, manchmal ist es leider auch Perl, PHP, Shell-Scripting, oder VBA (oder noch schlimmeres :-)).
rundll
User
Beiträge: 9
Registriert: Freitag 13. Juni 2014, 12:48

Hallo,

Oh, ok, das ist natürlich viel Erfahrung!
Sowiel habe ich leider noch nicht: Habe an der Uni Java gelernt und davor auch schon C programmiert, aber noch nicht viel mit Python gemacht.
Das wird sich aber jetzt ändern :mrgreen:
Auch dank deinem Beispiel und deiner Hilfe!

Gruß!
Antworten