Tkinter active state - python

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)

Related

How to disable the movement of the Tkinter Window without removing the title bar

I have been creating an application for taking the test. So, for that, I have to do two things.
First, disable the drag of the Tkinter window and don't let the user focus on other windows rather than my application window. This means I wanted to make my application such that, No other application can be used while my application is in use.
Try this:
import tkinter as tk
class FocusedWindow(tk.Tk):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Force it to be unminimisable
super().overrideredirect(True)
# Force it to always be on the top
super().attributes("-topmost", True)
# Even if the user unfoceses it, focus it
super().bind("<FocusOut>", lambda event: self.focus_force())
# Take over the whole screen
width = super().winfo_screenwidth()
height = super().winfo_screenheight()
super().geometry("%ix%i+0+0" % (width, height))
root = FocusedWindow()
# You can use it as if it is a normal `tk.Tk()`
button = tk.Button(root, text="Exit", command=root.destroy)
button.pack()
root.mainloop()
That removed the title bar but you can always create your own one by using tkinter.Labels and tkinter.Buttons. I tried making it work with the title bar but I can't refocus the window for some reason.
One way to do this is by the following, another could be to overwrite the .geometry() method of tkinter.
In the following code I simply had get the position by using winfo_rootx and winfo_rooty. After this you can force the window by calling the geometry method via binding the event every time the window is configured.
import tkinter as tk
def get_pos():
global x,y
x = root.winfo_rootx()
y = root.winfo_rooty()
def fix_pos():
root.bind('<Configure>', stay_at)
def stay_at(event):
root.geometry('+%s+%s' % (x,y))
root = tk.Tk()
button1 = tk.Button(root, text='get_pos', command=get_pos)
button2 = tk.Button(root, text='fix_pos', command=fix_pos)
button1.pack()
button2.pack()
root.mainloop()

I get the error _tkinter.TclError: bad window path name ".!button" when I destroy the button

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.

Python Tkinter How use bindings correctly?

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.

Entry widget contents not changing with function call

I need to change the content of an entry whenever the tkinter frame is shown. Below is what I have so far, and it doesn't seem to work. I have tried to use data = self.read() and then now.insert(0, data) and that has not worked either. If the value is displayed then it doesn't get changed every time the class ReadLabel1 is called.
class ReadLabel1(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent, bg="blue")
label = tk.Label(self, text="SomeData:", font = "Times 12", bg="blue")
label.place(x=10, y=100) #ack(pady=5,padx=30)
self.smStr = tk.StringVar()
now=tk.Entry(self, width=22, textvariable=self.read())
now.place(x=120, y=103)
def read(self):
# code to get data
return data
You need to turn 'change the content of an entry' into a one parameter callback, turn 'whenever the tkinter frame is shown' into an event, and then bind together the app, the event, and the callback. Here is a minimal example.
import time
import tkinter as tk
root = tk.Tk()
now = tk.StringVar()
lab = tk.Label(root, textvariable=now)
lab.pack()
def display_now(event):
now.set(time.ctime())
root.bind('<Visibility>', display_now)
root.bind('<FocusIn>', display_now)
Minimizing the window to a icon and bringing it back up triggers the Visibility event. Covering and merely uncovering with a different window did not, at least not with Windows. Clicking on the uncovered, or merely inactivated, window triggered FocusIn. You can experiment more with your system. I used this tkinter reference

In tkinter, how to make custom child widget receive mouse scroll event?

I can't sort out how to make a custom widget receive mouse scroll events. If I bind to the root window, then notifications occur. And if I bind to a child of root window other than my widget (here a simple Listbox), the notifications also occur (evidenced by watching the list move when I move the wheel). What am I overlooking?
Example code where roll() never gets called:
#!/usr/bin/python3
from tkinter import *
from tkinter.ttk import *
class CustomWidget(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.width = 200
self.height = 200
self.canvas = Canvas(self, width=200, height=200)
self.canvas.config(background='red')
self.canvas.pack()
self.bind('<MouseWheel>', self.roll)
self.bind('<Button-4>', self.roll)
self.bind('<Button-5>', self.roll)
def roll(self, event):
print("detected mouse roll!");
if __name__ == "__main__":
root = Tk()
root.wm_title("TestRoot")
sb = Scrollbar(root, orient=VERTICAL)
lb = Listbox(root, yscrollcommand=sb.set)
sb.config(command=lb.yview)
cw = CustomWidget(root)
for char in list("abcdefghijklmnopqrstuvwxyz"):
lb.insert(END, char)
cw.pack()
lb.pack()
sb.pack()
root.update()
root.mainloop()
So in order for a frame to receive events, it needs to have focus. You can call frame.set_focus() on it, but as soon as you give another widget focus it won't work. To get around that we could bind <Button-1> to the frame and have that set the focus to the frame, but your canvas takes up the entire size of the frame, so You will need to bind the <Button-1> events to that instead.
Adding:
self.canvas.bind("<Button-1>", lambda _: self.focus_set())
after your other bindings in CustomWidget.__init__ will make your bindings work as long as the widget has focus, which it will when the user clicks it (similar how to the listbox works). If the canvas is never as large as it's frame, you may need to add another <Button-1> binding to the frame.

Categories

Resources