Tkinter - TreeWidget
 x   TIP: Přetáhni ikonu na hlavní panel pro připnutí webu

Tkinter - TreeWidgetTkinter - TreeWidget

 
Hledat
Moderní platforma pro vytvoření vašeho nového webu – Wix.com.
Nyní už můžete mít web zdarma.
Vybavení pro Laser Game
Spuštěn Filmový magazín
Laser Game Brno
Laser Game Ostrava

Tkinter - TreeWidget

Google       Google       3. 12. 2007       12 282×

Vyrobíme si udělátko, které není součástí Tkinteru ani Pmw.

Reklama
Reklama

V dnešní lekci si vyrobíme udělátko TreeWidget. Jedná se o takový rozevírací seznam. Jeho konstrukce je celkem složitá, takže tento díl kurzu je určen spíše pokročilejším pythonýrům. A takto bude náš výtvor vypadat:

Základ třídy

Budeme potřebovat dva obrázky: minus.gif a plus.gif. Nějak takto by měla vypadat kostra třídy.

# -*- coding: cp1250 -*-
from Tkinter import*
class Tree(Frame):
    def __init__(self, parent=None):
        self.i_plus=PhotoImage(file="plus.gif")
        self.i_minus=PhotoImage(file="minus.gif")
        self.okno=self
        Frame.__init__(self,parent)
        ram=Frame(self)
        ram.pack(fill=BOTH, expand=1)
        scy=Scrollbar(ram)
        scy.pack(fill=Y,side=RIGHT)
        scx=Scrollbar(ram,orient="horizontal")
        scx.pack(fill=X,side=BOTTOM)
        self.platno=Canvas(ram,bg='white',yscrollcommand=scy.set,xscrollcommand=scx.set)
        self.platno.pack(fill=BOTH, expand=1,side=LEFT)
        scy["command"]=self.platno.yview
        scx["command"]=self.platno.xview
        #
        

        
        #
        methods = Pack.__dict__.keys()
        methods = methods + Grid.__dict__.keys()
        methods = methods + Place.__dict__.keys()
        for m in methods:
            if m[0] != '_' and m != 'config' and m != 'configure':
                setattr(self, m, getattr(self, m))
if __name__ == "__main__":
    okno=Tk()
    t=Tree(okno)
    t.pack()
    mainloop()

Kód, který je již součástí třídy, je samotné GUI programu a poslední část se stará o to, abychom mohli použít grid,pack a place.

Systém ukládání hodnot

Než se pustíme do samotné realizace projektu, měli bychom vymyslet nějaký způsob, jakým budeme vkládat hodnoty do udělátka. Každá hodnota bude mít vlastní id. Tyto idéčka budeme ukládat do slovníku. Druhou stranu slovníku bude tvořit seznam, do kterého budeme ukládat následující hodnoty: id, nadřazené id, text a status. První tři hodnoty jsou snad jasné a hodnota status bude reprezentovat, jestli je daná větev rozbalená (open) nebo zavřená (close). Nyní bychom již mohli udělat část metody insert:

def insert(self, kam,co):
    self.id=self.id+1#ve funkci __init__ musíme vytvořit proměnnou id=0
    self.slovnik[self.id]=[self.id,kam,co,"close"]#Standardně budou všechny větve zavřené 
    return self.id

Jak vidíte, metoda insert vrací self.id. Doufám, že již všichni pochopili, proč to tak je, ale raději to vysvětlím. Budeme tak vytvářet různé větve, takže pokud bychom chtěli vytvořit něco takového:

Použijeme metodu insert následujícím způsobem:

t=Tree(okno)
t.pack()
vetev=t.insert(0,"čísla")#Pokud něco vkládáme na základní úroveň, použijeme vždy 0
t.insert(vetev,"jedna")#vlož "jedna" do čísel
t.insert(vetev,"dva")
t.insert(0,"písmena")

Po každém přidání nové hodnoty se nebude celé plátno překreslovat, to by trvalo moc dlouho. Proto budeme volat metodu update:

def update(self):
    self.platno.delete(ALL)#Vyčištění plochy
    self.y=10
    self.vykresli(0,10)#Vysvětlíme si o odstavec později

Nyní bychom měli napsat nějakou vykreslovací funkci. Budeme jí říkat například vykresli. Ta bude přijímat parametr id a odsazeni. Pak nějak vybereme z self.slovnik všechny hodnoty na základní úrovni a předáme je jiné funkci, která je zobrazí. Funkce, která bude hledat položky patřící do daného ID, by mohla vypadat takto:

def najdi_polozky(self,id):
        s=[ ]
        for prvek in self.slovnik.values():#procházej všechny hodnoty ve slovníku
            if prvek[1] == id:#pokud se id rovná nadřazené skupině, přidej ho do seznamu
                s.append(prvek)
        return s

Tím pádem bude vypadat funkce vykresli takto:

def vykresli(self, id,odsazeni=10):
        s=self.najdi_polozky(id)
        for prvek in s:
            self.maluj(prvek,odsazeni)

Použili jsme funkci maluj, která ještě není definovaná, nicméně je celkem jasné, co by měla dělat. My zatím pouze vypíšeme text s patřičným odsazením.

def maluj(self, prvek, odsazeni):
        self.platno.create_text(odsazeni,self.y,text=unicode(prvek[2],"cp1250"),anchor="w")#Používáme unicode, aby se správně zobrazila diakritika
        self.y=self.y+20#Posuneme se o 20 pixelů níže.

Vložíme-li tedy nějaké hodnoty na pozici 0 a zavoláme funkci update(), mělo by se objevit něco takového:

Pro jistotu uvedu kód, kterým jsme dosáhli předchozího obrázku:

t=Tree(okno)
t.pack()
t.insert(0,"písmena")
t.insert(0,"čísla")
t.update()

Nyní bychom se měli pustit do vykreslovaní vložených hodnot (na jiné než základní úrovni). Proto budeme muset trochu upravit funkci vykresli. Tato funkce by měla rekurzivně volat sama sebe, pokud některý z prvků má status open. Zároveň by se mělo zvětšit odsazení10.

def vykresli(self, id,odsazeni):
        s=self.najdi_polozky(id)
        for prvek in s:
            self.maluj(prvek,odsazeni,zn=zn)
            if prvek[3] == "open":#Pokud má prvek status open
                self.vykresli(prvek[0],odsazeni+10)#Zavolej vykresli s id=prvek[0]

Nyní, kdybychom vyzkoušeli znovu pustit program, neuvidíme žádnou změnu, protože všechny statusy jsou standardně nastavené na close. Proto musíme vytvořit rozevírací tlačítka. Rozevírací tlačítko budeme vykreslovat ve funkci maluj. Ale jak poznat kdy ho vykreslit? Já jsem použil způsob zavolání funkce najdi_polozky a pokud by délka vráceného seznamu byla větší než nula, je jasné, že musíme vykreslit tlačítko. Vzniká ale další problém. Jak poznat, jestli máme vykreslit obrázek s plusem nebo s mínusem? Řešení je velmi jednoduché. Při vkládání záznamu do udělátka definujeme, že je close. Takže vše vyřešíme jednou podmínkou.

#Musíte definovat "self.znacky" ve funkci __init__
#Následující kód patří do funkce maluj
if len(self.najdi_polozky(prvek[0])) != 0:
    image=self.i_minus
    if prvek[3] == "close":
          image=self.i_plus
     i=self.platno.create_image(odsazeni,self.y,image=image)
     self.znacky[i]=prvek

Snad si ještě vzpomínáte na dřívější lekce o Tkinteru, kde jsme si vysvětlovali metodu tag_bind.

self.platno.tag_bind("otevrit_zavrit","<1>",self.otevrit_zavrit)#Tento kód přidejte do funkce __init__

Jak vidíte, musíme definovat metodu otevrit_zavrit. V dřívějších lekcích jsme si vysvětlovali, jak získat objekt plátna, na který jsme klikli. Ale teď potřebujeme zjistit ID objektu. Proto jsme vytvářeli slovník self.znacky. Pomocí něho zvládneme získat ID objektu:

def otevrit_zavrit(self, akce):
    akce= self.platno.find_withtag(CURRENT)[0]#získej objekt
    opak={"open":"close","close":"open"}[self.znacky[akce][3]]#Získej opak: close->open; open->close
    self.znacky[akce][3]=opak#Ulož změněnou hodnotu
    self.update()

Nyní bychom měli opět vyzkoušet, jak bude náš program vypadat, když ho spustíme:

t=Tree(okno)
t.pack()
vetev=t.insert(0,"čísla")
t.insert(vetev,"jedna")
t.insert(vetev,"dva")
t.insert(0,"písmena")
t.update()

