Tkinter Binding Multiple Scrollbar's to MouseWheel - python

I'm building something that uses multiple scrollbars. This class is actually a wrapper for the tk.ScrollBar and it's used to create scrollable frames. What I want is to be able to set a "default" scroll-container.
Assume 90% of the time someone using the application would want to scroll some frame (Which we will call main_frame), and 10% of the time they would want to scroll a different frame. (normal_frame)
It would make sense to have the mousewheel scroll the normal_frame only when the user was hovering over it with the mouse, but to have the mousewheel scroll the main_frame in all instances except when hovering over normal_frame.
The problem is that whenever you call bind_all upon "<Enter>"(ing) the normal_frame, when you "<Leave>" it, the main_frame no longer scrolls. Any suggestions?
class ScrollThing(object):
def __init__(self, some_frame, default=False):
self.default = default
self.canvas = tkinter.Canvas(some_frame)
self.view_window = tkinter.Frame(self.canvas)
#things and stuff to setup scroll bar
def setup_view_window(self):
if self.default:
self.canvas.bind_all('<MouseWheel>', self.on_mousewheel)
else:
self.view_window.bind('<Enter>', self.focus_in)
self.view_window.bind('<Leave>', self.focus_out)
def focus_in(self, *args):
del args
# I'm a seperate ScrollThing from main_frame
# I don't have access to the other ScrollThing because
# we are both dynamically created.
# I want to save the current bound arguments somehow
# but .bindtags() returns only MY bindings
self.canvas.bind_all('<MouseWheel>', self.on_mousewheel)
def focus_out(self, *args):
self.canvas.unbind_all('<MouseWheel>', self.on_mousewheel
def on_mousewheel(self, event):
self.canvas.yview_scroll(int(-1*(event.delta//120)), 'units')

I just found a solution involving handing the function and the canvas back to the Master.
class Master(object)
def __init__(self, *args, **kwargs):
#Things and stuff
self.default_scroll = None
Then by setting default_scroll to hold the default parameters
def setup_view_window(self):
if self.default:
self.canvas.bind_all('<MouseWheel>', self.on_mousewheel)
# HERE
self.dynamic.stuff.MASTER.default_scroll = [self.canvas, self.on_mousewheel]
else:
self.view_window.bind('<Enter>', self.focus_in)
self.view_window.bind('<Leave>', self.focus_out)
You can then access it in the focus_out.
def focus_out(self, *args):
self.canvas.unbind_all('<MouseWheel>', self.on_mousewheel)
# HERE
if self.dynamic.stuff.MASTER.default_scroll is not None:
self.dynamic.stuff.MASTER.default_scroll[0].bind_all('<MouseWheel>', self.dynamic.stuff.MASTER.default_scroll[1])
Although, I would be interested to know if anyone knows of a way to access it in a more concise way, perhaps something in tkinter that lets you see all bound events in the entire application?

Arguably the simplest solution is to set a variable to be the widget you want to scroll, and then set a global binding to scroll that widget.
For example, your app would initialize the binding like this exactly once:
self.root.bind_all("<MouseWheel>", self.scroller)
Then, in scroller you scroll the default window:
def scroller(self, event):
self.default_window.yview(...)
You can also add the binding to the canvases themselves instead of calling bind_all if you prefer. The basic concept still works without having to adjust the bindings on enter/leave events.

Related

Object oriented Tkinter, best way to communicate between widgets in gui with many frames

I am trying to figure out what the best way to communicate between different widgets is, where the widgets are custom classes inheriting from tkinter widgets and I have several frames present (to help with layout management). Consider for example the following simple gui (written for python 3, change tkinter to Tkinter for python 2):
from tkinter import Frame,Button,Tk
class GUI(Frame):
def __init__(self, root):
Frame.__init__(self,root)
self.upper_frame=Frame(root)
self.upper_frame.pack()
self.lower_frame=Frame(root)
self.lower_frame.pack()
self.upper_btn1 = Button(self.upper_frame, text="upper button 1")
self.upper_btn2 = Button(self.upper_frame, text="upper button 2")
self.upper_btn1.grid(row=0,column=0)
self.upper_btn2.grid(row=0,column=1)
self.lower_btn = CustomButton(self.lower_frame, "lower button 3")
self.lower_btn.pack()
class CustomButton(Button):
def __init__(self,master,text):
Button.__init__(self,master,text=text)
self.configure(command=self.onClick)
def onClick(self):
print("here I want to change the text of upper button 1")
root = Tk()
my_gui = GUI(root)
root.mainloop()
The reason I put them in different frames is because I want to use different layout managers in the two different frames to create a more complicated layout. However, I want the command in lower_btn to change properties of eg upper_btn1.
However, I can not immediately access upper_btn1 from the instance lower_btn of the customized class CustomButton. The parent of lower_btn is frame2, and the frame2 parent is root (not the GUI instance). If I change the parent of lower_btn to the GUI instance, ie
self.lower_btn = CustomButton(self, "lower button")
it will not be placed correctly when using pack(). If I change the parent of frame1/frame2 to the GUI instance, ie
self.upper_frame=Frame(self)
self.upper_frame.pack()
self.lower_frame=Frame(self)
self.lower_frame.pack()
I could in principle access the upper_btn1 from lower_btn by self.master.master.upper_btn1, BUT then the frame1/frame2 are not placed correctly using pack() (this I don't understand why). I can of course pass the GUI instance as separate variable, on top of master, to CustomButton, ie something like
class CustomButton(Button):
def __init__(self,master,window,text):
Button.__init__(self,master,text=text)
self.window=window
self.configure(command=self.onClick)
def onClick(self):
self.window.upper_btn1.configure(text="new text")
and then change the construction of lower_btn to
self.lower_btn = CustomButton(self.lower_frame,self, "lower button 3")
but is that the "correct" way of doing it?
So, what is the best way to reorganize this gui? Should I pass GUI as a separate variable on top of the master/parent argument? Should I change the master/parent of the buttons and/or the frames (and somehow fix the issues with the layout management)? Or should I make the commands of the buttons as methods of the GUI instance so they can communicate with the widgets in that GUI, although this does not really feel like object oriented programming? I can't seem to find a working object oriented design that feels "correct". Also, an explanation why pack() does not work for frame1/frame2 if I make the GUI instance (self) the master, would also be appreciated, as that would have been my intuitive, most object oriented, approach.
Edit: Maybe the best way is to not use frames inside frames at all, and use only grid() and then use colspan/rowspan to give the layout management more flexibility.
A year late, but: One way to communicate between widgets is to instantiate some kind of control center object that receives knowledge about the state of your widgets and then compels other widgets to act on that information. This 'manager' functionality will be independent of the layout of your widgets.
Here's an implementation that's customized to your example. The idea is to add a .manager attribute to the lower button, and to notify this manager when clicked. The GUI class remains unchanged.
from tkinter import Frame,Button,Tk
class Manager(object):
def __init__(self, gui):
self.gui = gui
self.gui.lower_btn.manager = self
def onClick(self):
self.gui.upper_btn2.configure(text="changed text")
class GUI(Frame):
def __init__(self, root):
Frame.__init__(self,root)
self.upper_frame=Frame(root)
self.upper_frame.pack()
self.lower_frame=Frame(root)
self.lower_frame.pack()
self.upper_btn1 = Button(self.upper_frame, text="upper button 1")
self.upper_btn2 = Button(self.upper_frame, text="upper button 2")
self.upper_btn1.grid(row=0,column=0)
self.upper_btn2.grid(row=0,column=1)
self.lower_btn = CustomButton(self.lower_frame, "lower button 3")
self.lower_btn.pack()
class CustomButton(Button):
def __init__(self,master,text):
Button.__init__(self,master,text=text)
self.configure(command=self.onClick)
self.manager = None
def onClick(self):
if self.manager:
self.manager.onClick()
else:
print("here I want to change the text of upper button 1")
root = Tk()
my_gui = GUI(root)
Manager(my_gui)
root.mainloop()

How can I unbind every single binding from a Tkinter root window

So I have an app in Tkinter that has a lot of buttons in the first screen and when you press one you pass into a new "Window" (basically destroying all widgets and drawing the ones that are needed for the 'window'). There is a standard function that uses some commands to destroy every child on the root. I would like to add some code that can unbind all of the bindings that are made in the root. Bindings that are on specific widgets get destroyed but those that are bind on the root stay there and cause error.
Here's the code for destroying the widgets.
#staticmethod
def clear():
for widget in guihandle.root.winfo_children():
widget.destroy()
#staticmethod
def set(db,table):
guihandle.clear()
curW = Window(db,table)
guihandle.current_Window = curW
curW.initialize()
guihandle.windows.push(curW)
(Yes, I make the base GUI from a sqlite3 database :P)
There is nothing in Tkinter to do what you want. Your app will need to keep track of the bindings it wants to remove.
That being said, depending on just how complex your real problem is, there may be other solutions. For example, instead of binding to the root window, bind to a custom binding tag (also called a bind tag or bindtag). You will then need to add that tag to every widget to enable the bindings, and remove the tag from any existing widgets to effectively disable the bindings.
I know I am VERY late but, if you go through your code and replace every widget.bind with the function below, which was taken and modified from here
def bindWidget(widget: Widget, event, all:bool=False, func=None):
# https://stackoverflow.com/a/226141/19581763
'''Set or retrieve the binding for an event on a widget'''
try:
_ = widget.__dict__['bindings']
except KeyError:
has_binding_key = False
else:
has_binding_key = True
if not has_binding_key:
setattr(widget, 'bindings', dict())
if func:
if not all:
widget.bind(event, func)
else:
widget.bind_all(event, func)
widget.bindings[event] = func
else:
return(widget.bindings.setdefault(event, None))
Than the function will keep track of every binding for you by setting a attribute called bindings which has both, the event and the function inside of it.
For example:
label.bind('<Button-1>', label.destroy)
Will become:
bindWidget(label, '<Button-1>', func=label.destroy)
After doing that, you can write a simple function which deletes all of the bindings and widgets:
def clear(self): # Self will be your Tk() instance
"""Clears everything on the window, including bindings"""
for child in self.winfo_children():
child.destroy()
if not hasattr(self, 'bindings'):
self.bindings = {}
for event, func in self.bindings.items():
self.unbind_all(event)
self.bindings = {}
There are only 2 caveats with this approach
Time
You will have to go through your code to replace every widget.bind and if you have lots of bindings, than it will take a lot of time
Readability
bindWidget(label, '<Button-1>', func=label.destroy) is less readable and less clear than label.bind('<Button-1>', label.destroy)
Edit 8/11/2022
Alternatively, you can destroy the entire window and recreate it like this:
window.destroy()
window = Tk()
However, I am not sure if creating a whole Tcl interpreter is good.

How to derive a Tkinter widget?

I am trying to enhance the Tkinter Text widget and provide additional capabilities.
One of the key features of my new widget is to hack the arrows/keyboard key strokes (syntax highlighting, auto-complete context menus).
I tried a direct approach of binding the Key and Up/Down keystrokes to methods, but this approach failed as my method handlers were executed before the Text event handlers therefore my methods were executed before the last keystroke handled by the text itself.
class Editor(Frame):
def __init__(self, parent, *args, **kwargs):
Frame.__init__(self, parent) # initialize the base class
# The Main Text Widget
self.text = scrolledtext.ScrolledText(self, *args, **kwargs)
self.text.pack(side='left', fill='both', expand=1)
self.text.bind("<Key>", lambda event: self.editor_key_handler())
self.text.bind("<Up>", lambda event:self.editor_arrow_key_handler('Up'))
self.text.bind("<Down>", lambda event: self.editor_arrow_key_handler('Down'))
I then tried to switch the bindtag order, and make the Class handler run first, then my instance handlers - this indeed fixed the original issue:
bindtag = list()
bindtag.append(self.text.bindtags()[1])
bindtag.append(self.text.bindtags()[0])
bindtag.append(self.text.bindtags()[2])
bindtag.append(self.text.bindtags()[3])
self.text.bindtags(bindtag)
But now as my handlers were running after the Text's... my Up / Down handlers were running after the insert cursor has already moved inside the Text losing the original position where the user clicked the Up/Down arrows.
Being an experienced Perl Tk programmer, I moved along to try and derive the Tkinter Text widget to allow me to hijack the Up/Down default handler and provide my own customised methods..
I didn't find a way to simply derive and override the Text widget (something seems trivial to an OOP system.)
How this can be done?
The best way to add custom event handling to a widget is to leave the bind tags alone, and simply add your own bindings that return the literal string "break". Returning "break" will prevent the default bindings from firing.
In the following example I've added a custom handler for the up arrow, inserting "" rather than doing the default behavior. Notice that the handler returns the string "break":
import tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.text = tk.Text(root)
self.text.pack(fill="both", expand=True)
self.text.bind("<Up>", self.handle_up)
def handle_up(self, event):
self.text.insert("insert", "<up>")
return "break"
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
For a lengthy description of what happens when a key is handled, see this answer: https://stackoverflow.com/a/11542200/7432. The answer is to a question about the Entry widget, but event processing is the same for all widgets.

pyqtgraph Lock dock layout

Is there a way to lock docks in pyqtgraph so that the user cannot move them around?
I'm using a small touchscreen to display a pyqtgraph application with multiple docks. It is very easy for the user to accidentally move a dock. When that happens the screen becomes unusable because of the size. I would like to prevent the user from moving the docks.
However, the user must still be able to choose between docks (i.e. treat them like a tab widget).
Just to be clear, I want to prevent a dock from being detached and I want to prevent the dock from being drug to a new location.
Thanks,
Chris
I managed to disable the ability to detach and drag docks by overriding the Dock class' methods.
Dragging a dock moves it to another location. So I overrode all of the 'drag' event handlers with methods that do nothing (i.e. a no-op).
Double-clicking on a dock's label will cause the dock to detach. So, I overrode the dock's label's double-click event handler with a no-op.
Replace Dock with MyDock in your code. UPDATE: I added code to override the drag methods for the DockArea too because I was still able to move DockAreas around.
Here is the code:
##
# This class is used to eliminate a standard Dock class' ability to detach and
# move (i.e. dragging this Dock will have no effect)
#
class MyDock(Dock):
def __init__(self, name, area=None, size=(10, 10), widget=None, hideTitle=False, autoOrientation=True):
# Initialize the baseclass
#
Dock.__init__(self, name, area, size, widget, hideTitle, autoOrientation)
# Override the label's double click event. Normally double clicking
# the dock's label will cause it to detach into it's own window.
#
self.label.mouseDoubleClickEvent=self.noopEvent
def dragEventEnter(self, ev):
pass
def dragMoveEvent(self, ev):
pass
def dragLeaveEvent(self, ev):
pass
def dragDropEvent(self, ev):
pass
def noopEvent(self,ev):
pass
class MyDockArea(DockArea):
def dragEventEnter(self, ev):
pass
def dragMoveEvent(self, ev):
pass
def dragLeaveEvent(self, ev):
pass
def dragDropEvent(self, ev):
pas

maya close window signal

I have created a ui from scratch using the commands within Maya documentation.
The following function that I have wrote applies in two scenerios:
When the user has clicked onto another button - Import, in which it will do as what it was written in the code then close it off with the following function (see readFile function)
When user has clicked onto the button where it close the UI without running anything.
In my script, to cater the above two scenarios, I wrote as the following where closeWindow is catered to Scenario1 and cancelWindow is catered to Scenario2
def ui(self):
...
cancelButton = cmds.button( label='Cancel', command=self.cancelWindow, width=150, height=35)
def closeWindow(self, *args):
cmds.deleteUI(self.window, window=True)
def cancelWindow(self, *args):
cmds.delete(camSel[0])
cmds.deleteUI(self.window, window=True)
def readFile(self, *args):
...
self.closeWindow()
As such, is it possible to create some sort of signal like those in PyQt (clicked(), returnPressed() etc) by combining the above 2 (automated + manual), seeing that the deleteUI command usage is the same?
Default Maya UI provides only callbacks, not signals. You can create a sort of 'pseudo signal' by calling an event handler object instead of a function. In that scenario the button only knows 'I fired the button event' and the handler can call as many functions as needed.
class Handler(object):
def __init__(self):
self.handlers = []
def add_handler (self, func):
self.handlers.append(func)
def __call__(self, *args, **kwargs):
for eachfunc in handler:
eachfunc(*args, **kwargs)
hndl = Handler()
hndl.add_handler(function1) # do some ui work...
hndl.add_handler(function2) # do some scene work...
hndl.add_handler(function3) # do something over network, etc....
b = cmds.button('twoFunctions', c = Hndl)
In a large complex UI this is a nice way to keep minor things like button higlights and focus changes separated out from important stuff like changing the scene. In your application it's almost certainly overkill. You've only sharing 1 line between close and cancel, that's not too bad :)
Heres' more background on on pseudo-events in maya gui.
You can also use Maya's QT directly to get at the close event... Again, seems like overkill. More here

Categories

Resources