156/418

Hlídač narozenin a svátků -- příklad

Obsah:

  1. Zadání
  2. Reference
  3. Kostra zdrojového kódu
  4. GUI
  5. Datum
  6. Jmeniny a pohyb v kalendáři
  7. Narozeniny
  8. Změny v narozeninách
  9. Chybové stavy

Zadání

Vytvořte aplikaci pro hlídání narozenin a svátků.

Reference

Kostra zdrojového kódu

#!/usr/bin/python
# -*- coding: utf8 -*-

from Tkinter import *

##################################################
### Definice funkcí 


##################################################
### GUI
okno = Tk()


##################################################
## Nekonečná smyčka
okno.mainloop()

`--> stáhnout

GUI

Jsou zde použity widgety LabelFrame, Label, Entry, Button, ListBox spolu se správcem rozmístění grid.

okno = Tk()
okno.config(padx=10, pady=10)
okno.title("Hlídač")

### Datum
LFdatum=LabelFrame(okno, text='Datum', padx=10, pady=10 )
LFdatum.grid( sticky=W+E, )

denVar = StringVar()
mesicVar = StringVar()
rokVar = StringVar()
LFdatum.grid_columnconfigure(0, weight=1)
LFdatum.grid_columnconfigure(1, weight=1)
LFdatum.grid_columnconfigure(2, weight=1)
LFdatum.grid_columnconfigure(3, weight=1)

lbl = Label(LFdatum, text=u"Den").grid(row=0,column=0)
SBden = Spinbox(LFdatum, from_=1, to=31, width=5, textvar=denVar)
SBden.grid(row=1,column=0, sticky=W+E, padx=3)

lbl = Label(LFdatum, text=u"Měsíc").grid(row=0,column=1)
SBmesic = Spinbox(LFdatum, from_=1, to=12, width=5, textvar=mesicVar)
SBmesic.grid(row=1,column=1, sticky=W+E, padx=3)

lbl = Label(LFdatum, text=u"Rok").grid(row=0,column=2)
SBrok = Spinbox(LFdatum, from_=1900, to=datetime.date.today().year, width=5, textvar=rokVar)
SBrok.grid(row=1,column=2, sticky=W+E, padx=3)

Button(LFdatum, text='Dnes', command=dnes).grid(row=1, column=3, sticky=W+E, padx=10)

### Jmeniny
LFjmeniny = LabelFrame(okno, text='Jmeniny', padx=10, pady=10)
LFjmeniny.grid( sticky=N+S+W+E)
LBLjmeniny =  Label(LFjmeniny, text='karel')
LBLjmeniny.grid( )

### Narozeniny
LFnarozeniny = LabelFrame(okno, text='Narozeniny', padx=10, pady=10)
LFnarozeniny.grid(row=3, column=0, )

LBnarozeniny=Listbox(LFnarozeniny)
LBnarozeniny.grid(row=0, column=0, columnspan=2, sticky=W+E )

jmenoVar = StringVar()
jmenoVar.set('Jméno')
Ejmeno=Entry(LFnarozeniny,textvar=jmenoVar)
Ejmeno.grid(row=1, column=0)
poznamkaVar = StringVar()
poznamkaVar.set('poznámka')
Epoznamka=Entry(LFnarozeniny, textvar=poznamkaVar)
Epoznamka.grid(row=1, column=1)


Button(LFnarozeniny, text='(+) Přidej\nzáznam narozenin', command=pridej).grid(row=2, column=0, sticky=W+E)
Button(LFnarozeniny, text='(-) Odeber\nvybranou osobu', command=odeber).grid(row=2, column=1, sticky=W+E)

`--> stáhnout

Datum

Jednoduchou práci s datem a časem umožňuje modul datetime. Funkce dnes nastaví aktuální datum

def dnes():
    # dnešní datum
    dnes=datetime.date.today()

    denVar.set( dnes.day )
    mesicVar.set( dnes.month )
    rokVar.set( dnes.year )

`--> stáhnout

Jmeniny a pohyb v kalendáři

Načtení souboru s jmenin zajišťuje funkce nactiJmeniny. Funkce čte soubor jmena.txt. Nejprve vyhledá mezeru. Text před mezerou je datum. Text za mezerou je jméno. Datum se rozdělí na den a měsíc podle tečky. Proměnná jmena je slovnik ve kterém jsou obsaženy slovníky. Jako klíče se používá měsíc a den.

Ve funkci jsou dvě vnořené sekce try:, aby bylo možné rozlišit na kterém řádku došlo k chybě a aby byl ignorován právě jen ten jeden řádek, na kterém je chyba.

def nactiJmeniny():
    jmena={}
    try :
        n = 0   # počítadlo řádků
        f = open('jmena.txt','r')
        while 1:
            n += 1
            radek = f.readline()
            if radek == '':
                break
            try:
                m = radek.find(' ') # mezera
                datum = radek[:m]
                jmeno = radek[m:].strip()
                ( den,mesic ) = datum.split('.')
                (den, mesic ) = map(int, (den,mesic) )
                if not jmena.has_key(mesic):
                    jmena[mesic]={}
                if not jmena[mesic].has_key(den):
                    jmena[mesic][den] = jmeno
            except:
                sys.stderr.write('jmena.txt: na řádku {} je chyba\n'.format(n)) 
        f.close()
    except:
        sys.stderr.write('jmena.txt: na řádku je chyba\n')
    return jmena

`--> stáhnout