Po vyzkoušení tohoto kódu na nás vykoukne toto:

Jak vidíte, obrázky nepěkně vstupují do textu. Proto musíme každý text, vedle kterého vykreslujeme obrázek, posunout o 12 pixelů doprava. Toto určitě zvládnete sami. Poslední věc, co musíme udělat, je vykreslovaní čar. Naštěstí se nejedná o nic složitého, proto s tím budeme hotovi celkem rychle.

Pro každou hodnotu, kterou do udělátka vložíme, budeme muset vykreslit dvě čáry. Následujícím obrázkem se pokusím nastínit jak vykreslit čáry.

Nyní můžeme již vykreslit obě čáry.

#Tento kód přidejte do funkce maluj
#Vložte ho před vykreslování rozevíracích tlačítek
self.platno.create_line(odsazeni-10, self.y-17,odsazeni-10, self.y+3)#vertikální čára
self.platno.create_line(odsazeni-10, self.y+2,odsazeni-2, self.y+2)#horizontální čára

Teď můžeme opět vyzkoušet program:

Ejhle! Chybí nám tam jedna čára. Chyba je ve funkci vykresli. Předtím, než vykreslíme podsložky (tedy vložené hodnoty se statusem open), musíme uložit do proměnné pozice hodnotu self.y. Po vykreslení podsložek vykreslíme čáru se souřadnicemi odsazeni-10, pozice-20, odsazeni-10, self.y-20. Funkce vykresli tedy bude vypadat takto:

def vykresli(self, id,odsazeni):
    s=self.najdi_polozky(id)
    for prvek in s:
        pozice=self.y
        self.maluj(prvek,odsazeni)
        if prvek[3] == "open":
            self.vykresli(prvek[0],odsazeni+10)
        cara=self.platno.create_line(odsazeni-10,pozice-20,odsazeni-10,self.y-20)
        self.platno.lower(cara)

Použili jsme možná pro někoho neznámou metodu self.platno.lower(objekt). Takto funkce posouvá objekt na spodek zásobníku. Nyní je hlavní část programu za námi a zbývá doladit některé maličkosti.

Funkční posuvníky

Možná jste si všimli, že pokud vložíte do udělátka více hodnot, posuvníky se neaktivují. My musíme ještě manuálně určit, která oblast bude skrolovatelná. Budeme potřebovat metodu bbox:

#Tento kod přidejte nakonec funkce update
self.platno["scrollregion"]=self.platno.bbox(ALL)

Výběr položky

Máme sice funkční udělátko, ale nemůžeme vybrat žádnou hodnotu. Já jsem se rozhodl pro asi nejjednodušší řešení, nicméně není úplně vhodné pro velké množství hodnot, protože je časově náročné.

Já jsem přidal do seznamu, který vytváříme v metodě insert, položku "unselect". Potom musíme definovat tag (například vybrat) a tento tag přiřadit textu, který vytváříme v metodě maluj. V metodě self.vybrat musíme podobně jako v metodě otevrit_zavrit zjistit ID prvku a následně nahradit unselect na select (popř. obráceně):

def vybrat(self, akce):
    prvek=self.znacky[self.platno.find_withtag(CURRENT)[0]]
    prvek[4]={"select":"unselect","unselect":"select"}[prvek[4]]
    self.update()#Opět překreslíme celou plochu

Pokud bychom tuto funkci dále neupravovali, mohli bychom vybrat neomezený počet položek, proto na začátku musíme zavolat metodu unselect_all, která by mohla vypadat takto:

def unselect_all(self):
    for prvek in self.slovnik.values():
        prvek[4]="unselect"

Pokud bychom program teď spustili, zjistíme, že vybraná položka není zvýrazněná. Proto musíme ve funkci malujpřidat podmínku, že pokud prvek[4] == "select" zabarvíme plochu za textem do modra a změníme barvu písma na bílou. O zabarvení plochy za textem se obstará metoda self.platno.create_rectangle(x1,y1,x2,y2,fill='blue'). Velikost obdélníku zjistíme nám už známou metodu bbox(objekt):

barva="black"
if prvek[4] == "select":
    barva="white"
text=self.platno.create_text(odsazeni+pricist,self.y,text=unicode(prvek[2],"cp1250"),anchor="w",tags=("vybrat"),fill=barva)
if prvek[4] == "select":
    x1,y1,x2,y2=self.platno.bbox(text)
    vyber=self.platno.create_rectangle(x1,y1,x2,y2,fill='blue')
    self.platno.lower(vyber)

