Tooltip dla każdego elementu z Listbox w Tkinter

0

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?

image

1

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

1

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

1

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

2

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

1

@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 :(

2
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

0

@Radosław Głębicki: Dzieki, mega mi pomogles!

1 użytkowników online, w tym zalogowanych: 0, gości: 1