Dále je třeba pří každé změně data načíst příslušný svátek. Proto zaregistrujeme funkci zmenaData, která se bude volat vždy při změně hodnot Spinboxů.

Funkce změna data musí dále ošetřit různé počty dnů v jednotlivých měsících. Proto se na jejím začátku nejprve správně nastaví meze hodnot Spinboxů. (config(to=...))


def zmenaData(*args):
    ### kontrola a nastavení počtu dnů v měsíci
    # přestupný rok
    rok=int( rokVar.get() )
    unor = 29 if rok % 4 == 0 else 28
    mesic=int( mesicVar.get() )
    if mesic==1 or mesic==3 or mesic==5 or mesic==7 or mesic==8 or mesic==10 or mesic==12:
        SBden.config(to=31)
    elif mesic == 2 :
        SBden.config(to=unor)
        if int(denVar.get())>unor:
            denVar.set(unor)
    else:
        SBden.config(to=30)
        if int(denVar.get())>30:
            denVar.set(30)
    den=int( denVar.get() )
    # vyhlednání narozenin
    try: 
        LBLjmeniny.config(text=jmena[mesic][den])
    except: 
        LBLjmeniny.config(text='CHYBA')
        print "Nelze najít jmeniny -> den:{}, měsíc:{}".format(den,mesic)


...
...

####################################################
### události
denVar.trace('w',zmenaData)
mesicVar.trace('w',zmenaData)
rokVar.trace('w',zmenaData)

`--> stáhnout

Dále je vhodné pro větší komfort ovládání umožnit změnu data pomocí kolečka myši. V Linuxu se kolečko myši chová jako 4. a 5. tlačítko. Na Window nebo Mac je nutné použít událost <MouseWheel>.

def rolovani(event):
    if event.num == 4:
        event.widget.invoke("buttonup")
    if event.num == 5:
        event.widget.invoke("buttondown")

...
...

####################################################
### události

# rolování kolečkem myši
SBden.bind('<Button-4>', rolovani)
SBden.bind('<Button-5>', rolovani)
SBmesic.bind('<Button-4>', rolovani)
SBmesic.bind('<Button-5>', rolovani)
SBrok.bind('<Button-4>', rolovani)
SBrok.bind('<Button-5>', rolovani)

`--> stáhnout

Narozeniny

Narozeniny je nutné ze souboru nejen načíst ale také je tam při změně uložit. Vytvoříme proto dvě funkce nactiNarozeniny a ulozNarozeniny.

Narozeniny se ukládají do datové struktury založené na slovnících.

  1. do slovníku, kde je klíčem měsíc se ukládá slovník se dny
  2. do slovníku, kde je klíčem den se ukládá seznam s jednotlivými osobami, které mají v daný den narozeniny.
  3. do každé položky seznamu se opět slovník s klíči: rok, jmeno a poznamka

Pro tyto údaje:

3.11.1979  Jan Klk: bratr
12.4.1974  Petr Holoubek: známí
12.4.1985 Jan Novák: neznámí
12.4.1979 Tonda Neuman: uhlobaron
13.4.1984  Jaroslv Smykl: hodinář

... budou data vypadat takto


{
 11:
   {
    3: 
       [
        {
         'rok': 1979, 
         'jmeno': 'Jan Klk', 
         'poznamka': 'bratr'
        }
       ]
    }, 
 4: 
   {
    12:
       [
        {
         'rok': 1974, 
         'jmeno': 'Petr Holoubek',
         'poznamka': 'známí'
        },
        {
         'rok': 1985, 
         'jmeno': 'Jan Novák', 
         'poznamka': 'neznámí'
        }, 
        {
         'rok': 1979, 
         'jmeno': 'Tonda Neuman', 
         'poznamka': 'uhlobaron'
        }
       ], 
    13: 
       [
        {
         'rok': 1984,
         'jmeno': 'Jaroslv Smykl', 
         'poznamka': 'hodinář'
        }
       ]
    }
}