Získání výběru

Musíme samozřejmě definovat metodu self.get, která bude vracet právě vybraný text. Tato metoda je velmi jednoduchá. Budeme pouze procházet self.slovnik.values() a bude položka vybrána (select), vrátíme její hodnotu:

def get(self):
    for prvek in self.slovnik.values():
        if prvek[4] == "select":
            return prvek[2]
    return ""

Zdrojový kód

Protože dnešní lekce byla trochu složitější, radši si ukážeme, jak vypadá zdrojový kód:

# -*- coding: cp1250 -*-
#TreeWidget
from Tkinter import*
import tkFont
class Tree(Frame):
    def __init__(self, parent=None):
        self.id=0
        self.i_plus=PhotoImage(file="plus.gif")
        self.i_minus=PhotoImage(file="minus.gif")
        self.parent=self
        Frame.__init__(self,parent)
        ram=Frame(self)
        ram.pack(fill=BOTH, expand=1)
        scy=Scrollbar(ram)
        scy.pack(fill=Y,side=RIGHT)
        scx=Scrollbar(ram,orient="horizontal")
        scx.pack(fill=X,side=BOTTOM)
        self.platno=Canvas(ram,bg='white',yscrollcommand=scy.set,xscrollcommand=scx.set)
        self.platno.pack(fill=BOTH, expand=1,side=LEFT)
        self.platno.tag_bind("otevrit_zavrit","<1>",self.otevrit_zavrit)
        self.platno.tag_bind("vybrat","<1>",self.vybrat)
        scy["command"]=self.platno.yview
        scx["command"]=self.platno.xview
        self.seznam=[]
        self.slovnik={}
        self.znacky={}
        self.texty={}
        self.update()
   
        methods = Pack.__dict__.keys()
        methods = methods + Grid.__dict__.keys()
        methods = methods + Place.__dict__.keys()

        for m in methods:
            if m[0] != '_' and m != 'config' and m != 'configure':
                setattr(self, m, getattr(self, m))
    def vybrat(self, akce):
        prvek=self.znacky[self.platno.find_withtag(CURRENT)[0]]
        self.unselect_all()
        prvek[4]={"select":"unselect","unselect":"select"}[prvek[4]]
        self.update()
        
    def otevrit_zavrit(self, akce):
        akce= self.platno.find_withtag(CURRENT)[0]
        opak={"open":"close","close":"open"}[self.znacky[akce][3]]
        self.znacky[akce][3]=opak
        self.update()
    def maluj(self, prvek,odsazeni):
        pricist=0
        self.platno.create_line(odsazeni-10, self.y-17,odsazeni-10, self.y+3)#vertical line
        self.platno.create_line(odsazeni-10, self.y+2,odsazeni-2, self.y+2)
        if len(self.najdi_polozky(prvek[0])) != 0:
            image=self.i_minus
            if prvek[3] == "close":image=self.i_plus
            i=self.platno.create_image(odsazeni,self.y,image=image,tags=("otevrit_zavrit"))
            self.znacky[i]=prvek
            pricist=12
        barva="black"
        if prvek[4] == "select":
            barva="white"
        text=self.platno.create_text(odsazeni+pricist,self.y,text=unicode(prvek[2],"cp1250"),anchor="w",tags=("vybrat"),fill=barva)
        if prvek[4] == "select":
            x1,y1,x2,y2=self.platno.bbox(text)
            vyber=self.platno.create_rectangle(x1,y1,x2,y2,fill='blue')
            self.platno.lower(vyber)
        self.znacky[text]=prvek    
        self.parent.image=[self.i_plus,self.i_minus]
        self.y=self.y+20
    
    def unselect_all(self):
        for prvek in self.slovnik.values():
            prvek[4]="unselect"
    def update(self):
        self.platno.delete(ALL)
        self.x=10
        self.y=10
        self.vykresli(0,10)
        self.platno["scrollregion"]=self.platno.bbox(ALL)
    def vykresli(self, id,odsazeni):
        s=self.najdi_polozky(id)
        for prvek in s:
            pozice=self.y
            self.maluj(prvek,odsazeni)
            if prvek[3] == "open":
                self.vykresli(prvek[0],odsazeni+10)
            cara=self.platno.create_line(odsazeni-10,pozice-20,odsazeni-10,self.y-20)
            self.platno.lower(cara)
    def najdi_polozky(self,id):
        s=[]
        for prvek in self.slovnik.values():
            if prvek[1] == id:
                s.append(prvek)
        return s
    def insert(self,kam,co,status="close"):
        self.id=self.id+1
        s=[self.id,kam,co,status,"unselect"]
        self.slovnik[self.id]=s
        return self.id
    def get(self):
        for prvek in self.slovnik.values():
            if prvek[4] == "select":
                return prvek[2]
        return ""
