15 domowy NAS

Niedawno zakupiłem do domu mały serwer NAS firmy Synology. W tym wpisie podzielę się pierwszymi wrażeniami.



Urządzenie to Synology DS115j które umożliwiające podłączenie jednego dysku twardego.
Koszt tego urządzenia to 370zł. Cena modelu DS216se posiadającego porty na dwa dyski to 600zł.

Model droższy umożliwia ustawienie dysków w trybie RAID co gwarantuje bezpieczeństwo danych w przypadku awarii dysku. Dopłata 230zł wydawała się jednak za duża, tym bardziej, że reszta parametrów urządzeń jest taka sama (procesor 800Mhz i 256MB RAM).

Bezpieczeństwo danych postanowiłem zapewnić podłączając pendrive na usb i ustawiające regularny backup ważnych folderów realizowany przez jedną z aplikacji Synology.


Pośród wielu aplikacji dostarczanych przez producenta przetestowałem:

WebStation:

aplikację dzięki której na naszym NASie postawimy stronę www.

Jeśli posiadamy stały publiczny adres IP wystarczy na routerze nadać stały adres w sieci domowej naszemu urządzeniu NAS i przekierować port 80 na ten adres (w moim przypadku NAS w sieci domowej posiada adres 192.168.0.182 i na ten adres przekierowałem porty)

Na przykładowym zdjęciu przekierowane są dodatkowo porty:
ftp - 21
do panelu NAS - 5000
smp (samba ) - 137,138,139, 445



Teraz wystarczy wpisać nasze publiczne ip w wyszukiwarce i przykładowa strona od razu działa.
Chyba najprostszy sposób postawienia własnego serwera www. Na pewno prościej niż konfigurować Apache samemu na pc bądź raspberry pi.

FTP:
Kolejna funkcjonalność to serwer FTP, tutaj też wszystko od razu działało, jednak o ile ściąganie danych z NAS (mówimy tutaj o ściąganiu z poza domu czyli połączenie ftp z internetu poprzez nasz publiczny ip do serwera NAS) działało z szybkością domowego łącza (upload z NAS do netu) o tyle wysyłanie plików z internetu na NAS które powinno być jeszcze szybsze (domowe łącze otrzymuje dane jako download ) odbyło się z prędkością 5 razy wolniejszą niż możliwości łącza. Nie wiem czemu.

SMP
Dlatego otworzyłem porty na routerze na udostępnianie poprzez sambę (smp) czyli to co zwykle w Windows wykorzystujemy do łączności i dzielenia zasobów z drugim komputerem w sieci lokalnej:

wpisując w eksploratorze plików  \\192.168...

...co wyświetla nam udostępnione dyski, foldery drukarki innego komputera.

ewentualnie  \\192.168...\c$  jeśli żaden dysk wybranego komputera  nie jest udostępniony a chcemy dostać się do jego dysku "c"


w naszym przypadku po otwarciu portów na routerze na każdym komputerze w Internecie z Windowsem wpisujemy w oknie eksploratora plików

\\nasz.publiczny.ip

i widzimy wszystkie udostępnione foldery naszego NASa.

Teraz kopiowanie danych w obie strony odbywało się z pełną prędkością domowego łącza internetowego.
Dla fanów linuksa możemy zestawić NFS a dla Maca - AFP



Tutaj mała uwaga, domową sieć warto mieć opartą o router z wbudowanym switchem obsługującym porty 1Gb. Przepustowość takiej sieci to około 100MB na sekundę a to pozwala przesyłać dane pomiędzy komputerem a dyskiem twardym NAS'a z pełną prędkością dysku twardego czyli około 50MB/s. Jeśli chcemy korzystać z połączenia WiFi to router i komputer powinien obsługiwać standard IEEE 802.11ac  czyli 5Ghz gwarantujący 800Mb/s.

Podłączenie NAS do sieci kablowej opartej na standardzie 100Mb czyli około 10MB/s pięciokrotnie ograniczy przesył danych pomiędzy NAS a komputerem. Korzystanie z WiFi 802.11g – czyli 54 Mb/s która w praktyce przesyła 22Mb/s w jedną stronę (tylko 2,5MB/s !!!) sprawi, że komfort pracy w domu z danymi zgromadzonymi na NASie będzie tragiczny.