`--> stáhnout

Seznam osob pro 12.4. dostaneme jako narozeniny[4][12]. Ke jménu třetí osoby (index 2) v pořadí se dostaneme narozeniny[4][12][2]['jmeno']

def nactiNarozeniny():
    narozeniny={}
    try :
        n = 0   # počítadlo řádků
        f = open('narozeniny.txt','r')
        while 1:
            n += 1
            radek = f.readline()
            if radek == '':
                break
            try: 
                m = radek.find(' ') # mezera
                datum = radek[:m]
                clovek = radek[m:].strip()
                ( den,mesic,rok ) = datum.split('.')
                (den, mesic,rok ) = map(int, (den,mesic,rok) )
                (jmeno, poznamka) = clovek.split(':')
                jmeno = jmeno.strip()
                poznamka = poznamka.strip()
                if not narozeniny.has_key(mesic):
                    narozeniny[mesic]={}
                if not narozeniny[mesic].has_key(den):
                    narozeniny[mesic][den] = []

                narozeniny[mesic][den].append( {} )
                narozeniny[mesic][den][-1]['rok'] = rok
                narozeniny[mesic][den][-1]['jmeno'] = jmeno
                narozeniny[mesic][den][-1]['poznamka'] = poznamka
            except:
                sys.stderr.write('narozeniny.txt: na řádku {} je chyba\n'.format(n))
        f.close()
    except:
        sys.stderr.write('narozeniny.txt: nedaří se číst soubor\n')
    return narozeniny 

`--> stáhnout

Nyní do funkce zmenaData přidáme několik řádků pro přidávání narozenin

    ### Narozeniny
    global narozeniny
    LBnarozeniny.delete(0, END)  # vymažu všechny záznamy
    if narozeniny.has_key(mesic) and narozeniny[mesic].has_key(den):
        for osoba in narozeniny[mesic][den] : 
            vek = datetime.date.today().year - osoba['rok']
            zaznam= "{} let | {} | {}".format(vek, osoba['jmeno'], osoba['poznamka'])
            LBnarozeniny.insert(END,zaznam)

`--> stáhnout

Změny v narozeninách

Ukládání do souboru narozenin bude vypadat asi následovně: (Nebude se samozřejmě zapisovat na stdout, ale do příslušného souboru...)

def ulozNarozeniny():
    for mesic in sorted(narozeniny.keys()): 
        for den in sorted(narozeniny[mesic].keys()): 
            for osoba in narozeniny[mesic][den]:
                sys.stdout.write('{den}.{mesic}.{rok} {jmeno}: {poznamka}\n'.format(
                    **dict({'den':den, 'mesic':mesic}, **osoba) ))

`--> stáhnout

Nyní je již vše nachystáno, aby mohlo fungovat i přidání a odebírání osob: Důležité je, že je třeba přidávat a odebírat z

def pridej():
    den=int( denVar.get() )
    mesic=int( mesicVar.get() )
    rok=int( rokVar.get() )
    jmeno = jmenoVar.get().encode('UTF8')
    poznamka = poznamkaVar.get().encode('UTF8')

    # přidání do seznamu
    vek = datetime.date.today().year - rok
    zaznam= "{} let | {} | {}".format(vek, jmeno, poznamka)
    LBnarozeniny.insert(END,zaznam)

    # přidání do souboru
    global narozeniny
    if not narozeniny.has_key(mesic):
        narozeniny[mesic]={}
    if not narozeniny[mesic].has_key(den):
        narozeniny[mesic][den] = []
    narozeniny[mesic][den].append( {} )
    narozeniny[mesic][den][-1]['rok'] = rok
    narozeniny[mesic][den][-1]['jmeno'] = jmeno
    narozeniny[mesic][den][-1]['poznamka'] = poznamka

    ulozNarozeniny()


def odeber():
    den=int( denVar.get() )
    mesic=int( mesicVar.get() )
    rok=int( rokVar.get() )
    jmeno = jmenoVar.get().encode('UTF8')
    poznamka = poznamkaVar.get().encode('UTF8')

    # odeber ze seznamu
    LBnarozeniny.delete(LBnarozeniny.index(ACTIVE))

    # odeber ze souboru 
    global narozeniny
    if narozeniny.has_key(mesic) and narozeniny[mesic].has_key(den):
        narozeniny[mesic][den].pop( LBnarozeniny.index(ACTIVE) )

    ulozNarozeniny()

`--> stáhnout

Chybové stavy

Chyby v práci se souborem jsou již ošetřeny. Zbývá ještě ošetřit vložení nečíselných znaků do políček data. Já přidal do funkce zmenaData následující kód. Je samozřejmě zapotřebí udělat to stejné i pro měsíc a den:

    try :
        rok=int( rokVar.get() )
    except :
        SBrok.delete( SBrok.index(INSERT) - 1 )
        rok=int( rokVar.get() )

`--> stáhnout

| navigace |

Licence Creative Commons Valid XHTML 1.0 Strict Valid CSS! Antispam.er.cz Blog: Tlapicka.net