Hej chciałbym stworzyć Listbox w tkitner w taki sposób że jak najadę myszką na jakiś element z listy to zostanie wyświetlona jego cała nazwa. Ponizej screen pokazujacy pożądany efekt. Jak mozna to zrobic?
Do widgetList bind-ować <Enter>
, <Motion>
, <Leave>
. <Enter>
i <Leave>
aby wiedzieć, że jesteś wewnątrz listboxa. <Motion>
aby wyodrębnić y
i przekazać do widgetList.nearest(y)
i masz indeks nad którym jesteś. Otworzyć małe okienko TopLevel bez ramek (wm_overrideredirect
) i sobie coś tam Label wypisać.
Każda zmiana nearest(y)
zmieniać pozycję tooltipa, a <leave>
zamknąć. Trzeba stosować .after
dla otwarcia tooltipa po określonym czasie, a wartość z .after
zachować dla przerwania tego niby "wątku". Strasznie to pokręcone, więc swego czasu przesiadłem się na PyQT bo tam jest wbudowane.
Poszukam to może znajdę kod.
Pozdrawiam
Radek Głębicki
Tak na szybko, ale bez .after
.
import tkinter as tk
from tkinter import Text,Listbox,Label
tkWidget=tk.Tk()
tkWidget.minsize(500,400)
tkWidget.title("List tooltips")
tkWidget.geometry("600x700+100+100")
tkListWidget=Listbox(height=9,width=30)
lListOver=[-1,None,None] #[0] dla indeksu,[1] dla okienka tt,[2] ew. dla obj. .after
lListElms=['aaa','bbb','ccc','ddd']
lListCom =['aaa kom','bbb kom','ccc kom','ddd kom']
for elm in lListElms:
tkListWidget.insert(tk.END,elm)
def f_coord(*d):
global lListOver
xEnrPos,yEnrPos=d[0].x,d[0].y
idxOver=tkListWidget.nearest(yEnrPos)
if idxOver!=lListOver[0]:
sX,sY=str(tkWidget.winfo_pointerx()+10),str(tkWidget.winfo_pointery()+20)
if lListOver[1] is not None: lListOver[1].destroy()
lListOver[1]=tk.Toplevel(tkWidget)
lListOver[1].wm_geometry("+"+sX+"+"+sY)
lListOver[1].wm_overrideredirect(True)
Label(lListOver[1],text=lListCom[idxOver],
bd=1,justify=tk.LEFT).pack(padx=2,pady=2)
lListOver[0]=idxOver
print(idxOver)
return None
tkListWidget.bind('<Motion>',f_coord)
def f_resetnListOver(*d):
global lListOver
lListOver[0]=-1
lListOver[1].destroy()
lListOver[1]=None
lListOver[2]=None
return None
tkListWidget.bind('<Leave>',f_resetnListOver)
tkListWidget.pack()
tkWidget.mainloop()
tkWidget.quit()
quit()
Pozdrawiam
Radek Głębicki
@Radosław Głębicki: Super! Tylko mam teraz taki problem ze probowalem to przezucic na programowanie obiektowe tak ze teraz tkListWidget.bind('<Motion>',lambda x: self.f_coord(x)
i dla funkcji f_coord(self, l, *d)
gdzie l
to moj Listbox dostaje blad "Tuple index out of range" dla xEnrPos,yEnrPos=d[0].x,d[0].y
.
@Radosław Głębicki: Tzn ja chce poslac moja liste jako argument do funkcji f_coord
zeby moc jej uzyc w innych klasach. Piszac tkListWidget.bind('<Motion>',lambda x: self.f_coord(x)
mialem nadzieje ze wywola sie funkcja f_coord
ktora jako argument l
wezmie x
ktore w tym wypadku bedzie tkListWidget
. Gdy jednak w srodku funkcji wywoluje print(d)
to dostaje ()
a dla print(l)
dostaje <Motion event x=..., y =...>
.
EDIT: Naprawione ale mam ostatnie pytanie. Co musze zmienic w kodzie zeby tooltip
wyswietlal sie tylko jak najade na element a nie na sama liste? Bo jak mam dluga liste i jeden element to jak tylko najade liste to mi sie wyswietla ten element.
@Radosław Głębicki: moglbys rozwinac ten bbox? W sensie jak powinien wygladac kod? Ja znam tkinter
od 3 tygodni wiec niestety nic mi to jeszcze nie mowi :(
import tkinter as tk
from tkinter import Text,Listbox,Label
tkWidget=tk.Tk()
tkWidget.minsize(500,400)
tkWidget.title("List tooltips")
tkWidget.geometry("600x700+100+100")
tkListWidget=Listbox(height=9,width=30)
lListOver = [-1,None,None] #[0] dla indeksu,[1] dla okienka tt,[2] ew. dla obj. .after
lListElms = ['aaa','bbbbb','ccc','ddd','eee','fff','ggg','hhh','iii','jjj','kkk','lll']
e = 'Element '
lListCom = []
for elm in lListElms:
lListCom.append(e + elm)
for elm in lListElms:
tkListWidget.insert(tk.END,elm)
def f_coord(*d):
global lListOver
xEnrPos,yEnrPos = d[0].x,d[0].y
idxOver = tkListWidget.nearest(yEnrPos)
tLast = tkListWidget.bbox(len(lListElms)-1) # tupla ostatniego elementu listy
if tLast is not None:
yDownLast = tLast[1] + tLast[3] # sumujemy y elm. z wysokością elm.
if yEnrPos>yDownLast:
if lListOver[1] is not None:
f_resetnListOver(None)
return None
if idxOver != lListOver[0]:
sX,sY = str(tkWidget.winfo_pointerx()+15),str(tkWidget.winfo_pointery()+15)
if lListOver[1] is not None: lListOver[1].destroy()
lListOver[1] = tk.Toplevel(tkWidget)
lListOver[1].wm_geometry("+"+sX+"+"+sY)
lListOver[1].wm_overrideredirect(True)
Label(lListOver[1],text=lListCom[idxOver],
bd=1,justify=tk.LEFT).pack(padx=2,pady=2)
lListOver[0] = idxOver
return None
def f_resetnListOver(*d):
global lListOver
if lListOver[1] is None: return None
lListOver[0] = -1
lListOver[1].destroy()
lListOver[1] = None
lListOver[2] = None
return None
tkListWidget.bind('<Motion>',f_coord)
tkListWidget.bind('<Leave>',f_resetnListOver)
tkListWidget.pack()
tkWidget.mainloop()
tkWidget.quit()
Pozdrawiam
Radek
@Radosław Głębicki: Dzieki, mega mi pomogles!