Temat nie dotyczy tych którzy wykorzystują NAS głównie poprzez internet, wtedy wąskim gardłem pozostaje prędkość łącza często i tak niższa niż 100Mb.


DLNA
Ponieważ posiadam telewizor ze smartTV liczyłem na możliwość odtwarzania filmów prosto z NAS'a bez konieczności ich przegrywania na dysk który podłączam do TV (przesada wiem ;-)

Faktycznie TV od razu wykrył urządzenie nadające identyfikator DLNA i pozwolił odtwarzać filmy z katalogi "video" NAS'a. Nie rozumiem tylko dlaczego nie działa opcja przewijania pilotem takiego filmu. Można jedynie za pomocą podłączonej do TV myszki wskazać na linii upływu czasu miejsce odtwarzania.



OS
Po przekierowaniu portu 5000 możemy dostać się z zewnątrz do systemu operacyjnego Synology,
gdzie po zalogowaniu możemy korzystać z zainstalowanych aplikacji. Dzięki czemu praktycznie na dowolnym obcym komputerze w internecie po połączeniu uzyskujemy dostęp do swojego środowiska i danych.



Firma Synology już w najniższych modelach udostępnia całą masę ciekawych aplikacji od businesowych (backup, office, synchronizowanie danych z chmur, radius server, zapis z kamer ip)
przez multimedialne (serwer multimediów, iTunes, audio, video )
po aplikacje dla developerów (java, apache, node.js, perl, php, python, ruby)
kończąc na narzędziach typu directory server, dns server, git server, vpn server, mail server, web server.

spis aplikacji


Nie testowałem urządzeń innych producentów ale Synology w niskiej cenie oferuje urządzenie o wysokiej funkcjonalności i przyjemności korzystania

Moje pierwsze wrażenie jest bardzo pozytywne dlatego jeśli rozważałeś zakup NAS'a polecam :-)


Konfigurowanie:

instalacja Django:

1) Podłączyć się przez terminal SSH do NAS'a  (PuTTY)
2) uzyskać uprawnienia root wpisując
    $ sudo -i
    Password: ........
3) następnie:

UPDATE For DSM 6.0 & python3.5
python3.5 -m ensurepip
python3.5 -m pip install flask
python3.5 -m pip install Django
4) można też zrobić upgrade pip:
python3.5 -m pip install --upgrade pip

5) rozpoczęcie projektu:

python3.5 -m django startproject mysite






14 GUI okienkowe II

Leksykon Widżetów:

sposób konstrukcji
w=Widget(master, option,...)
gdzie:
master - wskazanie okna rodzica (osadzenie)
option - opcje

1) Button:
b=Button(okno,opcja,...)

własności (czyli opcja)
command - funkcja wywoływana w momencie kliknęcia (callback)
justify - wyrównywanie tekstu wewnątrz  przycisku (LEFT, CENTER, RIGHT)
state - (DISABLED - dezaktywuje, NORMAL)*

* - uwaga button zdeaktywowany nie będzie wywoływał callbacka tylko jeśli został on
ustawiony poprzez polecenie command.
b=Button(okno,command=funkcjacallback)
b.place(x=0,y=0)
Jeśli powiązanie z funcją callback zostało stworzone drugą metodą czyli bind (tu reakcja na mysz)
b=Button(okno, text="przycisk")
b.place(x=0,y=0)
b.bind('<Button-1>', funkcjacallback)
to nawet zdeaktywowany button będzie reagował na kliknięcie myszy <Button-1>

metody:
flash() - migotanie przycisku
invoke() - wywołuje callbacka przypisanego do przycisku (jedyny dozwolony sposób wywołania)


2) Checkbutton - zaznaczanie ...ptaszkiem
c=Checkbutton(okno, opcja,...)

własności:
bd - szerokość ramki (domyślnie 2)
command -  callback (gdy checkbutton ZMIENIA STAN)
justify - wyrównywanie tekstu wewnątrz  przycisku (LEFT, CENTER, RIGHT)
variable - zmienna śledzona, stan widżetu( unchecked IntVar =0, checked = 1)
offvalue - domyślna wartośc wpisywana do zmiennej gdy stan unchecked
onvalue - domyślna wartośc wpisywana do zmiennej gdy stan checked
state - (DISABLED - dezaktywuje, NORMAL)*

