The idea was show in label where my cursor(line.column) is. Which work with .index(INSERT) well but if i bind the right mouse button with text it returns the previous cursor position not the current.
It seems that callback is executed after event?
from tkinter import Tk, Text, Frame, Label, StringVar, constants, END, INSERT
EXPL_TEXT = "I know that dress is karma. Perfume regret\nYou got me thinking bout"
class App(Frame):
def __init__(self,master):
Frame.__init__(self,master)
self.pack()
self.var = StringVar()
self.init_widgets()
def init_widgets(self):
self.text = Text(self)
self.text.bind('<Button-1>',self.callback_index)
self.text.pack()
self.text.insert(END,EXPL_TEXT)
self.label = Label(self, textvariable=self.var)
self.label.pack()
def callback_index(self,event):
x = self.text.index(INSERT)
self.var.set(x)
if __name__ == '__main__':
root = Tk()
app = App(root)
root.mainloop()
The issue I believe you are seeing is that when you click your mouse down the event fires to read INSERT. The problem is before you lift your mouse the location will still have the value of the previous INSERT. So in order for you to get the update after the event has completed we can use after() to wait for the event to finish and then set the value for self.var.
Change your callback_index method to:
def callback_index(self,event):
root.after(0, lambda: self.var.set(self.text.index(INSERT)))
What we are doing is telling python to schedule something to happen after a set time. I believe (Correct me if I am wrong) Because an event is in progress it waits until that event finishes to perform the action in the after() method.
We use lambda to create an anonymous function to update your self.var variable and all should work as intended.
Related
from tkinter import *
master=Tk()
class check:
def __init__(self,root):
self.root=root
self.b1=Button(root,text="Click me",command=self.undo)
self.b2=Button(root,text="Again",command=self.click)
def click(self):
self.b1.place(relx=0.5,rely=0.5)
def undo(self):
self.b1.destroy()
self.b2.place(relx=0.2,rely=0.2)
c=check(master)
c.click()
master.mainloop()
This is my code. I get _tkinter.TclError: bad window path name ".!button" error only when I use destroy method. But I want to delete previous button when another button appears.What should I do?
What are you doing? When you click the "Click me" button (and call the self.undo method, where the self.b1 button is destroyed) and then click the "Again" button (and call the self.click method, which tries to place already destroyed self.b1 button), you get the error, that the button does not exist. Of course, it doesn't because you have destroyed it.
It looks like you meant to hide the button. If you intended to do this, then you could just use .place_forget() method (there are also .pack_forget() and .grid_forget() methods for pack and grid window managers, respectively), that hides the widget, but not destroys it, and hence you would be able to restore it again when you need.
Here is your fixed code:
from tkinter import *
master = Tk()
class check:
def __init__(self, root):
self.root = root
self.b1 = Button(root, text="Click me", command=self.undo)
self.b2 = Button(root, text="Again", command=self.click)
def click(self):
self.b2.place_forget()
self.b1.place(relx=0.5, rely=0.5)
def undo(self):
self.b1.place_forget()
self.b2.place(relx=0.2, rely=0.2)
c = check(master)
c.click()
master.mainloop()
I can also give you a piece of advice about the implementation:
1) You should write the code according to the PEP8 style; classes should be named in the CamelCase.
2) You should inherit your Tkinter app class(es) either from Tk (usage is shown below) Toplevel(the same as Tk, but use ONLY for child windows), Frame class (almost the same as for Tk, but you need to pack/grid/place that Frame in a window).
3) It's better to create the widgets in a separate function (it helps while developing complex and big apps).
4) It's recommended to write if __name__ == "__main__": condition before creating the window (if you do like this, you will be able to import this code from other modules, and the window won't open in that case).
Here is an example:
from tkinter import *
class Check(Tk):
def __init__(self):
super().__init__()
self.create_widgets()
self.click()
def create_widgets(self):
self.b1 = Button(self, text="Click me", command=self.undo)
self.b2 = Button(self, text="Again", command=self.click)
def click(self):
self.b2.place_forget()
self.b1.place(relx=0.5, rely=0.5)
def undo(self):
self.b1.place_forget()
self.b2.place(relx=0.2, rely=0.2)
if __name__ == "__main__":
Check().mainloop()
After you destroyed button b1 in the undo(self) function tkinter cannot access it anymore and will be confused when you try to place is somewhere in the click(self) function.
To make button b1 only disappear visually you could place it outside of the window instead of destroying it. To do so replace
self.b1.destroy()
with
self.b1.place(relx=-5, rely=0)
This will move the button b1 far to the left, where it cannot be seen.
When calling the click(self) function, the button will reappear, because it will be moved inside the window again.
What I'm wanting to do is detect if the mouse has moved over an object on the screen and if it has I want to display the data about the object in a separate frame. No mouse click allowed, only mouse movement, just like activewidth. I don't see and reference anywhere showing a built in feature that will allow what I'm trying to do. Am I wrong and if so what have I not seen yet. Can't research further that which I don't know about.
Edit: I did use the bad word Object when I was referring to drawn shapes on a canvas. These are lines, aka a mapping program, placed on a canvas. When I scroll over a line/road I want it pop up the name of the road on a separate frame. When I scroll over the symbol for a business I want it to bring up the name of the business and other pertinent information in the separate frame. Hence why I said activewidth is what I'm basically trying to mimic as its capturing the mouse location and then automatically recognizing something is on the screen underneath where the mouse pointer is located. If I could capture that same pointer reference...Right now as I'm editting this I'm thinking I would have to create each line with its own separate reference name...
z[0] = self.canvas.create_line()
z[1] = self.canvas.create_line()
z[2] = self.canvas.create_line()
etc. Am I wrong on this thought? Is there an easier way of doing this?
You can bind to the <Enter> and <Leave> events, which will fire whenever the mouse enters or leaves a widget.
Example:
import tkinter as tk
root = tk.Tk()
l1 = tk.Label(root, text="Hover over me",
width=40, bd=2, relief="groove",
background="lightblue")
l2 = tk.Label(root)
l1.pack(side="top", fill="x", padx=10, pady=10)
l2.pack(side="top", fill="both", expand=True)
def handle_enter(event):
event.widget.configure(background="pink")
l2.configure(text="you entered the widget")
def handle_leave(event):
event.widget.configure(background="lightblue")
l2.configure(text="")
l1.bind("<Enter>", handle_enter)
l1.bind("<Leave>", handle_leave)
root.mainloop()
If the object is a tkinter widget, bind to "<Motion>". It will only trigger when the mouse moves over the object that you bound to (or it's children). The event object will contain information about which object you were over and even the object itself.
import Tkinter as tk
class GUI(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
lbl = tk.Label(text='Label 1')
lbl.pack()
lbl.bind('<Motion>', self.motion_detected)
lbl = tk.Label(text='Thing B')
lbl.pack()
lbl.bind('<Motion>', self.motion_detected)
def motion_detected(self, event):
print('motion detected in {} at {},{}'.format(event.widget['text'], event.x, event.y))
def main():
root = tk.Tk()
root.geometry('200x200')
win = GUI(root)
win.pack()
root.mainloop()
if __name__ == '__main__':
main()
with the CURRENT tag, you can match the canvas element under the mouse
CURRENT (or “current”) matches the item under the mouse pointer, if
any. This can be used inside mouse event bindings to refer to the item
that triggered the callback.
source
so,
first you pass your information through the tags option
self.canvas.create_line(.... tags="road1")
then you bind to the <Motion> event and inside the handler you get the tags of the current ellement
ellement_id = canvas.find_withtag(CURRENT)
ellement_tags = canvas.gettags(ellement_id)
I am working on a program that needs a GUI with buttons to do certain things, as is usually the case when having questions about Buttons, but I have ran into difficulties because while you can activate functions with buttons, you cannot test wether they are currently being pressed with an if statement. I know how to use check buttons and radio buttons, but I have not found anything else remotely useful. I need to be able to tell how long they are pressed, and to do things as they are being pressed that stop when they are released. I need a way of assigning a variable that will be true while you are still holding click down over the button, and false any other time, with a normal button, not one that toggles each time you press.
It's not clear to me what you're having trouble with, so I took the liberty of coding up a little GUI that times how long a button is pressed.
import tkinter as tk
import time
class ButtonTimer:
def __init__(self, root):
self.master = root
self.button = tk.Button(self.master, text="press me") # Notice I haven't assigned the button a command - we're going to bind mouse events instead of using the built in command callback.
self.button.bind('<ButtonPress>', self.press) # call 'press' method when the button is pressed
self.button.bind('<ButtonRelease>', self.release) # call 'release' method when the button is released
self.label = tk.Label(self.master)
self.startTime = time.time()
self.endTime = self.startTime
self.button.grid(row=1, column=1)
self.label.grid(row=2, column=1)
def press(self, *args):
self.startTime = time.time()
def release(self, *args):
self.endTime = time.time()
self.label.config(text="Time pressed: "+str(round(self.endTime - self.startTime, 2))+" seconds")
root = tk.Tk()
b = ButtonTimer(root)
root.mainloop()
Note: I tested this in python 2.7 then changed the import from Tkinter to tkinter. It will probably work in 3.x, but I haven't tested it with that version.
Write a GUI application with a button labeled "Good-bye". When the
Button is clicked, the window closes.
This is my code so far, but it is not working. Can anyone help me out with my code?
from Tkinter import *
window = Tk()
def close_window (root):
root.destroy()
frame = Frame(window)
frame.pack()
button = Button (frame, text = "Good-bye.", command = close_window)
button.pack()
window.mainloop()
With minimal editing to your code (Not sure if they've taught classes or not in your course), change:
def close_window(root):
root.destroy()
to
def close_window():
window.destroy()
and it should work.
Explanation:
Your version of close_window is defined to expect a single argument, namely root. Subsequently, any calls to your version of close_window need to have that argument, or Python will give you a run-time error.
When you created a Button, you told the button to run close_window when it is clicked. However, the source code for Button widget is something like:
# class constructor
def __init__(self, some_args, command, more_args):
#...
self.command = command
#...
# this method is called when the user clicks the button
def clicked(self):
#...
self.command() # Button calls your function with no arguments.
#...
As my code states, the Button class will call your function with no arguments. However your function is expecting an argument. Thus you had an error. So, if we take out that argument, so that the function call will execute inside the Button class, we're left with:
def close_window():
root.destroy()
That's not right, though, either, because root is never assigned a value. It would be like typing in print(x) when you haven't defined x, yet.
Looking at your code, I figured you wanted to call destroy on window, so I changed root to window.
You could create a class that extends the Tkinter Button class, that will be specialised to close your window by associating the destroy method to its command attribute:
from tkinter import *
class quitButton(Button):
def __init__(self, parent):
Button.__init__(self, parent)
self['text'] = 'Good Bye'
# Command to close the window (the destory method)
self['command'] = parent.destroy
self.pack(side=BOTTOM)
root = Tk()
quitButton(root)
mainloop()
This is the output:
And the reason why your code did not work before:
def close_window ():
# root.destroy()
window.destroy()
I have a slight feeling you might got the root from some other place, since you did window = tk().
When you call the destroy on the window in the Tkinter means destroying the whole application, as your window (root window) is the main window for the application. IMHO, I think you should change your window to root.
from tkinter import *
def close_window():
root.destroy() # destroying the main window
root = Tk()
frame = Frame(root)
frame.pack()
button = Button(frame)
button['text'] ="Good-bye."
button['command'] = close_window
button.pack()
mainloop()
You can associate directly the function object window.destroy to the command attribute of your button:
button = Button (frame, text="Good-bye.", command=window.destroy)
This way you will not need the function close_window to close the window for you.
from tkinter import *
window = tk()
window.geometry("300x300")
def close_window ():
window.destroy()
button = Button ( text = "Good-bye", command = close_window)
button.pack()
window.mainloop()
You can use lambda to pass a reference to the window object as argument to close_window function:
button = Button (frame, text="Good-bye.", command = lambda: close_window(window))
This works because the command attribute is expecting a callable, or callable like object.
A lambda is a callable, but in this case it is essentially the result of calling a given function with set parameters.
In essence, you're calling the lambda wrapper of the function which has no args, not the function itself.
from tkinter import *
def close_window():
import sys
sys.exit()
root = Tk()
frame = Frame (root)
frame.pack()
button = Button (frame, text="Good-bye", command=close_window)
button.pack()
mainloop()
I'm using the slider to update my visualization, but the command updateValue is sent everytime I move the slider thumb, even for intermediate values.
Instead I want to trigger it only when I release the mouse button and the interaction is complete.
self.slider = tk.Scale(self.leftFrame, from_=0, to=256, orient=tk.HORIZONTAL, command=updateValue)
How can I trigger the function only once, when the interaction is ended ?
This is quite an ancient question now, but in case anyone stumbles upon this particular problem just use the bind() function and the "ButtonRelease-1" event like so:
import Tkinter as tk
class App:
def __init__(self):
self.root = tk.Tk()
self.slider = tk.Scale(self.root, from_=0, to=256,
orient="horizontal")
self.slider.bind("<ButtonRelease-1>", self.updateValue)
self.slider.pack()
self.root.mainloop()
def updateValue(self, event):
print self.slider.get()
app=App()
Hope this helps anyone!
You can't.
What you can do instead is have your command delay any real work for a short period of time using 'after'. Each time your command is called, cancel any pending work and reschedule the work. Depending on what your actual requirements are, a half second delay might be sufficient.
Another choice is to not use the built-in command feature and instead use custom bindings. This can be a lot of work to get exactly right, but if you really need fine grained control you can do it. Don't forget that one can interact with the widget using the keyboard in addition to the mouse.
Here's a short example showing how to schedule the work to be done in half a second:
import Tkinter as tk
#create window & frames
class App:
def __init__(self):
self.root = tk.Tk()
self._job = None
self.slider = tk.Scale(self.root, from_=0, to=256,
orient="horizontal",
command=self.updateValue)
self.slider.pack()
self.root.mainloop()
def updateValue(self, event):
if self._job:
self.root.after_cancel(self._job)
self._job = self.root.after(500, self._do_something)
def _do_something(self):
self._job = None
print "new value:", self.slider.get()
app=App()