Morjensta
Tkinterissä on mahdollisuus kysyä käyttäjältä tiedostoa tai kansiota, käyttämällä seuraavia funktioita:
from tkinter import * import tkinter.filedialog as fd root = Tk() # Tiedosto nimi = fd.askopenfilename() print(nimi) # Kansio nimi = fd.askdirectory() print(nimi) root.mainloop()
Nämä näyttävät paikallisen koneen hakemistolistauksen. Onko mahdollista kysyä serverikoneella tiedosto-/hakemistolistausta ja palauttaa se clientille valittavaksi? Onhan tietenkin mahdollista jakaa serverikoneen kovalevyt, ja sitten ottaa clientillä se kohdasta My network places. Se vaan ei ole kovin hyvä tapa, koska siinä tulisi ylimääräistä vekslaamista.
Oletuksena askdirectory():ssa on hakemistona kansio, jossa koodi sijaitsee. Voiko sitä muuttaa, esimerkiksi verkkosijaintiin \\xxx\x? (Muokkaus. Tämä onnistuukin laittamalla parametriksi initialdir:n. Verkkosijainnin antaminen ei kumminkaan toimi. Muokkaus 2. Kyllä nähtävästi verkkosijainninkin voi määrittää oletushakemistoksi, kunhan laittaa sen näin: c:/documents and settings/käyttäjä/nethood/share name.)
Eikö tähän oe helppoa ratkaisua? Saako funktion palauttamaan listan hakemistoista siten, että sen voisi näyttää samaisella funktiolla? Tuskin, parametreihin ei sellaista ainakaan kuulunut.
Mikä oli tarkalleen ottaen ongelmana? Eivätkö nuo ylläolevat Tkinter funktiot näytä tiedoston ja hakemistonvalinta dialogit?
Jos haluat vain listata esim. kaikki .mp3 päätteiset tiedostot ja näyttää valinta listan ruudulla, niin voit toki käyttää hardcore tapaa: walk() auttanee alkuun.
katso tuolta: http://docs.python.org/library/os.html#os.access
jalski kirjoitti:
Mikä oli tarkalleen ottaen ongelmana? Eivätkö nuo ylläolevat Tkinter funktiot näytä tiedoston ja hakemistonvalinta dialogit?
Niinhän ne näyttävät dialogit. Ongelmana on, että miten voin listata toisen koneen hakemistot ja tiedostot valittavaksi.
Yksi tapa on soveltaa edellä kertomaani walk() -funktiota serverin päässä listaamaan esim. kaikki .mp3 päätteiset tiedostot vaikkapa media nimisessä hakemistossa. tämän listan voit muodostaa merkkijonoksi käyttämällä esim. puolipistettä listan jäsenten erotusmerkkinä. Tuon merkkijonon muunnat tavuiksi ja lähetät asiakasohjelmalle, mikä parsii vastaanotetun ja lisää omaan listaansa.
En ole perehtynyt asiaan, mutta uskoisin, että Tk:n valmiit tiedostodialogit osaavat lukea vain oman koneen hakemistoja (tai sellaisilta näyttäviä paikkoja kuten tuokäyttävät nethood-juttu) eivätkä kelpuuta ulkopuolisia listoja. Joudut siis luultavasti tekemään oman valintadialogin, ellei Tk satu sisältämään jotain valmista dialogia omien listojen näyttämiseen.
Jos serveri lähettää listauksen clientille, niin miten se muotoillaan clientillä? Minusta pyörän uudelleen keksiminen on turhaa, jos joku on tämän jo tehnyt. Onko olemassa jotain kirjastoa tms. jolla tämän saisi toimimaan helposti?
Macro kirjoitti:
Jos serveri lähettää listauksen clientille, niin miten se muotoillaan clientillä?
Eihän tuo nyt mitenkään vaikeata ole. Jos serveri lähettää merkkijonona listan, missä listan jäsenet on erotettu esim. puolipisteellä. En tiedä Pythonin kirjastojen vastinetta, mutta Infernolla ainakin löytyy suoraan tokenize -funktio mikä hajoittaa merkkijonon osiin (listaan) halutun erotusmerkin mukaan. Sitten ei tarvi muuta, kuin käydä tuo lista läpi ja syöttää sen jäsenet siitä vaikka Tk:n listboxiin.
Olen käyttänyt tuota tapaa chat-serverin kanssa käyttäjätietojen välittämiseen asiakkaille.
Aikamoista kikkailua, mutta voihan sen näinkin tehdä. Tosin, nyt ei ole sopiva aika sille.
Oikeastaan, miksi siinä pitäisi käyttää tuollaisia funktioita? Voihan käyttää rekursiota ja os.listdir()-funktiota.
import os from tkinter import * tm = ["aac", "flac", "m3u", "mid", "midi", "mp1", "mp2", "mp3", "mpga", "oga", "ogg", "pls", "wav", "wave", "wma"] root = Tk() root.config(width=200, height=400) scrollbar = Scrollbar(root) scrollbar.pack(side=RIGHT, fill=Y) listbox = Listbox(root) listbox.pack() listbox.config(bd=0, height=40, width=50) listbox.config(yscrollcommand=scrollbar.set) scrollbar.config(command=listbox.yview) lista = [] def ListDir(d, taso): global listbox, lista for name in os.listdir(d): if os.path.isdir(d + "\\" + name): lista.append(" " * (taso + 1) + d + "\\" + name) ListDir(d + "\\" + name, taso + 1) else: o = name.split(".") o = o[-1] if o in tm: lista.append(" " * (taso + 1) + d + "\\" + name) ListDir("C:\\Documents and settings\\....\\My documents\\Musat", 0) for i in lista: listbox.insert(END, i) def b(e): global listbox, lista try: print(lista[listbox.index(listbox.curselection())]) except: pass listbox.bind("<Button-1>", b) root.mainloop()
Muuten hyvä, mutta vaakasuuntainen scrollbar puuttuu (Miksiköhän tähän aikaa yöstä ei ajatus luista?). Eikö vain?
Olisi kiva, etät kansion voisi sulkea (tai oikeastaan ne olisivat suljettuna, ja klikatessa se aukeaisi) tilan takia. Ideoita tämän toteutukseen?
Jos kehitän tuota niin pitkälle, että se toimii vastaavasti kuin nuo valmiit funktiot, niin minulla olisi kysyttävää. Miten saan tekstikentän tiettyyn frameen ilman uutta ikkunaa? Normaalistihan aukeaa uusi ikkuna, kun haluaa kysyä tietoja.
Aijempaan kysymykseeni sain itse keksittyä ratkaisun. Teen kaksi listaa, toisessa on kaikki tiedot ja toisessa vain albuminimet. Täysi lista on tälläinen:
lista { "Albumi 1" : [ "1.", "2.", "3." ] }
Toisessa listassa on vain albumeiden nimet, ja kun nimeä klikataan, niin haetaan toisesta listasta (täysi) kappaleet ja muut, mitä siihen kuuluu.
Macro kirjoitti:
Oikeastaan, miksi siinä pitäisi käyttää tuollaisia funktioita? Voihan käyttää rekursiota ja os.listdir()-funktiota.
Tuolla aiemmin mainitsemallani walk() funktiollahan saadaan kyllä rekursiivisesti käytyä läpi hakemistopuuta.
Mutta varsinainen ongelmahan tässä oli se, että serverin päässä olevat mediatiedostojen tiedot pitäisi saada välitettyä asiakkaalle päin. Tähän yksinkertaisena ratkaisuna ehdotin siis, että serverin päässä olevasta mediatiedostolistasta koostetaan erotinmerkeillä varustettu merkkijono, mikä muunnetaan tavuiksi ja lähetetään asiakasohjelmalle. Asiakasohjelma tekee tuon merkkijonon pohjalta sitten oman listansa.
Mitä tulee siihen, että haluat Tk:ssa lisätä tekstikentän johonkin kehykseen ilman uutta ikkunaa. Kokeilepas yksinkertaisesti vain määrittää tuo uusi tekstikenttä siten, että haluamasi kehys on parenttina. Tämän jälkeen pakkaa koko kehys uudelleen.
jalski kirjoitti:
erotinmerkeillä varustettu merkkijono
Erotinmerkkien kanssa pitää olla erittäin tarkkana: voihan tiedostonimikin sisältää kaikenlaisia kummallisia merkkejä. Yleisesti NUL-merkki (ASCII-arvo 0) on sieltä turvallisimmasta päästä, jos tuohon haluaa ruveta. Kuitenkin viisaampaa olisi käyttää suoraan jotain kirjastoa, joka muuntaa Python-dataa tekstiksi, jolloin ei tarvitse kikkailla erotinmerkkien ja erilaisten puurakenteiden tallentamisen kanssa.
Käytin funktiota Entry() datan lukemiseen, ja funktiota grid sen sijoittamiseen. Se toimii hyvin.
Miten voin käyttää bindiä siten, että yhdellä klikkauksella suoritetaan funktio X, ja kahdella (tuplaklikkaus) funktio Y? Jos laitan kaksi kertaa bindin (ensimmäiseen <Button-1> bindiin, ja seuraavaan <Double-Button-1>), niin ensimmäinen ei toimi oikein. Testikoodi:
import os, tkinter.simpledialog from tkinter import * tm = ["aac", "flac", "m3u", "mid", "midi", "mp1", "mp2", "mp3", "mpga", "oga", "ogg", "pls", "wav", "wave", "wma"] root = Tk() root.resizable(height=FALSE, width=FALSE) y_scrollbar = Scrollbar(root) y_scrollbar.pack(side=RIGHT, fill=Y) x_scrollbar = Scrollbar(root) x_scrollbar.pack(side=BOTTOM, fill=BOTH) listbox = Listbox(root) listbox.pack() listbox.config(bd=0, height=20, width=120) listbox.config(yscrollcommand=y_scrollbar.set) y_scrollbar.config(command=listbox.yview) listbox.config(xscrollcommand=x_scrollbar.set) x_scrollbar.config(command=listbox.xview, orient=HORIZONTAL) lista = [] lista_k = [] def ListDir(d, taso): global listbox, lista for name in os.listdir(d): if os.path.isdir(d + "\\" + name): lista.append(" " * (taso + 1) + d + "\\" + name) lista_k.append([" " * (taso + 1) + name, d + "\\" + name]) ListDir(d + "\\" + name, taso + 1) else: o = name.split(".") o = o[-1] if o in tm: lista.append(" " * (taso + 1) + d + "\\" + name) ListDir("C:\\Documents and settings\\....\\My documents\\Musat", 0) tekstikentta = Frame(root) tekstikentta.pack(side=LEFT) Label(tekstikentta, text="Kansio: ").grid(row=0) v = StringVar() t = Entry(tekstikentta, width=50, textvariable=v).grid(row=0, column=1) for i in lista_k: listbox.insert(END, i[0]) def b(e): global listbox, lista print(lista_k[listbox.index(listbox.curselection())][0].strip()) v.set(lista_k[listbox.index(listbox.curselection())][0].strip()) listbox.bind("<Double-Button-1>", b) root.mainloop()
Opsista, yllä olevasta koodista jäikin se ongelmakohta pois. No ei tuo mitään, laitan sen uudelleen sitten.
import os, tkinter.simpledialog from tkinter import * tm = ["aac", "flac", "m3u", "mid", "midi", "mp1", "mp2", "mp3", "mpga", "oga", "ogg", "pls", "wav", "wave", "wma"] root = Tk() root.resizable(height=FALSE, width=FALSE) def LuoIkkuna(): global ikkuna, y_scrollbar, x_scrollbar, listbox, lista, lista_k ikkuna = Toplevel() y_scrollbar = Scrollbar(ikkuna) y_scrollbar.pack(side=RIGHT, fill=Y) x_scrollbar = Scrollbar(ikkuna) x_scrollbar.pack(side=BOTTOM, fill=BOTH) listbox = Listbox(ikkuna) listbox.pack() listbox.config(bd=0, height=20, width=120) listbox.config(yscrollcommand=y_scrollbar.set) y_scrollbar.config(command=listbox.yview) listbox.config(xscrollcommand=x_scrollbar.set) x_scrollbar.config(command=listbox.xview, orient=HORIZONTAL) lista = [] lista_k = [] def ListDir(d, taso): global listbox, lista, lista_k for name in os.listdir(d): if os.path.isdir(d + "\\" + name): lista.append(" " * (taso + 1) + d + "\\" + name) lista_k.append([" " * (taso + 1) + name, d + "\\" + name]) ListDir(d + "\\" + name, taso + 1) else: o = name.split(".") o = o[-1] if o in tm: lista.append(" " * (taso + 1) + d + "\\" + name) for i in lista_k: listbox.insert(END, i[0]) def LuoInput(): global tekstikentta, v, t, ikkuna tekstikentta = Frame(ikkuna) tekstikentta.pack(side=LEFT) Label(tekstikentta, text="Kansio: ").grid(row=0) v = StringVar() t = Entry(tekstikentta, width=50, textvariable=v).grid(row=0, column=1) def b(e): global listbox, lista, v, ikkuna, lista_k v.set(lista_k[listbox.index(listbox.curselection())][0].strip()) print("Avataan kansiota " + lista_k[listbox.index(listbox.curselection())][0].strip() + "...") print("Suljetaan valintaikkuna...") ikkuna.destroy() root.destroy() def bb(e): global listbox, v, lista_k v.set(lista_k[listbox.index(listbox.curselection())][0].strip()) def ValintaikkunaBind(): listbox.bind("<Button-1>", bb) listbox.bind("<Double-Button-1>", b) LuoIkkuna() LuoInput() ListDir("C:\\Documents and settings\\Mikko\\My documents\\Musat", 0) ValintaikkunaBind() root.mainloop()
Minä ymmärtäisin tuon siten, että kun klikataan kerran, niin suoritetaan funktio bb. Jos klikataan kahdesti, suoritetaan funktiot bb ja b (Ensin tulee yksi klikkaus -> bb, sitten toinen -> bb). Voihan sen käsittää myös kahtena bb:nä.
Voisit muuten myös kohtuu helposti toteuttaa tiedostonhallinnan tyyppisen kansionäkymän käyttäen canvasta. Toteutus voisi näyttää hyvin pitkälti Infernon mukana tulevan graafisen hakemistoselaajan tapaiselta. Tuohon voisi yksinkertaisesti bindata hiirennapin kaksoispainalluksen siten, että hakemistokansion ollessa kyseessä siirrytään kyseiseen hakemistoon ja näytetään sen sisältö. Jos taas hiirennapin kaksoispainalluksen kohde on datatiedosto, niin valitaan, avataan, käynnistetään tai tehdään jotain muuta sen kanssa.
Voisihan sen niinkin tehdä, mutta kun olen noin jo aloittanut, niin niin sen teenkin. Ongelmana on kaksi bindausta. Miten suoritataan funktio X kun klikataan kerran, ja funktio Y kun klikataan kahdesti?
Macro kirjoitti:
Ongelmana on kaksi bindausta. Miten suoritataan funktio X kun klikataan kerran, ja funktio Y kun klikataan kahdesti?
Luulen, että bindatessa molemmat: painallus sekä tuplapainallus samaan kohteeseen, niin tuplapainalluksen tapauksessa suoritetaan molempien bindausten callbackit. Tämä ei välttämättä ole kovin mukavaa saati tarpeellista.
Mitä pitaisi tapahtua yhdellä painalluksella? Entäs sitten tupla painalluksella?
Sillä ei ole väliä, suoritetaanko tuplaklikatessa molemmat vai ei. Kumminkin tuplaklikatessa joudun tekemään samat kuin yhdellä klikkauksella & vähän enemmän.
Yhdellä klikkauksella pitäisi suorittaa kenttä.set(text)
. Kahdella klikkauksella äskeinen, ja kansion avaaminen (Tapahtuu kutsumalla funktiota Avaa(kansio)
).
En ole aivan varma, hahmotanko haluamaasi käyttöliittymää. Mikset vain bindaa pelkkää tuplapainallusta? Listboxihan näyttää valitun suoraan ja tuplapainalluksen tullessa voi itse tarkistaa onko kyseessä hakemisto vai tiedosto ja toimia sen mukaan.
Suosittelin tuota canvasta sen helppouden takia. Canvakseen lisätyille objekteille pystyy helposti määrittelemään kullekin omat bindauksensa (esim. onko hakemisto vai tiedosto). Se on helppo täyttää käyttäen joko kuvia ja tekstejä tai pelkkää tekstiä. Tyhjennys ja muu muokkaus käy myös todella näppärästi.
Voit myös varmaankin listboxin tapauksessa korvata tuon <Button-1> eventin bindauksen <<ListboxSelect>> virtuaali eventin bindauksella.
Hei, kiitos! Tuo <<ListboxSelect>> auttoi juuri oikealla tavalla. Yhden klikkauksen bindausta tarvitaan siihen, että tekstikenttään voidaan asettaa valitun kansion/tiedoston nimi (Koitan mahdollisimman paljon tehdä samankaltaista, kuin Windowsin omat systeemit).
Muokkaus. Koodissani on virhe: Lisään ListDir()-funktiossa tavaraa listboxiin. Tästä syystä se tekee monta kertaa saman. Muutin sen toiseen paikkaan, ja nyt hakemistoja on oikea määrä.
Muuten. Miten pystyisin lisäämään helposti kappaleet listaan kun kansio "avataan"?
import os, tkinter.simpledialog from tkinter import * tm = ["aac", "flac", "m3u", "mid", "midi", "mp1", "mp2", "mp3", "mpga", "oga", "ogg", "pls", "wav", "wave", "wma"] root = Tk() root.resizable(height=FALSE, width=FALSE) def LuoIkkuna(): global ikkuna, y_scrollbar, x_scrollbar, listbox, lista, lista_k ikkuna = Toplevel() ikkuna.title("Valitse kansio") y_scrollbar = Scrollbar(ikkuna) y_scrollbar.pack(side=RIGHT, fill=Y) x_scrollbar = Scrollbar(ikkuna) x_scrollbar.pack(side=BOTTOM, fill=BOTH) listbox = Listbox(ikkuna) listbox.pack() listbox.config(bd=0, height=20, width=120) listbox.config(yscrollcommand=y_scrollbar.set) y_scrollbar.config(command=listbox.yview) listbox.config(xscrollcommand=x_scrollbar.set) x_scrollbar.config(command=listbox.xview, orient=HORIZONTAL) lista = [] lista_k = [] km = [] def ListDir(d, taso): global listbox, lista, lista_k, km for name in os.listdir(d): if os.path.isdir(d + "\\" + name): lista.append(" " * (taso + 1) + d + "\\" + name) lista_k.append([" " * (taso + 1) + name, d + "\\" + name]) ListDir(d + "\\" + name, taso + 1) else: o = name.split(".") o = o[-1] if o in tm: lista.append(" " * (taso + 1) + d + "\\" + name) def Avaa(): print("Avataan kansiota \"" + lista_k[listbox.index(listbox.curselection())][0].strip() + "\"...") print(lista_k[listbox.index(listbox.curselection())][1].strip()) print("Suljetaan valintaikkuna...") ikkuna.destroy() print("Suljetaan root...") root.destroy() def LuoInput(): global tekstikentta, v, t, ikkuna tekstikentta = Frame(ikkuna) tekstikentta.pack(side=LEFT) Label(tekstikentta, text="Kansio: ").grid(row=0) v = StringVar() t = Entry(tekstikentta, width=50, textvariable=v).grid(row=0, column=1) b = Button(tekstikentta, text="Avaa", padx=5, command=Avaa).grid(row=0, column=2) def double(e): global listbox, lista, v, ikkuna, lista_k print("Avataan kansio ja näytetään sen sisältämät audio-tiedostot.") def single(e): global listbox, v, lista_k v.set(lista_k[listbox.index(listbox.curselection())][0].strip()) def Tayta(): global lista_k, listbox for i in lista_k: listbox.insert(END, i[0]) def ValintaikkunaBind(): listbox.bind("<<ListboxSelect>>", single) listbox.bind("<Double-Button-1>", double) LuoIkkuna() LuoInput() ListDir("C:\\Documents and settings\\Mikko\\My documents\\Musat", 0) Tayta() ValintaikkunaBind() root.mainloop()
Tuossa koko koodi. Kun suoritetaan double(), niin miten saisin sen lisäämään listboxiin kappaleet, jotka kuuluvat ko. kansion alle? Ajattelin, että sen voisi jotenkin toteuttaa laskemalla alussa olevia välilyöntejä, eli sisennyksiä. Mitenköhän sen voisi toteuttaa järkevästi?
Muokkaus. Lisäksi sulkemisen pitäisi onnistua.
Macro kirjoitti:
Muuten. Miten pystyisin lisäämään helposti kappaleet listaan kun kansio "avataan"?
Perinteinen tapa listboxin kanssa taitaa olla se, että tyhjennät listboxin ja tämän jälkeen täytät sen uudelleen kyseisen hakemiston sisällöllä.
Joo. listbox.insert(sijainti, teksti):llä voi myös lisätä haluamaansa paikkaan, ja on myös funktio, jolla voi poistaa tietyn indeksin. Ei tarvitse koko listaa tehdä uudelleen.
Nyt koitan ratkaista sitä, miten saan haettua oikeat kappaleet siihen. Koodi osaa hakea oikean määrän kappaleita jo, mutta ne näyttävät olevan "vähän" vääriä.
Listan viimeisessä alkiossa kansion audiotiedostojen määrä. Sen perusteella näytän niiden määrän verran tiedostoja siitä kohtaa, mistä on valittu kansio. Ongelman ratkaisen funktiolla index().
Macro kirjoitti:
Joo. listbox.insert(sijainti, teksti):llä voi myös lisätä haluamaansa paikkaan, ja on myös funktio, jolla voi poistaa tietyn indeksin. Ei tarvitse koko listaa tehdä uudelleen.
Tuolla listan uudelleen tekemisellä tarkoitin sitä, että tupla napin painalluksella listboxissa olevaan hakemiston nimeen siirryttäisiin listboxissa näyttämään kyseisen hakemiston sisältö. Tämä hoituu siis käytännössä siten, että tyhjennät listboxin ja täytät sen uudelleen kyseisen hakemiston sisällöllä.
Toisin sanottuna hakemistorakenne pitää olla tallennettuna jotenkin muuten, kuin listboxiin. Listboxin tehtävä on vain näyttää tietty osa hakemistorakenteesta kerrallaan, ei varsinaisesti säilyttää sitä.
Löydät todennäköisesti myös jonkunlaisia kirjastoja Tkinter:ille, joissa on toteutettu tree widgettejä. Tämä on siis yksi vaihtoehto, jos haluat esittää hakemistorakenteen puumaisesti.
Niin, voihan sen noinkin tehdä. Tosin, ajattelin Windows-tyylistä (Näitäkin on kaksi, tässä tämä toinen, sinun ajateuksen vastakohta) valitsinta, eli tuplaklikatessa kansion nimeä, niin alapuolelle aukeaisi sisältö.
Ei, en säilytä tietoja vain listboxissa, minulla on kaksi listaa, lista_k ja lista, jossa ensimmäisessä hakemistojen nimet ja toisessa myös tiedostojen nimet.
Tämä kohta on pian valmis. Pitää rakentaa for-silmukka, joka laskee indexin seuraavalle tiedostolle perustuen siihen miten monta tiedostoa sitä ennen on.
Jaahas, täällä ollaan kai rakentamassa treeviewiä listbox-kontrollilla. Ei hyvä. Pythonin sivuila löytyy tarvittava dokumentaatio tkinterin treeviewin käyttämiseen: http://docs.python.org/dev/library/tkinter.ttk.
Teet jonkun funktion joka käy hakemistot läpi rekursiivisesti ja samalla lisää tiedostojen nimet ja muut tarvittavat tiedot puuhun. Käyttäjä saa sen jälkeen vapaasti selata puurakennetta ja sun tarvitsee huolehtia vaun lopullisen tiedoston valitsemisesta (miten se sitten ikinä tapahtuu, tuplaklikkaamalla/painamalla jotain nappia)
Deffi kirjoitti:
Teet jonkun funktion joka käy hakemistot läpi rekursiivisesti ja samalla lisää tiedostojen nimet ja muut tarvittavat tiedot puuhun.
No, mun mielestä tää taas ei välttämättä kuulosta ihan järkevältä. Eli jos tiedostoja ja hakemistoja on paljon, niin onko järkeä ladata ne kaikki 1 783 201 kpl käyttäjän koneelle vain, jotta käyttäjä saisi niistä näkyviin 721 kpl?
Itse olen käyttänyt treeview-rakennetta niin, että ladataan aina yhtä pykälää syvemmälle kuin missä käyttäjä heiluu, jolloin ei tarvitse ladata hirveästi ylimääräistä, ja käyttäjä ei edes huomaa mitään viiveitä. Tämä tosin eri kielillä toteutetussa järjestelmässä eikä siellä järjestelmässä edes ole kuin noin 10 000 kohdetta.
Ai, että on sitä valmis systeemi jo olemassa. Pitää perehtyä tuohon, ja katsoa miten se toimii. Se voisi helpottaa, kun ei tarvitsisi itse alkaa säätämään.
Tosiaan ei kannata ladata kaikkia, vaan vain ne jotka halutaan näyttää (eli juri niin kuin Grez selitti). Pythonin dokumentaatiossa ei tietenkään ole esimerkkiä asiasta, joten pieni esimerkki voisi olla myös paikallaan.
Muokkaus. Tein koodinpätkän, joka tekee jonkinlaisen listan.
from tkinter import * from tkinter import ttk root = Tk() puu = ttk.Treeview(root) for i in range(0, 20): d = puu.insert("", END, "Kansio " + str(i), text="Kansio " + str(i)) for b in range(0, 10): puu.insert(d, END, text="Tiedosto " + str(b)) def bind_cb(e): print(puu.selection()) puu.bind("<<TreeviewSelect>>", bind_cb) puu.pack() root.mainloop()
Miten tuota bind_cb():n tulostusta pitäisi tulkita? Viimeiset numerot ovat ainakin indexinumero, mutta tuo on vähän kummallinen.
Kirjoitin vielä vähän lisää, nyt löytyy jo kaikki tiedostot ja ne lisätään treeviewiin. Ongelma: UnboundLocalError: local variable 'd' referenced before assignment
from tkinter import * from tkinter import ttk import os root = Tk() puu = ttk.Treeview(root) paatteet = ["aac", "flac", "m3u", "mid", "midi", "mp1", "mp2", "mp3", "mpga", "oga", "ogg", "pls", "wav", "wave", "wma"] def Listaa(kansio): for nimi in os.listdir(kansio): if os.path.isdir(kansio + "\\" + nimi): d = puu.insert("", END, text=nimi) Listaa(kansio + "\\" + nimi) else: o = nimi.split(".") o = o[-1].lower() # Koska joidenkin tiedostojen päätteet on kirjoitettu suurilla kirjaimilla if o in paatteet: puu.insert(d, END, text=nimi) # Virhe: UnboundLocalError: local variable 'd' referenced before assignment sijainti = "C:\\Documents and settings\\---\\My Documents\\Musat" def bind_cb(e): print(puu.selection()) Listaa(sijainti) puu.pack() puu.bind("<<TreeviewSelect>>", bind_cb) root.mainloop()
Virhe tarkoittaa siis sitä, että muuttujaan viitattiin ennen sen luomista? Eli Musat-kansiossa ei koodin mielestä voisi olla yhtään kappaletta. Miten tuollaisen voisi korjata?
Muuttuja d pitäisi antaa funktiolle parametrina, ja ensimmäisellä kutsukerralla sen pitäisi olla "". Kummassakin insertissä pitäisi käyttää d:tä.
Korjasin asian try-lohkolla, mutta nämä menevät ihan sekaisin nytten (Vain yksi avattava kohta, sielläkin vääriä tiedostoja. Niitäkin liian vähän.).
from tkinter import * from tkinter import ttk import os root = Tk() puu = ttk.Treeview(root) paatteet = ["aac", "flac", "m3u", "mid", "midi", "mp1", "mp2", "mp3", "mpga", "oga", "ogg", "pls", "wav", "wave", "wma"] def Listaa(kansio): for nimi in os.listdir(kansio): if os.path.isdir(kansio + "\\" + nimi): d = puu.insert("", END, text=nimi) Listaa(kansio + "\\" + nimi) else: o = nimi.split(".") o = o[-1].lower() # Koska joidenkin tiedostojen päätteet on kirjoitettu suurilla kirjaimilla if o in paatteet: try: puu.insert(d, END, text=nimi) # Virhe: UnboundLocalError: local variable 'd' referenced before assignment except UnboundLocalError: puu.insert("", END, text=nimi) sijainti = "C:\\Documents and settings\\---\\My Documents\\Musat" def bind_cb(e): print(puu.selection()) Listaa(sijainti) puu.pack() puu.bind("<<TreeviewSelect>>", bind_cb) root.mainloop()
Mitä jos et sitten "korjaisi" eli piilottaisi asiaa try-lohkolla vaan tekisit, kuten neuvoin?
Metabolix kirjoitti:
Mitä jos et sitten "korjaisi" eli piilottaisi asiaa try-lohkolla vaan tekisit, kuten neuvoin?
Kai minä niin tekisinkin, jos vaan osaisin. Miten se d annetaan parametrinä, jollei noin? (Mhm, jostain syystä tuolla on väärä versio koodista.)
Macro kirjoitti:
Kai minä niin tekisinkin, jos vaan osaisin. Miten se d annetaan parametrinä, jollei noin?
Jos hahmotin oikein:
def Listaa(kansio, d=''): # ... Listaa(os.path.join(kansio, nimi), d)
os.path.joinin käyttö auttaa tekemään koodistasi alustariippumatonta.
Chiman kirjoitti:
Macro kirjoitti:
Kai minä niin tekisinkin, jos vaan osaisin. Miten se d annetaan parametrinä, jollei noin?
Jos hahmotin oikein:
def Listaa(kansio, d=''): # ... Listaa(os.path.join(kansio, nimi), d)os.path.joinin käyttö auttaa tekemään koodistasi alustariippumatonta.
:) En tajunnut, että se pitäisi Listaa-funktiolle antaa parametrinä, luulin Metabolixin tarkoittavan insert-funktiota. Kiitos teille. Nyt se toimii hienosti, ja sen voi liittää ohjelmaani.
Muuten, miksei Treeviewille voi antaa leveyttä? Tuo kapea ikkuna on liian kapea kun nimet ovat pitkiä.
Muokkaus. Ei se vielä ihan toiminutkaan. Se listaa kaiken tarvittavan, mutta ei osaa laittaa kaikkea oikein. Jos on Musat-hakemiston alihakemiston alihakemisto, niin tätä ei listata 1. alihakemiston alle, vaan lisätään jonoon.
Toivottu tulos:
Esittäjä 1 Albumi 1 -> Kappale 1 Kappale 2 Albumi 2 -> Kappale 1 Kappale 2 Esittäjä 2 Albumi 1 -> Kappale 1 Kappale 2 Albumi 2 -> Kappale 1 Kappale 2
Nyt käy näin:
Esittäjä 1 Albumi 1 -> Kappale 1 Kappale 2 Albumi 2 -> Kappale 1 Kappale 2 Esittäjä 2 Albumi 1 -> Kappale 1 Kappale 2 Albumi 2 -> Kappale 1 Kappale 2
Muutitko nuo molemmat antamani rivit?
Kyllä muutin.
from tkinter import * from tkinter import ttk import os root = Tk() puu = ttk.Treeview(root) paatteet = ["aac", "flac", "m3u", "mid", "midi", "mp1", "mp2", "mp3", "mpga", "oga", "ogg", "pls", "wav", "wave", "wma"] def Listaa(kansio, d=""): for nimi in os.listdir(kansio): if os.path.isdir(os.path.join(kansio, nimi)): d = puu.insert("", END, text=nimi) Listaa(os.path.join(kansio, nimi), d) else: o = nimi.split(".") o = o[-1].lower() # Koska joidenkin tiedostojen päätteet on kirjoitettu suurilla kirjaimilla if o in paatteet: puu.insert(d, END, text=nimi) # Virhe: UnboundLocalError: local variable 'd' referenced before assignment sijainti = "C:\\Documents and settings\\---\\My Documents\\Musat" def bind_cb(e): print(puu.selection()) Listaa(sijainti) puu.pack() puu.bind("<<TreeviewSelect>>", bind_cb) root.mainloop()
Enkö jo pari viestiä sitten sanonut, että d:tä pitää käyttää kaikissa inserteissä? (Voisit itsekin vähän miettiä koodisi toimintaa. Ei nimittäin vaadi paljon nähdä, että nyt jokainen hakemisto lisätään kohdan "" alle.)
Niin, olisihan se muuten hyvä, mutta kun nyt kaikki ovat saman hakemiston alla.
Hakemisto 1 -> Hakemisto 1:n kappaleet Hakemisto 2 -> Hakemisto 2:n kappaleet Hakemisto 3 -> Hakemisto 3:n kappaleet
Pitäisi olla näin:
Hakemisto 1 -> Hakemisto 1:n kappaleet Hakemisto 2 -> Hakemisto 2:n kappaleet Hakemisto 3 -> Hakemisto 3:n kappaleet
Entä jos Listaa-funktio on tällainen:
def Listaa(kansio, d=''): for nimi in os.listdir(kansio): if os.path.isdir(os.path.join(kansio, nimi)): Listaa(os.path.join(kansio, nimi), puu.insert(d, END, text=nimi)) else: osat = nimi.split('.') paate = osat[-1].lower() # Koska joidenkin tiedostojen päätteet on kirjoitettu suurilla kirjaimilla if paate in paatteet: puu.insert(d, END, text=nimi)
Macro kirjoitti:
Niin, olisihan se muuten hyvä, mutta kun nyt kaikki ovat saman hakemiston alla.
Silloin teit edelleen jotain väärin. Tämän pitäisi toimia täsmälleen oikein:
def Listaa(polku, haara = ''): for nimi in os.listdir(polku): uusipolku = os.path.join(polku, nimi) if os.path.isdir(uusipolku) or (nimi.rpartition(".")[2].lower() in paatteet): uusihaara = puu.insert(haara, END, text = nimi) if os.path.isdir(uusipolku): Listaa(uusipolku, uusihaara)
Ratkaisu on aivan sama kuin Chimanilla ja aivan sama, jota alun perinkin tarkoitin. (Lisäksi tiedostopäätteen tunnistuksen pitäisi tukea myös päätteettömiä tiedostoja.) Jos hetken mietit asiaa, eikö olekin aivan ilmeistä, että Listaa-funktion parametriksi pitää antaa uusi hakemisto ja uusi puun haara?
Aihe on jo aika vanha, joten et voi enää vastata siihen.