metody:
deselect() - ustawia w stan unchecked
select() - ustawia w stan checked
flash() - mruga bez zmiany stanu
invoke() - wywołuje callbacka przypisanego
toggle() - zmienia stan widżetu na przeciwny

dla przypomnienia:

Funkcja a Metoda:

funkcja(argument)           otrzymuje argument, zwraca wynik

dana.metoda(argument)     otrzymuje argument, modyfikuje daną

czyli po stworzeniu Checkbuttona pod zmienną 'c' metodę wywołujemy:

c.flash()


przykład:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from tkinter import *
from tkinter import messagebox

def licz():
   global licznik
   licznik+=1

def pokaz():
   global c,przel,licznik
   messagebox.showinfo("","licznik="+str(licznik)+",zmienna="+str(przel.get()))


o = Tk( )
przel=IntVar()
licznik=0
b=Button(o,text="Pokaż",command=pokaz)
b.pack()
c=Checkbutton(o,text="Fifek",variable=przel,command=licz)
c.pack()
o.mainloop()



przykład użycia metody toggle()

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from tkinter import *

def przelacz():
   global c
   c.toggle()

o = Tk( )
stan=IntVar()
licznik=0
b = Button(o,text="Przelacz",command=przelacz)
b.pack()
c = Checkbutton(o,text="Fifek",variable=stan)
c.pack()
o.mainloop()


3) Radiobutton
r=Radiobutton(okno,opcja,...)

własności:
command - callback gdy zmienia stan
justify - wyrównywanie tekstu wewnątrz  przycisku (LEFT, CENTER, RIGHT)
state - (DISABLED - dezaktywuje, NORMAL)*
variable - zmienna śledzona, uwaga to przypisanie do konkretnej zmiennej grupuje przyciski !!!
wskazuje który z przycisków jest wybrany (może być IntVar albo StringVar)
value - unikalna wartość przypisana do każdego przycisku

metody:
deselect() - ustawia unchecked (żaden wybrany oznacza wyszarzona cała grupa)
flash() - kilkukrotnie mruga
invoke() - wywołuje callback
select() - ustawia checked

przykład:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from tkinter import *

def cmdr1():
    global r1,r2
    r2.set(r1.get())
def cmdr2():
    global r1,r2
    r1.set(r2.get())

o = Tk( )
r1 = IntVar()
r11 = Radiobutton(o,text="słońce",variable=r1,value=1,command=cmdr1)
r11.select()
r11.pack()
r12 = Radiobutton(o,text="deszcz",variable=r1,value=2,command=cmdr1)
r12.pack()
r2 = IntVar()
r21 = Radiobutton(o,text="północ",variable=r2,value=2,command=cmdr2)
r21.pack()
r22 = Radiobutton(o,text="połódnie",variable=r2,value=1,command=cmdr2)
r22.select()
r22.pack()
o.mainloop()


Widżety tekstowe:

Label: umieszcza tekst, nie obsługuje callbacka (command) choć może za pomocą bind
L=Label(okno, opcja)

własności:
tekst="tekst" - napis etykiety
textvariable - zmienna śledzona wyświetlana jako tekst

metody:
brak


Message: jak Label ale formatuje długi tekst
m=Message(okno,opcja)

własności:
jak Label

metody:
brak



Widżety edycyjne:

Entry: jednowierszowe pole edycyjne
e=Entry(okno, opcja,...)

określanie pozycji w polu tekstowym:
liczba - tak jak indeksowanie łańcuchów
ANCHOR - pozycja początkowa zaznaczenia
END -  pozycja końcowa zaznaczenia
INSERT - położenie kursora tekstowego

własności:
nie umożliwia dowiązania callbacka(command)
exportselection - domyślnie1, zaznaczony tekst przenoszony jest do schowka
show - znak który wyświetla podczas wpisywania np * podczas wpisywania hasła
state - (DISABLED - dezaktywuje, NORMAL)
textvariable - zmienna śledzona (StringVar) odzwierciedlająca stan pola edycji
with - szerokośc pola w znakach

