Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: Python tiedoston tai kansion valitseminen

Sivun loppuun

Macro [03.07.2010 16:30:17]

#

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.)

Macro [07.07.2010 23:51:41]

#

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.

jalski [08.07.2010 19:39:14]

#

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

Macro [08.07.2010 19:52:57]

#

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.

jalski [08.07.2010 20:05:51]

#

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.

Metabolix [08.07.2010 20:43:52]

#

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.

Macro [10.07.2010 10:04:36]

#

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?

jalski [10.07.2010 14:34:28]

#

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.

Macro [10.07.2010 15:36:20]

#

Aikamoista kikkailua, mutta voihan sen näinkin tehdä. Tosin, nyt ei ole sopiva aika sille.

Macro [11.07.2010 01:15:14]

#

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?

Macro [11.07.2010 11:57:52]

#

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.

jalski [11.07.2010 18:11:01]

#

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.

Metabolix [11.07.2010 18:51:16]

#

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.

Macro [12.07.2010 16:47:06]

#

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()

Macro [13.07.2010 17:46:43]

#

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ä.

jalski [13.07.2010 19:18:17]

#

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.

Macro [13.07.2010 19:26:12]

#

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?

jalski [13.07.2010 19:35:01]

#

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?

Macro [13.07.2010 19:37:31]

#

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)).

jalski [13.07.2010 19:55:50]

#

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.

Macro [13.07.2010 20:38:28]

#

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ä.

Macro [13.07.2010 21:50:47]

#

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.

jalski [13.07.2010 22:39:56]

#

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ä.

Macro [14.07.2010 13:33:10]

#

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().

jalski [14.07.2010 17:09:51]

#

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.

Macro [14.07.2010 20:24:37]

#

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.

Deffi [14.07.2010 21:43:16]

#

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.html#tkinter.ttk.Treeview

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)

Grez [14.07.2010 21:59:28]

#

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.

Macro [15.07.2010 13:19:28]

#

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.

Macro [15.07.2010 17:13:55]

#

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?

Metabolix [15.07.2010 17:26:33]

#

Muuttuja d pitäisi antaa funktiolle parametrina, ja ensimmäisellä kutsukerralla sen pitäisi olla "". Kummassakin insertissä pitäisi käyttää d:tä.

Macro [15.07.2010 17:52:21]

#

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()

Metabolix [15.07.2010 19:59:36]

#

Mitä jos et sitten "korjaisi" eli piilottaisi asiaa try-lohkolla vaan tekisit, kuten neuvoin?

Macro [15.07.2010 20:28:07]

#

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.)

Chiman [15.07.2010 21:08:43]

#

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.

Macro [15.07.2010 21:19:59]

#

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

Chiman [15.07.2010 21:33:41]

#

Muutitko nuo molemmat antamani rivit?

Macro [15.07.2010 21:36:47]

#

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()

Metabolix [15.07.2010 22:09:10]

#

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.)

Macro [15.07.2010 23:34:39]

#

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

Chiman [16.07.2010 00:10:26]

#

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)

Metabolix [16.07.2010 10:10:11]

#

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?


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta