I want to create buttons labelled with names from a list. When you click on a button its relief shall change from groove to sunken. There is one condition, only one button is allowed to be sunken. Thus, when you click on a button while another one is already sunken, the sunken one has to go back to groove.
How it looks like
I was able to put my idea into action and coded the whole thing. However, I'm wondering if there might be a better way to implement it. What is your opinion? Here is my code:
import tkinter as tk
from functools import partial
class ButtonSunken:
def __init__(self):
self.tags = ('A','B','C','D','E','F')
self.buttons = []
self.win = tk.Tk()
self.create_buttons()
self.win.mainloop()
def create_buttons(self):
for j,i in enumerate(self.tags):
self.buttons.append(tk.Button(self.win, text = i))
self.buttons[-1].grid(column=0, row=j)
ho_general = partial(self.button_pressed, self.buttons[-1])
self.buttons[-1].configure(command = ho_general)
def button_pressed(self, button):
try: # first time active_button does not exist yet
self.active_button.configure(relief = 'groove')
except:
pass
button.configure(relief = 'sunken')
self.active_button = button
t_object = ButtonSunken()
Thank you very much for your help!
Your method is pretty much good, just that it can be done without using any special functions. In my code, I just store the index of the current active button and set its relief to groove whenever the next button is pressed whose relief is in turn changed to sunken. Have a look at the code.
import tkinter as tk
class ButtonSunken:
def __init__(self):
self.tags = ('A','B','C','D','E','F')
self.buttons = []
self.active = None
self.win = tk.Tk()
self.create_buttons()
self.win.mainloop()
def create_buttons(self):
for j,i in enumerate(self.tags):
self.buttons.append(tk.Button(self.win, text=i, command=lambda x=j: self.button_pressed(x)))
self.buttons[-1].grid(column=0, row=j)
def button_pressed(self, idx):
if self.active is not None:
self.buttons[self.active].configure(relief='groove')
self.buttons[idx].configure(relief='sunken')
self.active = idx
t_object = ButtonSunken()
This question already has an answer here:
Passing argument to a function via a Button in Tkinter, starnge behaviour in loop [duplicate]
(1 answer)
Closed 4 years ago.
I'm trying to make a simple interface with 3 buttons, and each button should trigger an action based on its label. However, even though (I think) I'm passing the correct argument, it always passes the label of the last button. Here's a stripped-down version to show what's happening:
import tkinter as tk
import random
class Application(tk.Frame):
def __init__(self, window=None):
super().__init__(window)
self.labels = ['Washington','London','Paris','Rome','Berlin','Madrid']
self.buttons = [tk.Button(self),tk.Button(self),tk.Button(self)]
self.pack()
for k,button in enumerate(self.buttons):
button.config(width=10)
button.grid(row=0, column=k)
self.update_buttons()
def update_buttons(self):
labels = list(random.sample(self.labels,3))
random.shuffle(labels)
for label,button in zip(labels,self.buttons):
button["text"] = label
button["command"] = lambda: self.verify(label)
def verify(self, label):
print(f'You pressed the button with label {label}')
self.update_buttons()
window = tk.Tk()
app = Application(window=window)
app.mainloop()
Why?
You are encountering a (late biding) closure problem.
When you create a function using lambda, a closure is created. This means the variables in function's body are looked up at the time you call the lambda, not when you create it (and the scope in which lambda was created will contain variable with the final value that was assigned).
In order to prevent this, you need to create an argument and set it to a default value, which stores the current value of the variable at creation time.
import tkinter as tk
import random
class Application(tk.Frame):
def __init__(self, window=None):
super().__init__(window)
self.labels = ['Washington','London','Paris','Rome','Berlin','Madrid']
self.buttons = [tk.Button(self),tk.Button(self),tk.Button(self)]
self.pack()
for k,button in enumerate(self.buttons):
button.config(width=10)
button.grid(row=0, column=k)
self.update_buttons()
def update_buttons(self):
labels = list(random.sample(self.labels,3))
random.shuffle(labels)
for label,button in zip(labels,self.buttons):
button["text"] = label
button["command"] = lambda label=label: self.verify(label) # Here
def verify(self, label):
print(f'You pressed the button with label {label}')
self.update_buttons()
window = tk.Tk()
app = Application(window=window)
app.mainloop()
You can also use functools.partial, which looks cleaner in my opinion:
import tkinter as tk
import random
from functools import partial
class Application(tk.Frame):
def __init__(self, window=None):
super().__init__(window)
self.labels = ['Washington','London','Paris','Rome','Berlin','Madrid']
self.buttons = [tk.Button(self),tk.Button(self),tk.Button(self)]
self.pack()
for k,button in enumerate(self.buttons):
button.config(width=10)
button.grid(row=0, column=k)
self.update_buttons()
def update_buttons(self):
labels = list(random.sample(self.labels,3))
random.shuffle(labels)
for label,button in zip(labels,self.buttons):
button["text"] = label
button["command"] = partial(self.verify, label)
def verify(self, label):
print(f'You pressed the button with label {label}')
self.update_buttons()
window = tk.Tk()
app = Application(window=window)
app.mainloop()
You need to assign a parameter to the lambda function, and pass it as argument to the function.
import tkinter as tk
import random
class Application(tk.Frame):
def __init__(self, window=None):
super().__init__(window)
self.labels = ['Washington','London','Paris','Rome','Berlin','Madrid']
self.buttons = [tk.Button(self),tk.Button(self),tk.Button(self)]
self.pack()
for k,button in enumerate(self.buttons):
button.config(width=10)
button.grid(row=0, column=k)
self.update_buttons()
def update_buttons(self):
labels = list(random.sample(self.labels,3))
random.shuffle(labels)
for label,button in zip(labels,self.buttons):
button["text"] = label
button["command"] = lambda lbl=label: self.verify(lbl) # <-- here
def verify(self, label):
print(f'You pressed the button with label {label}', flush=True) # <-- added flush=True to ensure the printing is done as the moment you click
self.update_buttons()
window = tk.Tk()
app = Application(window=window)
app.mainloop()
I am trying to make a large text-entry popup as part of a gui. The idea is to get paragraph-long user-input. The problem is that the method get_big_text() returns before the button is pushed. How can I have a separate window pop-up like this, and be able to save the user's text to a variable in my control program? Everything else in my program has been working out, until I tried to implement this. I am new to gui programming. I get the feeling that there is something fundamentally different about waiting for user input here, but I can't wrap my head around it in the functional context.
My goal is to have the line print(foo.get_big_text()) print the user's text, but of course it prints None because the get_big_text() method finishes.
I have left out the details of the rest of the gui, and wrote an __init__() that probably doesn't need to be there, but this is the basics of how my gui is coming along. The Toplevel widget is the only widget in my gui that is not somehow connected to root.
from tkinter import *
class Gui:
def __init__(self, root):
tframe = Frame(root)
tframe.pack(side='top')
bframe = Frame(root)
bframe.pack(side='bottom')
self.txt = Text(tframe)
self.txt.insert('0.0', 'Totally foobar')
self.txt.pack()
self.btn = Button(bframe, text='OK')
self.btn.pack()
def get_big_text(self, title='', text=''):
popup = Toplevel(height=160, width=180)
popup.title(title)
txtframe = Frame(popup)
txtframe.pack()
big_text = Text(txtframe)
big_text.insert('0.0',text)
big_text.pack()
btnframe = Frame(popup)
btnframe.pack()
grab_text = Button(btnframe)
grab_text.config(text="Done", command=lambda: big_text.get('0.0', 'end'))
grab_text.pack()
root=Tk()
root.title('Example')
foo = Gui(root)
print(foo.get_big_text())
root.mainloop()
You should pass the text to one function in your class and then do whatever you want with it (like printing):
from Tkinter import *
class Gui:
def __init__(self, root):
tframe = Frame(root)
tframe.pack(side='top')
bframe = Frame(root)
bframe.pack(side='bottom')
self.txt = Text(tframe)
self.txt.insert('0.0', 'Totally foobar')
self.txt.pack()
self.btn = Button(bframe, text='OK')
self.btn.pack()
def f(self, text):
print(text)
def get_big_text(self, title='', text=''):
popup = Toplevel(height=160, width=180)
popup.title(title)
txtframe = Frame(popup)
txtframe.pack()
big_text = Text(txtframe)
big_text.insert('0.0',text)
big_text.pack()
btnframe = Frame(popup)
btnframe.pack()
grab_text = Button(btnframe)
grab_text.config(text="Done", command=lambda: self.f(big_text.get('0.0', 'end')))
grab_text.pack()
root=Tk()
root.title('Example')
foo = Gui(root)
foo.get_big_text()
root.mainloop()
If you want to print the text after the gui finished you can do this modifications:
On Gui.f:
def f(self, text):
self.text = text
At the end of your code:
root.mainloop()
print(foo.text)
After some suggestions from #xndrme, and some hard thinking, I realized the solution is quite simple. It's just that I'm not used to programing in this functional style. Really fun to discover this, though.
I wanted the get_big_text() method to return the text so that I could pass it somewhere else and "do something" with it when the text comes. The solution was to pass an anonymous function to the method and "tell it" what should be done with it when it does come.
Note the new callback parameter in get_big_text()
from tkinter import *
class Gui:
def __init__(self, root):
tframe = Frame(root)
tframe.pack(side='top')
bframe = Frame(root)
bframe.pack(side='bottom')
self.txt = Text(tframe)
self.txt.insert('0.0', 'Totally foobar')
self.txt.pack()
self.btn = Button(bframe, text='OK')
self.btn.pack()
def get_big_text(self, callback, title='', text=''):
popup = Toplevel(height=160, width=180)
popup.title(title)
txtframe = Frame(popup)
txtframe.pack()
big_text = Text(txtframe)
big_text.insert('0.0',text)
big_text.pack()
btnframe = Frame(popup)
btnframe.pack()
grab_text = Button(btnframe)
grab_text.config(text="Done", command=lambda:callback(big_text.get('0.0', 'end')))
grab_text.pack()
root=Tk()
root.title('Example')
foo = Gui(root)
foo.get_big_text(lambda x:print(x))
root.mainloop()
The general flow for a dialog is to create the window, then call wait_window to wait until the window has been dismissed by the user. Your function can then return whatever you want.
There's a bit of a chicken-and-egg thing going on, in that you need to get the value from the dialog before the dialog is destroyed since the text widget will be destroyed when the toplevel is destroyed. You do this by explicitly managing the destruction of the window (read: get the value before actually destroying the window).
Here's a working example, trying to preserve as much as code as possible but without using a global import:
import Tkinter as tk
class CustomDialog(object):
def __init__(self, parent, title="Enter a paragraph", default_text=""):
self.parent = parent
self.title = title
self.default = default_text
def show(self):
self.popup = tk.Toplevel(self.parent)
self.popup.title(self.title)
txtframe = tk.Frame(self.popup)
txtframe.pack()
self.big_text = tk.Text(txtframe)
self.big_text.insert('1.0',self.default)
self.big_text.pack()
btnframe = tk.Frame(self.popup)
btnframe.pack()
grab_text = tk.Button(btnframe)
grab_text.config(text="Done", command=self.done)
grab_text.pack()
# make sure our "done" method gets called even if the
# user destroys the window
self.popup.wm_protocol("WM_DELETE_WINDOW", self.done)
# wait for the window to be destroyed
root.wait_window(self.popup)
return self.data
def done(self, *args):
# get the data from the window, then destroy
# the window and return to the caller
self.data = self.big_text.get("1.0", "end-1c")
self.popup.destroy()
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
b = tk.Button(self, text="Get Input", command=self.go)
b.pack()
def go(self):
dialog = CustomDialog(self, default_text="totally foobar")
result = dialog.show()
print "result:", result
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
Another option that you have is to pass a callback to your dialog, and tie that callback to the "done" button. That way, whenever the user clicks the button, you execute the callback to do whatever you want with the data before destroying the window.
That is how you implement a non-modal dialog, since you don't necessarily have to destroy the window. Font dialogs are a good example of this, where you might want to keep the dialog open for quite a while, and affect whatever is currently selected.
The effbot site has a decent writeup on dialogs. See http://effbot.org/tkinterbook/tkinter-dialog-windows.htm
I have a GUI made with TKinter in Python. I would like to be able to display a message when my mouse cursor goes, for example, on top of a label or button. The purpose of this is to explain to the user what the button/label does or represents.
Is there a way to display text when hovering over a tkinter object in Python?
I think this would meet your requirements.
Here's what the output looks like:
First, A class named ToolTip which has methods showtip and hidetip is defined as follows:
from tkinter import *
class ToolTip(object):
def __init__(self, widget):
self.widget = widget
self.tipwindow = None
self.id = None
self.x = self.y = 0
def showtip(self, text):
"Display text in tooltip window"
self.text = text
if self.tipwindow or not self.text:
return
x, y, cx, cy = self.widget.bbox("insert")
x = x + self.widget.winfo_rootx() + 57
y = y + cy + self.widget.winfo_rooty() +27
self.tipwindow = tw = Toplevel(self.widget)
tw.wm_overrideredirect(1)
tw.wm_geometry("+%d+%d" % (x, y))
label = Label(tw, text=self.text, justify=LEFT,
background="#ffffe0", relief=SOLID, borderwidth=1,
font=("tahoma", "8", "normal"))
label.pack(ipadx=1)
def hidetip(self):
tw = self.tipwindow
self.tipwindow = None
if tw:
tw.destroy()
def CreateToolTip(widget, text):
toolTip = ToolTip(widget)
def enter(event):
toolTip.showtip(text)
def leave(event):
toolTip.hidetip()
widget.bind('<Enter>', enter)
widget.bind('<Leave>', leave)
The widget is where you want to add the tip. For example, if you want the tip when you hover over a button or entry or label, the instance of the same should be provided at the call time.
Quick note: the code above uses from tkinter import *
which is not suggested by some of the programmers out there, and they have valid points. You might want to make necessary changes in such case.
To move the tip to your desired location, you can change x and y in the code.
The function CreateToolTip() helps to create this tip easily. Just pass the widget and string you want to display in the tipbox to this function, and you're good to go.
This is how you call the above part:
button = Button(root, text = 'click mem')
button.pack()
CreateToolTip(button, text = 'Hello World\n'
'This is how tip looks like.'
'Best part is, it\'s not a menu.\n'
'Purely tipbox.')
Do not forget to import the module if you save the previous outline in different python file, and don't save the file as CreateToolTip or ToolTip to avoid confusion.
This post from Fuzzyman shares some similar thoughts, and worth checking out.
You need to set a binding on the <Enter> and <Leave> events.
Note: if you choose to pop up a window (ie: a tooltip) make sure you don't pop it up directly under the mouse. What will happen is that it will cause a leave event to fire because the cursor leaves the label and enters the popup. Then, your leave handler will dismiss the window, your cursor will enter the label, which causes an enter event, which pops up the window, which causes a leave event, which dismisses the window, which causes an enter event, ... ad infinitum.
For simplicity, here's an example that updates a label, similar to a statusbar that some apps use. Creating a tooltip or some other way of displaying the information still starts with the same core technique of binding to <Enter> and <Leave>.
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.l1 = tk.Label(self, text="Hover over me")
self.l2 = tk.Label(self, text="", width=40)
self.l1.pack(side="top")
self.l2.pack(side="top", fill="x")
self.l1.bind("<Enter>", self.on_enter)
self.l1.bind("<Leave>", self.on_leave)
def on_enter(self, event):
self.l2.configure(text="Hello world")
def on_leave(self, enter):
self.l2.configure(text="")
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand="true")
root.mainloop()
You can refer to this- HoverClass
It is exactly what you need. Nothing more, nothing less
from Tkinter import *
import re
class HoverInfo(Menu):
def __init__(self, parent, text, command=None):
self._com = command
Menu.__init__(self,parent, tearoff=0)
if not isinstance(text, str):
raise TypeError('Trying to initialise a Hover Menu with a non string type: ' + text.__class__.__name__)
toktext=re.split('\n', text)
for t in toktext:
self.add_command(label = t)
self._displayed=False
self.master.bind("<Enter>",self.Display )
self.master.bind("<Leave>",self.Remove )
def __del__(self):
self.master.unbind("<Enter>")
self.master.unbind("<Leave>")
def Display(self,event):
if not self._displayed:
self._displayed=True
self.post(event.x_root, event.y_root)
if self._com != None:
self.master.unbind_all("<Return>")
self.master.bind_all("<Return>", self.Click)
def Remove(self, event):
if self._displayed:
self._displayed=False
self.unpost()
if self._com != None:
self.unbind_all("<Return>")
def Click(self, event):
self._com()
Example app using HoverInfo:
from Tkinter import *
from HoverInfo import HoverInfo
class MyApp(Frame):
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.grid()
self.lbl = Label(self, text='testing')
self.lbl.grid()
self.hover = HoverInfo(self, 'while hovering press return \n for an exciting msg', self.HelloWorld)
def HelloWorld(self):
print('Hello World')
app = MyApp()
app.master.title('test')
app.mainloop()
Screenshot:
I have a very hacky solution but it has some advantages over the current answers so I figured I would share it.
lab=Label(root,text="hover me")
lab.bind("<Enter>",popup)
def do_popup(event):
# display the popup menu
root.after(1000, self.check)
popup = Menu(root, tearoff=0)
popup.add_command(label="Next")
popup.tk_popup(event.x_root, event.y_root, 0)
def check(event=None):
x, y = root.winfo_pointerxy()
widget = root.winfo_containing(x, y)
if widget is None:
root.after(100, root.check)
else:
leave()
def leave():
popup.delete(0, END)
The only real issue with this is it leaves behind a small box that moves focus away from the main window
If anyone knows how to solve these issues let me know
If anyone is on Mac OSX and tool tip isn't working, check out the example in:
https://github.com/python/cpython/blob/master/Lib/idlelib/tooltip.py
Basically, the two lines that made it work for me on Mac OSX were:
tw.update_idletasks() # Needed on MacOS -- see #34275.
tw.lift() # work around bug in Tk 8.5.18+ (issue #24570)
Here is a simple solution to your problem that subclasses the tk.Button object. We make a special class that tk.Button inherits from, giving it tooltip functionality. The same for tk.Labels.
I don't know what would be cleanest and the easiest way to maintain code for keeping track of the text that goes into the tooltips. I present here one way, in which I pass unique widget IDs to MyButtons, and access a dictionary for storing the tooltip texts. You could store this file as a JSON, or as a class attribute, or as a global variable (as below). Alternatively, perhaps it would be better to define a setter method in MyButton, and just call this method every time you create a new widget that should have a tooltip. Although you would have to store the widget instance in a variable, adding one extra line for all widgets to include.
One drawback in the code below is that the self.master.master syntax relies on determining the "widget depth". A simple recursive function will catch most cases (only needed for entering a widget, since by definition you leave somewhere you once were).
Anyway, below is a working MWE for anyone interested.
import tkinter as tk
tooltips = {
'button_hello': 'Print a greeting message',
'button_quit': 'Quit the program',
'button_insult': 'Print an insult',
'idle': 'Hover over button for help',
'error': 'Widget ID not valid'
}
class ToolTipFunctionality:
def __init__(self, wid):
self.wid = wid
self.widet_depth = 1
self.widget_search_depth = 10
self.bind('<Enter>', lambda event, i=1: self.on_enter(event, i))
self.bind('<Leave>', lambda event: self.on_leave(event))
def on_enter(self, event, i):
if i > self.widget_search_depth:
return
try:
cmd = f'self{".master"*i}.show_tooltip(self.wid)'
eval(cmd)
self.widget_depth = i
except AttributeError:
return self.on_enter(event, i+1)
def on_leave(self, event):
cmd = f'self{".master" * self.widget_depth}.hide_tooltip()'
eval(cmd)
class MyButton(tk.Button, ToolTipFunctionality):
def __init__(self, parent, wid, **kwargs):
tk.Button.__init__(self, parent, **kwargs)
ToolTipFunctionality.__init__(self, wid)
class MyLabel(tk.Label, ToolTipFunctionality):
def __init__(self, parent, wid, **kwargs):
tk.Label.__init__(self, parent, **kwargs)
ToolTipFunctionality.__init__(self, wid)
class Application(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.tooltip = tk.StringVar()
self.tooltip.set(tooltips['idle'])
self.frame = tk.Frame(self, width=50)
self.frame.pack(expand=True)
MyLabel(self.frame, '', text='One Cool Program').pack()
self.subframe = tk.Frame(self.frame, width=40)
self.subframe.pack()
MyButton(self.subframe, 'button_hello', text='Hello!', command=self.greet, width=20).pack()
MyButton(self.subframe, 'button_insutl', text='Insult', command=self.insult, width=20).pack()
MyButton(self.subframe, 'button_quit', text='Quit', command=self.destroy, width=20).pack()
tk.Label(self.subframe, textvar=self.tooltip, width=20).pack()
def show_tooltip(self, wid):
try:
self.tooltip.set(tooltips[wid])
except KeyError:
self.tooltip.set(tooltips['error'])
def hide_tooltip(self):
self.tooltip.set(tooltips['idle'])
def greet(self):
print('Welcome, Fine Sir!')
def insult(self):
print('You must be dead from the neck up')
if __name__ == '__main__':
app = Application()
app.mainloop()
The best way I have found to create a popup help window is to use the tix.Balloon. I have modified it below to make it look better and show an example (note the use of tix.Tk):
import tkinter as tk
import tkinter.tix as tix
class Balloon(tix.Balloon):
# A modified tix popup balloon (to change the default delay, bg and wraplength)
init_after = 1250 # Milliseconds
wraplength = 300 # Pixels
def __init__(self, master):
bg = root.cget("bg")
# Call the parent
super().__init__(master, initwait=self.init_after)
# Change background colour
for i in self.subwidgets_all():
i.config(bg=bg)
# Modify the balloon label
self.message.config(wraplength=self.wraplength)
root = tix.Tk()
l = tk.Label(root, text="\n".join(["text"] * 5))
l.pack()
b = Balloon(root.winfo_toplevel())
b.bind_widget(l, balloonmsg="Some random text")
root.mainloop()
OLD ANSWER:
Here is an example using <enter> and <leave> as #bryanoakley suggested with a toplevel (with overridedirect set to true). Use the hover_timer class for easy use of this. This needs the widget and help-text (with an optional delay argument - default 0.5s) and can be easily called just by initiating the class and then cancelling it.
import threading, time
from tkinter import *
class hover_window (Toplevel):
def __init__ (self, coords, text):
super ().__init__ ()
self.geometry ("+%d+%d" % (coords [0], coords [1]))
self.config (bg = "white")
Label (self, text = text, bg = "white", relief = "ridge", borderwidth = 3, wraplength = 400, justify = "left").grid ()
self.overrideredirect (True)
self.update ()
self.bind ("<Enter>", lambda event: self.destroy ())
class hover_timer:
def __init__ (self, widget, text, delay = 2):
self.wind, self.cancel_var, self.widget, self.text, self.active, self.delay = None, False, widget, text, False, delay
threading.Thread (target = self.start_timer).start ()
def start_timer (self):
self.active = True
time.sleep (self.delay)
if not self.cancel_var: self.wind = hover_window ((self.widget.winfo_rootx (), self.widget.winfo_rooty () + self.widget.winfo_height () + 20), self.text)
self.active = False
def delayed_stop (self):
while self.active: time.sleep (0.05)
if self.wind:
self.wind.destroy ()
self.wind = None
def cancel (self):
self.cancel_var = True
if not self.wind: threading.Thread (target = self.delayed_stop).start ()
else:
self.wind.destroy ()
self.wind = None
def start_help (event):
# Create a new help timer
global h
h = hover_timer (l, "This is some additional information.", 0.5)
def end_help (event):
# If therre is one, end the help timer
if h: h.cancel ()
if __name__ == "__main__":
# Create the tkinter window
root = Tk ()
root.title ("Hover example")
# Help class not created yet
h = None
# Padding round label
Frame (root, width = 50).grid (row = 1, column = 0)
Frame (root, height = 50).grid (row = 0, column = 1)
Frame (root, width = 50).grid (row = 1, column = 2)
Frame (root, height = 50).grid (row = 2, column = 1)
# Setup the label
l = Label (root, text = "Hover over me for information.", font = ("sans", 32))
l.grid (row = 1, column = 1)
l.bind ("<Enter>", start_help)
l.bind ("<Leave>", end_help)
# Tkinter mainloop
root.mainloop ()
I wanted to contribute to the answer of #squareRoot17 as he inspired me to shorten his code while providing the same functionality:
import tkinter as tk
class ToolTip(object):
def __init__(self, widget, text):
self.widget = widget
self.text = text
def enter(event):
self.showTooltip()
def leave(event):
self.hideTooltip()
widget.bind('<Enter>', enter)
widget.bind('<Leave>', leave)
def showTooltip(self):
self.tooltipwindow = tw = tk.Toplevel(self.widget)
tw.wm_overrideredirect(1) # window without border and no normal means of closing
tw.wm_geometry("+{}+{}".format(self.widget.winfo_rootx(), self.widget.winfo_rooty()))
label = tk.Label(tw, text = self.text, background = "#ffffe0", relief = 'solid', borderwidth = 1).pack()
def hideTooltip(self):
tw = self.tooltipwindow
tw.destroy()
self.tooltipwindow = None
This class can then be imported and used as:
import tkinter as tk
from tooltip import ToolTip
root = tk.Tk()
your_widget = tk.Button(root, text = "Hover me!")
ToolTip(widget = your_widget, text = "Hover text!")
root.mainloop()
I am trying to recreate the boardgame monopoly using python and tkinter. I know how to place a label on a canvas or a frame, but how should I do this command is being run from another function in the class? I tried it using some function within the class Board, but then the error rises that the label, canvas, etc. are not defined as this happens in __init__(self,parent). How can I solve these errors? Or should I take a different approach to this? Hope I made my problem clear.
import tkFileDialog
from random import randint
class Board(Frame):
def __init__(self,parent):
##create the board
frame = Frame(parent)
frame.pack()
Frame.__init__(self,parent)
frame2 = Frame(frame)
frame2.pack()
c=Canvas(frame2,width=480,height=480)
c.pack(expand=YES,fill=BOTH)
c.background=PhotoImage(file='Board.gif')
c.create_image(0,0,image=c.background,anchor='nw')
##Add player 1
player1=PhotoImage(file='plane.gif')
label_player1 = Label(c,image=player1)
label_player1.image=player1
label_player1.place(x=430,y=420)
##Add player 2
player2=PhotoImage(file='car.gif')
label_player2 = Label(c,image=player2)
label_player2.image=player2
label_player2.place(x=430,y=450)
button = Button(frame, text="Next turn", command=self.next_turn)
button.pack()
button = Button(frame, text="Roll the dice", command=self.roll)
button.pack()
def roll(self):
number=randint(2,12)
if b==0:
self.place_player_down()
return number
def place_player_down(self):
for i in range(number+1):
h=int(430-i*30)
while h>=0:
player2=PhotoImage(file='car.gif')
label_player2 = Label(c,image=player2)
label_player2.image=player2
label_player2.place(x=h,y=420)
root = Tk()
board = Board(root)
board.pack()
root.mainloop()
The approach is correct (wrap your Tkinter widgets in a class with the event handler functions as methods), but you forgot to set the widgets as attributes of the class using the reference to self:
class Board(Frame):
def __init__(self,parent):
# ...
self.c = Canvas(frame2,width=480,height=480)
self.c.pack(expand=YES,fill=BOTH)
# ...
def place_player_down(self):
# Use 'self.c', not just 'c'
I think you want to do something similar with the value number, but it that case I would send it as an argument to place_player_down:
def roll(self):
number=randint(2,12)
if b==0:
self.place_player_down(number)
return number # Keep in mind that this value is returned but not used anymore
def place_player_down(self, number):
# Use 'number'