metody:
delete(first,last) - kasowanie, tylko pierwszy kasuje 1 znak
get() - pobiera zawartosc pola jako tekst
icursor(index) - ustawia kursor w pozycji index
index(index,s) - wstawia do pola łańcuch s od pola index
select_clear() - usuwa zaznaczenie
select_from(index) - ustawia zaznaczenie od index
select_present() - informuje czy w polu jest aktywne zaznaczenie (True/False)
select_range(start,end) - zaznaczenie od start do end


przykład, dzięki zmiennej stowarzyszonej pozwalamy tylko na cyfry:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from tkinter import *

def tylkocyfry(*a):
    global tx,pop_s
    s = tx.get()
    if s == '' or s.isdigit():
        pop_s = s
    else:
        tx.set(pop_s)

pop_s = ''
o = Tk( )
tx = StringVar()
tc = o.register(tylkocyfry)
e = Entry(o,textvariable=tx)
tx.set(pop_s)
tx.trace('w',tylkocyfry)
e.pack()
e.focus_set()
o.mainloop()



Widżety organizacyjne:

Frame: wydziela obszar organizacyjny, ma własne współrzędne, można do niej przypinać widżety
f=Frame(okno, opcje,...)

właśności:
takefocus- pozwala ramce przyjąć skupienie jeśli ma wartość 1

przykład:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from tkinter import *

o = Tk( )
f1 = Frame(o,width=200,height=100,bg='white')
f2 = Frame(o,width=200,height=100,bg='yellow')

b11 = Button(f1,text="przycisk 1 w ramce 1")
b12 = Button(f1,text="przycisk 2 w ramce 1")
b21 = Button(f2,text="przycisk 1 w ramce 2")
b22 = Button(f2,text="przycisk 2 w ramce 2")

b11.place(x=10,y=10)
b12.place(x=10,y=50)
b21.grid(column=0,row=0)
b22.grid(column=1,row=1)

f1.pack()
f2.pack()
o.mainloop()


Menu:   menu okienka 
wybór opcji może spowodować:
-wykonanie callbacka
-rozwinięcie podmenu

utworzenie:
tworzymy obiekt menu
dowiązujemy do okna
dowiązanie podmenu (cascade) lub akcji (command) do menu głównego

m=Menu(okno, opcja,...)

własności:
postcommand - callback wywoływany gdy menu uaktywnione
tearoff - ustawione na 0 (zero) wyłącza separatory oddzierania (obecnie nieużywane)

metody:
add_cascade(coption,....) - dodaje kaskadę do menu
add_command(coption,...) - dodaje callbacka
add_separator() - dodaje separator
entryconfig(i,coption,...) - meodyfikuje właśność i-tej pozycji menu

option:
accelerator - wyświetla obok opcji klawisz skrótu (tylko wyświetla nie uruchamia go)
columnbreak - użycie wartości 1 spowoduje pojawienie się wskazanej opcji w nowej kolumnie
state - ustawiona na DISABLED uniemożliwia wybranie opcji, ACTIVE


przykład:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from tkinter import *
from tkinter import messagebox

def OAplikacji():
    messagebox.showinfo("Aplikacja","Wspaniała aplikacja,\nktóra nic nie robi")
    
okno = Tk()
# tworzymy menu główne
menuglowne = Menu(okno)
okno.config(menu=menuglowne)

# tworzymy podmenu (na razie puste)
podmenuplik = Menu(menuglowne)
menuglowne.add_cascade(label="Plik",menu=podmenuplik)

# dowiązujemy akcję do pozycji menu
podmenupomoc = Menu(menuglowne)
menuglowne.add_command(label="O aplikacji",command=OAplikacji)

okno.mainloop()


by dodać do podmenu Plik odwołanie do callbacka pytającego o zamknięcie programu:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from tkinter import *
from tkinter import messagebox

def OAplikacji():
    messagebox.showinfo("Aplikacja","Wspaniała aplikacja,\nktóra nic nie robi")

def CzyKoniec():
    global okno
    if(messagebox.askyesno("","Na pewno chcesz zakończyć pracę?")):
        okno.destroy()
        
okno = Tk()
menuglowne = Menu(okno)
okno.config(menu=menuglowne)
podmenuplik = Menu(menuglowne)
menuglowne.add_cascade(label="Plik",menu=podmenuplik,underline=0)

# dowiązujemy akcję do podmenu Plik
podmenuplik.add_command(label="Koniec",underline=0,command=CzyKoniec)