if __name__ == "__main__":
    okno=Tk()
    t=Tree(okno)
    t.pack()
    vetev=t.insert(0,"čísla")
    t.insert(vetev,"jedna")
    t.insert(vetev,"dva")
    t.insert(0,"písmena")
    t.update()
    mainloop()

×Odeslání článku na tvůj Kindle

Zadej svůj Kindle e-mail a my ti pošleme článek na tvůj Kindle.
Musíš mít povolený příjem obsahu do svého Kindle z naší e-mailové adresy kindle@programujte.com.

E-mailová adresa (např. novak@kindle.com):

TIP: Pokud chceš dostávat naše články každé ráno do svého Kindle, koukni do sekce Články do Kindle.

1 názor  —  1 nový  
Hlasování bylo ukončeno    
0 hlasů
Google
Autor studuje na FIT ČVUT a je šéfredaktorem portálu Matematika pro každého.
Web    

Nové články

Obrázek ke článku Ericsson ConsumerLab Report: rozšířená realita je další úrovní gamingu

Ericsson ConsumerLab Report: rozšířená realita je další úrovní gamingu

Celkem 66 % uživatelů zajímá rozšířená realita v oblasti gamingu. Mezi nimi je i 35 % těch, kteří jinak hry nehrají.
Pro téměř 50 % respondentů by bylo zajímavé zapojení virtuální objektů do reálného světa. Objekty by zůstaly tam, kde je při hře „umístili“.
Až 43 % uživatelů láká využití rozšířené reality ve sportu

Reklama
Reklama
Obrázek ke článku Instalace nejnovější verze Apache 2.4, PHP 7.3, MariaDB 10.3 a Memcached na Windows 10

Instalace nejnovější verze Apache 2.4, PHP 7.3, MariaDB 10.3 a Memcached na Windows 10

Buďte při vývoji efektivní! Pomocí tohoto návodu během chvíle vytvoříte ze svého počítače lokální webový server. Vyzbrojíte jej vším, co budete při práci potřebovat: Apache 2.4, PHP 7.3, MariaDB 10.3 a Memcached. Je to plná polní pro webové vývojáře s Windows 10. Navíc poradíme, jak mít na localhostu více projektů pomocí VirtualHost.

Obrázek ke článku Do poskytovatele managed hostingových služeb vshosting~ vstupují zahraniční investoři

Do poskytovatele managed hostingových služeb vshosting~ vstupují zahraniční investoři

Po více než roce jednání do vshosting~ vstoupili 3 investiční skupiny z Německa: Pecunalta, BrainWeb Investment a Quines Capital. Jde o investiční skupiny, které mají účast na projektech jako PlusServer (největší managed provider v Německu a jeden z největších v Evropě), PLESK, cPanel, CloudLinux, GoDaddy (největší světový hostingový poskytovatel z USA), či Acronis, pomohou vshosting~ v jeho plánované mezinárodní expanzi na další zahraniční trhy. Ve vshosting~ nyní drží 75% podíl, zbylých 25 % zůstává zakladatelům vshosting~, kterými jsou Damir Špoljarič (CEO) a Jan Martinů (CTO).

Obrázek ke článku Posuňte své znalosti IT na výrazně vyšší úroveň

Posuňte své znalosti IT na výrazně vyšší úroveň

Zájem o IT odborníky je v současnosti v tuzemsku i v zahraničí enormní a vedení firem si moc dobře uvědomuje, jak těžké je získat ty správné. I přesto, že je odborníků na trhu dlouhodobý nedostatek, stále platí, že část z nich je - a bude - placena výrazně lépe než ti ostatní. Proč tedy nebýt mezi nimi?

Hostujeme u Českého hostingu       ISSN 1801-1586       ⇡ Nahoru Webtea.cz logo © 20032019 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý