Tkinter bind mouse clicks to frame - python

I must be missing something obvious, I have two frames in my Tkinter program, each with a bunch of labels in a grid layout. I want to bind the mouseclick to one of them but not the other. I currently use
root.bind("<Button-1>", mouse_function)
but that also triggers if I click in the other frame. I assumed that using
schedule_frame.bind("<Button-1>", mouse_function)
would work but then I get no response anywhere.
The function I am calling is:
def mouse_function(event):
y = event.widget.grid_info()['row']
x = event.widget.grid_info()['column']
widgets[(y, x)].configure(state="active")
shiftSelection(y,x)

When you bind to the root window, that binding applies to all widgets in that root window. That is why it triggered for either frame. This is standard behavior for tkinter.
When you move the binding to the frame, it stopped working because the frame never saw the event. When you click on the label, it is the label that sees the binding, not the frame (unless you click in the space between labels)
There are at least three ways to solve this problem. One is that you can put the binding on the labels rather than the frame. Another is to keep the binding on the root window, but within the function check to see if the widget is a child of that one frame.
A third solution involves changing the bind tags for the labels. For an in depth example see this answer: https://stackoverflow.com/a/32771893/7432

Related

Detect when a widget change from visible to invisible or the opposite [duplicate]

This question already has an answer here:
Event binding to detect a grid_remove (Tkinter)
(1 answer)
Closed 2 years ago.
Is it possible to be informed when we a widget which was visible become invisible .pack_forget() or an invisible widget become visible .pack() ?
Something Like button.bind("<Visible>", func_triggered_when_the_button_become_visible)
I want to hide and show entire frames and when I hide the widgets inside, I want their values to be reset.
I think <Map> is what your looking for, as per acw1668 suggestions. Here is an example to make you understand better:
from tkinter import *
from tkinter import messagebox
root = Tk()
def check(event): #function to be triggered only when the button is visible
messagebox.showinfo('Visible','Seeing this message only because button is visible')
b = Button(root,text='Im going to disappear') #making button but not packing it(invisible)
b1 = Button(root,text='Click me to make the button disappear',width=50,command=lambda: b.pack_forget()) #to hide the button(to make invisible)
b1.pack(padx=10)
b2 = Button(root,text='Click me to make the button appear',width=50,command=lambda: b.pack()) #to show the button(to make visible)
b2.pack(padx=10)
b.bind('<Map>',check) #every time button is visible, check() is triggered
root.mainloop()
Ive commented to understand this better, do let me know if any doubts.
A bit more on <Map> from the docs:
The Map and Unmap events are generated whenever the mapping state of a window changes.
Windows are created in the unmapped state. Top-level windows become mapped when they transition to the normal state, and are unmapped in the withdrawn and iconic states. Other windows become mapped when they are placed under control of a geometry manager (for example pack or grid).
A window is viewable only if it and all of its ancestors are mapped. Note that geometry managers typically do not map their children until they have been mapped themselves, and unmap all children when they become unmapped; hence in Tk Map and Unmap events indicate whether or not a window is viewable.

Refresh entire tkinter gui?

I've got a massive program that works flawlessly EXCEPT that I'm an idiot and all of the information is displayed via labels on the master/root window. When I remove one of the labels from the database it is pulling from it still shows the text as if the information still exists. If I close and reopen the entire program it loads the information correctly SO...
Rather than basically rewriting everyhing, within my update function I've placed:
global master
master.destroy()
master = Tk()
master.title('Encounter Manager')
And now the phantom information no longer shows, but it does exactly what you'd expect and closes/reopens the window. Is there a way to accomplish this without actually closing and reopening the window? It's annoying but still preferred to seeing the phantom information that I had since removed....
One solution is to add frame in the root window to hold the labels. So, root window -> frame -> labels. Then, you can destroy the frame and start over.
Another solution would be to iterate over the children of the root window and destroy them one by one:
for widget in root.winfo_children():
widget.destroy()

Issues with tkinter grid and pack in separate classes

So I've been working on a group project, some of use used pack and others used grid as a layout manager, I'm making the part of the application that puts everyones code together.
I've been working on a UI using pack, and what I want it to do is when I click on a button, a new tk.Tk() window is launched which then runs its code that is managed by grid.
Here is a snipped of the code to try and show you what I'm doing, I keep getting the error "cannot use the geometry manager grid inside . which already has slaves managed by pack"
def launchQuest(self, questType):
if(questType == "ham"):
ham = tk.Tk()
ham.configure(background='white')
app = HM(ham)
ham.mainloop()
If you need to see more code just ask, the whole class is around 400 lines so far but I don't think it is relevant.
Any help would be great!
Thanks!
Based on my first comment above, the answer is:
There should be only one Tk() root window. If you want other windows,
use Toplevel widget.
Only one type of positioning (grid, pack, or place) can be used at a time, within a container. Tk() gives you a window (Toplevel) which you use to contain other widgets, some of which can be containers themselves, like Frame, for example. You can pack two frames into a window, but you could not pack one frame and place another into the same window. This limitation only applies one level deep – you could place a frame, and then pack a frame inside that, and then grid inside that, if you wanted. It doesn't matter what method was used to position the container, only at the level of things directly contained by that container.

How to clear window with tkinter

Suppose I have a lovely window full of tkinter widgets all set with a function. One of these many widgets is a button. When this button is pressed, I want to 'move on to the next screen'. The next screen is in another function(including all the widgets I want to appear on that screen). I have tried to simply run the next procedure from the button, but If it does run correctly, it only adds the widgets to the existing window, and you end up with both screen#1 and screen#2 jumbled together. I have a feeling I need to use destroy, but I'm not sure how to do such, as the only way I could come up with was to group all the widgets in window 1 together in a frame, and destroy it, but I cant get access to destroy the frame from within function #2, as its a variable only within function/window #1. Sorry if that's confusing, The other option is the source, but there's a ton of widgets and other windows in progress which leads me to believe that would be even more confusing.
The simplest thing is to have your function create a single frame, and then place all of the widgets in that frame. The frame can then be placed in the main window such that it fills the whole window. Then, to delete everything you simply need to delete that one frame.
Another way to "move on to the next screen" is to use this same method, but create all of the frames ahead of time. You can stack these frames on top of each other, and use lift and/or lower to determine which one is on top. The one on top will obscure the ones below.
For an example of stacking, see Switch between two frames in tkinter
As for the problem of frame2 not knowing how to destroy frame1, you simply need to pass in a reference to the existing frame when creating a new frame, or pass in a reference to a "controller" - a function that knows about all the frames. You then ask the controller to delete the current frame, and the controller will know what the current frame is.
A button calling a function that deletes all existing frames and rebuilds another sounds like a design flaw. The propensity for errors (forgetting to delete certain elements in some places of the code etc) is pretty large.
If you don't have an insane number of UI elements, I suggest creating them all at once, and hiding/showing various elements as necessary.
Take a look at this SO answer for how you might go about creating GUI elements that can be shown/hidden, and how the callback function might look.
Edit: If you really need to do it based on these functions, then I guess an alternative approach might be this:
Say 'top_frame' is the frame that includes all your widgets which you want to destroy when you run function #2. Change all of your GUI elements in function #1 so that when you create them, you explicitly pass them top_frame so that they have a link to it (self.top_frame = top_frame). This means your button will also have an attribute self.top_frame. You pass that as one of the arguments to function #2, and function #2 now can refer to top_frame and destroy it.
But definitely prone to error and probably slower due to all the creation/destruction of GUI elements. I recommend going through the code in the answer above when you have the time, it really is a much better solution.

Clear Window in Tkinter

I have a tkinter GUI that, when opened, is an empty widget with a menu bar on top. Clicking on the options on the menu causes for text boxes, more buttons and more menus to appear in the widget. My problem is that everything overlaps.
I need an easy way to delete everything that is currently there, like a "clear window" option.
I've tried packing everything into a frame and then destroying a frame but, for some reason, it's not working.
Any suggestions?
Ideally, I would make something that checks to see if there is anything (button, text box, menu) in a designated space, and it would delete it all before creating the new widget attributes.
The Problem I was having was fixed by creating a new frame within each function and having the code destroy any previously existing frames with Frame.destroy().
My second problem, where widgets within my frame were not, appearing was being caused by how Tkinter frames automatically resize to fit the original widgets. Therefore, when I added widgets to the frame, the frame remained the size of the first widget and didn't show the new buttons. I used frame.pack_propagate(0) to force the frame to remain the specified size.

Categories