podmenupomoc = Menu(menuglowne)
menuglowne.add_command(label="O aplikacji",underline=2,command=OAplikacji)
okno.mainloop()


Jeśli chcesz się pozbyć oddzieracza z menu plik dodaj

1
podmenuplik = Menu(menuglowne,tearoff=0)

Jeśli chcemy umożliwić na korzystanie z Menu za pomocą klawiszy  - underline + index litery

1
2
# ustawiamy skrót klawiszony na Alt-P
menuglowne.add_cascade(label="Plik",menu=podmenuplik,underline=0)

By dodać do podmenu Plik kolejną pozycję np Otwórz

1
podmenuplik.add_command(label="Otwórz...",underline=0,command=Otworz)

Możesz też dodać separator w podmenu:

1
podmenuplik.add_separator()


Messagebox:
to nie widżet a klasa z metodami, metody mają trzy parametry:
tytuł - napis wyświetlany na górnej dekoracji okna
wiadomość - napis wnętrza okna \n odziela wiersze
opcje (nieobowiązkowe)
default - ustawienie odpowiedzi domyślnej: CANCEL, IGNORE, OK, NO, RETRY, YES
icon - ustawienie ikony: ERROR, INFO, QUESTION, WARNING
parent - można przypisać do innego okna niż główne

metody
askyesno(tytuł,wiadomośc,opcje) - pyta tak/nie zwraca True/False
askokcancel(tytuł,wiadomośc,opcje) - pyta ok/cancel
askretrycancel(tytuł,wiadomośc,opcje) - pyta ponów/anuluj
askquestion(tytuł,wiadomośc, opcje) - pyta tak/nie zwraca "yes" i "no"
showerror(tytuł,wiadomość, opcje) - informuje o błędzie
showinfo(tytuł,wiadomość, opcje) - pokazuje informacje
showwarning(tytuł,wiadomośc,opcje) - pokazuje ostrzeżenie



messagebox.askyesno(tytuł,wiadomość,opcje)
okienko zapyta o tak/nie, zwraca True na tak i False na nie.

przykład:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from tkinter import *
from tkinter import messagebox

def pytanie():
    odpowiedz = messagebox.askyesno("?","Być albo nie być?")
    print(odpowiedz)
    
okno = Tk()
b = Button(okno, text="Zadaj pytanie!",command=pytanie)
b.pack()
okno.mainloop()



Kształtowanie okna głównego:


zmiana tytułu okna głównego
metoda
title()

zmiana rozmiaru okna głównego
metoda
geometry()
jej argumentem jest napis w postaci "szerokość x wysokość"

określanie minimalnej wielkości okna
metoda
minsize()

maksymalnej
metoda
maxsize()

przykład

1
2
3
4
5
6
from tkinter import *

okno = Tk()
okno.minsize(width=250, height=200)
okno.geometry("500x500")
okno.mainloop()

blokowanie wielkości okna:
metoda
resizable() - i za pomocą True False określamy które parametry blokujemy

przykład:

1
2
3
4
5
6
from tkinter import *

okno = Tk()
okno.resizable(width=False, height=True)
okno.geometry("200x200")
okno.mainloop()



Przechwytywanie zdarzenia zamknięcia okna głównego:
metoda
okno.protocol("WM_DELETE_WINDOW",callback)


13 GUI okienkowe

Tkinter

import modułu dla ułatwienia nadajemy mu skrót 'tk'

1
import tkinter as tk

możemy importować również tak

1
from tkinter import *
wtedy możemy wywoływać konstruktory bez poprzedzania ich tkinter bądź tk. (wyklucza konieczność kwalifikacji)
Poniżej będą jednak wywoływane z użyciem skrótu 'tk'.


Pierwszy konstruktor Tk, który tworzy obiekt okna głównego

1
2
3
4
import tkinter as tk

okienko=tk.Tk()
okienko.mainloop()

metoda okienko.mainloop() to uruchomienie pętli głównej (kontrolera zdarzeń)  dzięki której okienko cały czas reaguje na nasze akcje.




Nazwa okienka i przycisk Button:

Metoda title klasy Tk ustawia tytuł okna.
1
2
3
4
5
6
7
import tkinter as tk

okienko=tk.Tk()
okienko.title("Okienko")
guzik=tk.Button(okienko , text="Hej")
guzik.place(x=15, y=15)
okienko.mainloop()

konstruktor klasy Button tworzy obiekt nazwany 'guzik' reprezentujący przycisk
guzik=tk.Button(okienko , text="Hej")
pierwszym argumentem "okienko" jest nazwa obiektu okna do którego widżet będzie należeć

metoda place() klasy Button umieszcza przycisk na wskazanej pozycji (w pikselach)





Tworzenie pierwszego callbacka:

Callback to bezparametrowa funkcja wywołana przez kontroler zdarzeń po określonej akcji.

Tworzymy prosty callback 'Click'  który zapewnia sobie dostęp do globalnej zmiennej okienko i poprzez metodę destroy() zamyka okienko po czym dowiązujemy go do widżetu Button (parametr command)

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import tkinter as tk

def Click():
    global okienko
    okienko.destroy()



okienko=tk.Tk()
okienko.title("Okienko")
guzik=tk.Button(okienko , text="Hej", command=Click)
guzik.place(x=15, y=15)
okienko.mainloop()


dodatkowo możemy uzupełnić callbacka o zapytanie czy chce wyłączyć okienko. Za pomocą klasy messagebox wyświetlamy komunikat pytania. Klasa wymaga zaimportowania:

1
2
3
4
5
6
7
8
import tkinter as tk
from tkinter import messagebox

def Click():
    global okienko
    odp = tk.messagebox.askquestion("Pytanie","Jesteś pewień?")
    if odp=='yes':
        okienko.destroy()




Rozmieszczanie widżetów:

Geometry menager Tkintera oferuje 3 metody rozmieszczania widzętów w oknie

1)   place()  - z dokładnością do piksela

2)   pack() - automatycznie jeden za drugim

3)   grid() - półautomatycznie w siatce z wyrównaniem



1) place(x,y)  - (pozioma, pionowa) start w lewym górnym rogu, dodatkowo możemy dodać parametry szerokości i wysokości przycisku

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import tkinter as tk

okno=tk.Tk()

g1=tk.Button(okno, text="Guzik1")
g2=tk.Button(okno, text="Guzik2")
g3=tk.Button(okno, text="Guzik3")

g1.place(x=5,y=5,width=120)
g2.place(x=35,y=35,height=60)
g3.place(x=85,y=85,)

okno.mainloop()

wynik:


Nie potrzebujemy tworzyć zmiennych w których przechowywane są widżety jeśli dostęp do niech nie będzie później potrzebny:

1
2
3
4
5
6
7
8
9
import tkinter as tk

okno=tk.Tk()

tk.Button(okno, text="Guzik1").place(x=5,y=5,width=120)
tk.Button(okno, text="Guzik2").place(x=35,y=35,height=60)
tk.Button(okno, text="Guzik3").place(x=85,y=85,)

okno.mainloop()




2) pack(side.tk.opcje)

opcje:
TOP - ku górze okna macierzystego (Domyślne)
BOTTOM - ku dołowi
LEFT
RIGHT


3) grid(opcje_siatki)

row - rząd
column - kolumna
columnspan - liczba kolumn zajetych przez widzet
rowspan - liczba wierszy zajetych przez widżet



Zdarzenia 

Widżety które nie mają parametru command (wywołanie callbacka, przeważnie te nie klikalne) mają metodę bind.

1
widzet.bind(zdarzenie,callback)

która po określonym zdarzeniu na widżecie wywoła callbacka.

Przykładowe zdarzenia:

<Button-1> pojedyncze kliknięce lewym przyciskiem myszy
<Button-2> pojedyncze kliknięcie środkowym przyciskiem myszy (o ile jest dostępny)
<Button-3> pojedyncze kliknięcie prawym przyciskiem myszy (o ile jest dostępny)
<ButtonRelease-1> zwolnienie lewego przycisku myszy
<DoubleButton-1> podwójne kliknięcie lewego przycisku myszy
<Enter> kursor myszy pojawił się nad widżetem
<Leave> kursor myszy opuścił obszar widżetu
<Focus-In> widżet (albo jego dziecko) przejął skupienie
<Focus-Out> widżet (albo jego dziecko) stracił skupienie
<Return> użytkownik nacisnął klawisz Enter
<Key> użytkownik nacisnął jakiś klawisz
<KeyRelease> zwolnienie klawisza klawiatury

Uwaga callback dla parametru command powinien być funkcją bezparametrową, natomiast dla metody bind() powinien być funkcją z jednym parametrem. Żeby ominąć ten problem i nie pisać osobnych funkcji dopisujemy w parametrze funkcji (zdarz=None)

1
2
def clik(zdarz=None):
tk.messagebox.showinfo("Nazwa","klikasz sobie")


Można bind ale można i unbind. Istnieje też metoda bind_all oraz unbind_all.



Zmienne śledzone

Tkinter ma zestaw klas które tworzą zmienne pozwalające nadzorować zmiany ich wartości.
Niektóre z widżetów używają tych zmiennych do sygnalizowania zmiany stanu lub wpływania na ich stan.

• BooleanVar zmienna logiczna
• DoubleVar zmienna rzeczywista
• IntVar zmienna całkowita
• StringVar zmienna napisowa (łańcuchowa)

zmienną śledzoną należy utworzyć przed pierwszym użyciem

1
napisik=StringVar()



Wpisanie wartości DO zmiennej śledzonej jest możliwe tylko przez metodę set()

1
napisik.set("To właśnie przypisaliśmy")



Odczytanie wartości ZE zmiennej jest możliwe tylko przez metodę get()

1
cotamjest=napisik.get()




do istniejące zmiennej śledzonej można dodać obserwatora za pomocą metody trace()

1
obsid = zmienna.trace(tryb_śledzenia, obserwator)


do 'obsid' zostanie zapisany identyfikator obserwatora

 tryb_śledzenia to:
 "r" jeśli chcemy śledzić odczyty ze zmiennej
 "w" jeśli chcemy śledzić zapisy do zmiennej
 "u" jeśli chcemy śledzić unicestwianie zmiennej

obserwator to funkcja, która zostanie wywołana, jeśli zajdzie któreś
ze śledzonych zdarzeń
obserwator powinien być zadeklarowany jako funkcja z trzema
parametrami:

1
def obs(id,ix,act):

• id – wewnętrzny identyfikator zmiennej (nieprzydatne)
• ix – w TkInter zawsze pusty łańcuch
• act – znak informujący, jaka akcja spowodowała wywołanie
obserwatora ('r', 'w', 'u')

 jeśli żaden z tych argumentów nie jest nam potrzebny, obserwator może być zadeklarowany tak:

1
def obs(*a):




12 Moduły Pakiety zapis

Moduły

Większy program warto podzielić na moduły. W module możemy umieścić pojedynczą klasę lub funkcję, lub wiele klas i funkcji. Kryterium podziału powinna być logika, podobne razem.

tworzymy plik przyklad.py z jedną klasą w module

i importujemy:

1
2
3
import przyklad

from przyklad import Nazwaklasy

ewentualnie:

1
from przyklad import *

Taki sposób importu umożliwia nie odwoływanie się do nazwy moduły przy wywoływaniu metod.


Można nadać importowanemu modułowi

1
import nazwamodulu as naszanazwamodulu

lub klasie naszą własną nazwę którą będziemy wywoływać:

1
from osobaModule import Osoba as Naszaosoba




Pakiety

Kolekcja modułów w katalogu.
Żeby umożliwić import z katalogu, musi zawierać plik __init__.py (może być pusty)

importowanie z poza katalogu:

1
import nazwapakietu.nazwamodulu

lub:

1
from nazwapakietu.nazwamodulu import NazwaKlasy


importowanie z katalogu w którym jest pakiet

1
from .nazwamodulu import NazwaKlasy


i z katalogu wyżej:

1
from ..nazwamodulu import NazwaKlasy



Zapis i odczyt pliku.

zapis tradycyjny:

1
2
3
4
5
6
7
def zapis(self):
        print("zapisuje plik o nazwie: ",self.nazwa,".txt")
        plik=open(str(self.nazwa)+".txt","w")
        try:
            plik.write("tresc zapisu może być zmienna z zawartością")
        finally:
            plik.close()


odczyt tradycyjny (z wrzuceniem w listę i oddzieleniem średnikami)

1
2
3
4
5
6
7
def odczyt(self):
        plik=open(str(self.nazwa)+".txt")
        try:
            tekst=plik.read()
        finally:
            plik.close()
        lista88=tekst.split(";")


odczyt i zapis za pomocą modułu pickle (nie trzeba zamykać i otwierać pliku)

1
2
3
4
5
6
7
8
9
import pickle
x="Dzień Dobry"

with open("plik.bin" , "wb" ) as file :
    pickle.dump(x ,file)
with open("plik.bin" , "rb" ) as file :
    z=pickle.load(file)

print(z)

do pliku plik.bin zapisaliśmy tekst ze zmiennej 'x' następnie do zmiennej 'z' odczytaliśmy z pliku ten tekst



11 Programowanie Obiektowe III

Dziedziczenie 


1
2
class Pochodna(Bazowa):
      pass

Klasa bazowa nazywana jest parent, superclass, base class.
Klasa pochodnia nazywana jest subclass, derived.


Przykład, na bazie klasy Czlowiek, tworzone są dwie klasy pochodne Dorosly i Dziecko:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Czlowiek:
    def __init__(self,imie,nazwisko):
        self.imie=imie
        self.nazwisko= nazwisko

class Dorosly(Czlowiek):
    pass

class Dziecko(Czlowiek):
    pass


osoba1=Dorosly("Zenon","Domyslny")
osoba2=Dziecko("Zenonek","Niedomyślny")

print(osoba1.imie)
print(osoba2.nazwisko)
wynik:

Zenon
Niedomyślny

klasy Dorosly i Dziecko  dziedziczą z superklasy w całości metodę __init__ i nie mają swoich metod.



Nadpisywanie metod klasy bazowej


W klasie pochodnej Dorosly, możemy nadpisać metodę __init__.
Jak widać nowa metoda ma dodany atrybut 'pseudonim'.
Żeby się nie powtarzać przypisanie zmiennych imię i nazwisko możemy skopiować z superklasy za pomocą polecenia:

super().__init__(imie,nazwisko)

Przykład:
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Czlowiek:
    def __init__(self,imie,nazwisko):
        self.imie=imie
        self.nazwisko= nazwisko

class Dorosly(Czlowiek):
    def __init__(self,imie,nazwisko,pseudonim):
        super().__init__(imie,nazwisko)
        self.pseudonim=pseudonim



osoba1=Dorosly("Zenon","Domyslny","Banita")

print(osoba1.pseudonim)

nadpisywać i wywoływać przez super można metody, ale nie specjalne.


Mamy możliwość dziedziczenia z typów wbudowanych, jak lista słownik itp.

Na bazie list stwórzmy własną klasę i dodajmy jej metodę znajdującą  średnią:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class MojaLista(list):
    
    def srednia(self):
        wynik=sum(self)
        wynik=wynik/len(self)
        return wynik


x=MojaLista( )
x.append(1)
x.append(2)
x.append(3)

print(x.srednia())

wynik:
2



Argument funkcji *args

Służy do przekazywania dowolnej liczby nie nazwanych parametrów do funkcji.

1
2
3
4
5
6
7
def too ( arg1 , arg2 ,*argv ):
    print ( "normalne argumenty:" , arg1 )
    for arg in argv :
        print ( "parametr *argv :" , arg )
    print ( "normalne argumenty:" , arg2 )

too("Dzień", "Jak", 1,2,3,4,"Codzień" )

Wynik:
normalne argumenty: Dzień
parametr *argv : 1
parametr *argv : 2
parametr *argv : 3
parametr *argv : 4
parametr *argv : Codzień
normalne argumenty: Jak



Dziedziczenie wielobazowe

Jest możliwe dziedziczenie jednoczesne z dwóch klas:

1
class Jakas(Domek,Mieszkanie) :




Agregacja

Nowa klasa powstaje przez użycie obiektów klas już istniejących.

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Reka:
    def __init__(self,ktora):
        self.ktora=ktora
        
class Noga:
    def __init__(self,ktora):
        self.ktora=ktora

class CialoSamo:
    def __init__ (self):
        self.lewa_reka=Reka("LEWA" )
        self.prawa_reka=Reka("PRAWA" )
        self.lewa_noga=Noga("LEWA" )
        self.prawa_noga=Noga("PRAWA" )

cialo = CialoSamo ( )

print(cialo.lewa_reka.ktora)
wynik:

LEWA

Obiekt ciało składa się z argumentów które w rzeczywistości są obiektami klas Reka i Noga.



.