I am learning kivy from a book that involves making a paint app. The author at one point introduces new buttons on a canvas class (inherited from Widget) and says that we have to check if the click received by the application on the canvas, also lies on one of its children. In that case, we would neglect the former and act on the latter.
I thought of achieving this by looping through self.children, checking if the on_touch_down(touch) member function returns true for any child, and returning from the function if that was the case:
class CanvasWidget(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.my_color = get_color_from_hex('#FF0000')
self.line_width=2
def on_touch_down(self, touch):
for child in self.children:
if(child.on_touch_down(touch)):
return
with self.canvas:
Color(rgba=self.my_color)
touch.ud['current_line'] = Line(points=(touch.x, touch.y), width=self.line_width)
And this works fine.
However, I don't follow and would like to understand his shorthand syntax:
class CanvasWidget(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.my_color = get_color_from_hex('#FF0000')
self.line_width=2
def on_touch_down(self, touch):
#I don't understand the following line
if Widget.on_touch_down(self, touch):
return
#Widget refers to the parent class (I hope?) how does it check with the
#children?
with self.canvas:
Color(rgba=self.my_color)
touch.ud['current_line'] = Line(points=(touch.x, touch.y), width=self.line_width)
Any clarity is appreciated. Thanks!
The Widget class is the base class for all the kivy widgets, and it handles the propagation of touch events to its children via its on_touch_down() method. Rather than referencing the Widget base class directly, a better approach is to use the super() method, like this:
if super(CanvasWidget, self).on_touch_down(touch):
return True
This does the propagation and returns True (to stop the propagation) if any of the children want the propagation stopped. The code in Widget that does this is:
for child in self.children[:]:
if child.dispatch('on_touch_down', touch):
return True
Since it does not directly reference the Widget base class, the super() method is safer if you decide to change the base class of CanvasWidget.
Related
I am trying to get PyCharm to understand that the subclass of my base controller class only takes a specific type of widget.
Minimal example:
import tkinter as tk
class BaseWidgetController:
def __init__(self, parent: 'tk.Widget'): # Parent is always __some__ kind of widget
self._parent = parent
class EntryWidgetController(BaseWidgetController):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._parent: 'tk.Entry' # On this class, I want Pycharm to understand _parent is only ever an Entry (a subclass of tk.Widget), but even adding this line doesn't change its mind.
def say_type(self) -> None:
print(type(self._parent)) # PyCharm still thinks _parent is a tk.Widget
ew = EntryWidgetController(parent=tk.Frame())
ew.say_type() # Obviously this works fine at runtime.
If you want to constrain the EntryWidgetController so that it only accepts tk.Entry or subclasses, the fix is rather simple - just do
class EntryWidgetController(BaseWidgetController):
def __init__(self, parent: 'tk.Entry', **kwargs):
super().__init__(parent=parent, **kwargs)
That way
ew = EntryWidgetController(parent=tk.Frame())
will make PyCharm complain that Expected type 'Entry', got 'Frame' instead.
First important note, I'm using tkinter. Each 'child' class has some unique widgets and functions, but every child class inherits the widgets and functions from the parent class (stuff like the background colour is defined here, because it's the same on every screen). When the user clicks certain buttons, the current class' screen is destroyed, and the next class is called. With that said, if I have a parent class like this:
class parent:
def __init__(self):
def back():
if (someCondition == True):
#some algorithm to go back, by deleting the current screen and popping the previous screen off a stack.
else:
change()
#Algorithm to create main window
self.back = Button(command=back)
And a child class like this
class child(parent):
def __init__(self):
parent.__init__(self)
def change()
#algorithm to change the contents of the screen, because in this unique case, I don't want to destroy the screen and call another one, I just want the contents of this screen to change.
#Some algorithm to put unique widgets and such on this screen
How could I call the change() function from within the back() function? I tried 'child.change', but this returned an error message stating there was no attribute of 'child' called 'change'.
The solution is to make back a normal method. The parent can call methods of the child in the normal way.
class Parent(object):
def back(self):
print("in parent.back()")
self.change()
class Child(Parent):
def change(self):
print("in child.change()")
# create instance of child
child = Child()
# call the back function:
child.back()
The above code yields the following output:
in parent.back()
in child.change()
If you prefer, you can make Parent.change() throw an error, to force the child to implement it:
class Parent(object):
...
def change(self):
raise Exception("Child must implement 'change'")
class MisfitChild(Parent):
pass
With the above, the following will throw an error:
child = MisfitChild()
child.back()
Currently, I have two classes:
class Parent:
def __init__(self, controller, parent):
# Key press event bind 'Return Key'
self.controller.bind('<Return>', self.averageTesting)
def averageTesting(variable):
if len(variable) > 0:
return variable
else:
messagebox.showerror("Error", "Enter a valid variable")
class Child(Parent):
def __init__(self):
......
The parent class is actually page one and child class is page two of the Tkinter frame pages.
I don't want the child to have the messagebox showing. Right now when I go to page two or the child class and I press return on my keyboard, the message shows up. And I don't want that in the second page. Only the first page which is the parent class.
But I need to inherit everything but the messagebox from the parent class to the child class.
How do I do achieve this?
Personally, I think that if you must exclude parts of your parent's constructor then your design is flawed. You could instead make the parent class more generic and make your current parent and your current child both inherit from the new parent.
If you really want to stick with that pattern then you could just unbind the event. If this is the only callback connected to the event then you can just do.
x = Child()
x.controller.unbind('<Return>')
Edit:
My next suggestion is to instead move the binding to a separate method inside the parent class. For example.
class Parent:
def __init__(self, controller, parent):
self.controller = controller
...
def averageTesting(variable):
if len(variable) > 0:
return variable
else:
messagebox.showerror("Error", "Enter a valid variable")
def initializeBinding(self):
self.controller.bind('<Return>', self.averageTesting)
... # plus anymore bindings that only the parent must have
class Child(Parent):
def __init__(self):
...
So now when you want to use the parent you would do this.
p = Parent(...)
p.initializeBinding()
Whereas when you instantiate a Child object you don't call the method.
I have a moderately complex GUI that I'm building for interacting with and observing some simulations. I would like to be able to continue to refactor and add features as the project progresses. For this reason, I would like as loose as possible a coupling between different widgets in the application.
My application is structured something like this:
import tkinter as tk
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.instance_a = ClassA(self)
self.instance_b = ClassB(self)
# ... #
class ClassA(tk.Frame):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# ... #
class ClassB(tk.Frame):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# ... #
def main():
application = Application()
application.mainloop()
if __name__ == '__main__':
main()
I would like to be able to perform some action in one widget (such as selecting an item in a Treeview widget or clicking on part of a canvas) which changes the state of the other widget.
One way to do this is to have the following code in class A:
self.bind('<<SomeEvent>>', self.master.instance_b.callback())
With the accompanying code in class B:
def callback(self): print('The more that things change, ')
The problem that I have with this approach is that class A has to know about class B. Since the project is still a prototype, I'm changing things all the time and I want to be able to rename callback to something else, or get rid of widgets belonging to class B entirely, or make instance_a a child of some PanedWindow object (in which case master needs to be replaced by winfo_toplevel()).
Another approach is to put a method inside the application class which is called whenever some event is triggered:
class Application(tk.Tk):
# ... #
def application_callback():
self.instance_b.callback()
and modify the bound event in class A:
self.bind('<<SomeEvent>>', self.master.application_callback())
This is definitely easier to maintain, but requires more code. It also requires the application class to know about the methods implemented in class B and where instance_b is located in the hierarchy of widgets. In a perfect world, I would like to be able to do something like this:
# in class A:
self.bind('<<SomeEvent>>', lambda _: self.event_generate('<<AnotherEvent>>'))
# in class B:
self.bind('<<AnotherEvent>>', callback)
That way, if I perform an action in one widget, the second widget would automatically know to to respond in some way without either widget knowing about the implementation details of the other. After some testing and head-scratching, I came to the conclusion that this kind of behavior is impossible using tkinter's events system. So, here are my questions:
Is this desired behavior really impossible?
Is it even a good idea?
Is there a better way of achieving the degree of modularity that I want?
What modules/tools can I use in place of tkinter's built-in event system?
My code in answer avoids the issue of class A having to know about internals of class B by calling methods of a handler object. In the following code methods in class Scanner do not need to know about the internals of a ScanWindow instance. The instance of a Scanner class contains a reference to an instance of a handler class, and communicates with the instance of ScannerWindow through the methods of Handler class.
# this class could be replaced with a class inheriting
# a Tkinter widget, threading is not necessary
class Scanner(object):
def __init__(self, handler, *args, **kw):
self.thread = threading.Thread(target=self.run)
self.handler = handler
def run(self):
while True:
if self.handler.need_stop():
break
img = self.cam.read()
self.handler.send_frame(img)
class ScanWindow(tk.Toplevel):
def __init__(self, parent, *args, **kw):
tk.Toplevel.__init__(self, master=parent, *args, **kw)
# a reference to parent widget if virtual events are to be sent
self.parent = parent
self.lock = threading.Lock()
self.stop_event = threading.Event()
self.frames = []
def start(self):
class Handler(object):
# note self and self_ are different
# self refers to the instance of ScanWindow
def need_stop(self_):
return self.stop_event.is_set()
def send_frame(self_, frame):
self.lock.acquire(True)
self.frames.append(frame)
self.lock.release()
# send an event to another widget
# self.parent.event_generate('<<ScannerFrame>>', when='tail')
def send_symbol(self_, data):
self.lock.acquire(True)
self.symbols.append(data)
self.lock.release()
# send an event to another widget
# self.parent.event_generate('<<ScannerSymbol>>', when='tail')
self.stop_event.clear()
self.scanner = Scanner(Handler())
I have an instance..
groupCell = QtGui.QGroupBox()
print groupCell.title() #this class has a method title()
I am not able to change anything of this instance, it comes how it is...
I need to extend this instance (add some methods etc.)
class GroupBoxWithCheckbox (QtGui.QGroupBox):
def __init__(self, basegroupbox, checkbox):
#something like self = basegroupbox ?
self.checkbox = checkbox
def method(self):
pass
and finally
groupCellWithCheckBox = GroupBoxWithCheckbox(groupCell, checkbox)
print groupCellWithCheckBox.title()
I have to get the same title as with groupCell.
You can define a new class extending QtGui.QGroupBox that looks like this:
class GroupBoxWithCheckbox(QtGui.QGroupBox):
def __init__(self, checkbox):
super(GroupBoxWithCheckbox, self).__init__()
self.checkbox = checkbox
def method(self):
pass
Then you can simply make groupCell an instance of this class, and pass in a checkbox when you initialise it:
groupCell = GroupBoxWithCheckbox(checkbox)
That will have the same effect as what you are trying to do here.
Edit, since new information has been provided:
Since we're talking Python here, you can dynamically add things to any instance you want. It's totally possible to do this:
groupCell.checkbox = checkbox
Even if the groupCell doesn't have a checkbox property. The property will be added when you set it, as in my snippet above. You could use that to do what you want. It's kind of a weird thing to do, and I don't recommend it, but it would work. The alternative is to make a wrapper class of some sort:
class GroupBoxWithCheckbox(object):
def __init__(self, groupbox, checkbox):
self.groupbox = groupbox
self.checkbox = checkbox
groupCell = GroupBoxWithCheckbox(groupCell, checkbox)
And then any time you want to access a method of the original GroupBox, you can do something like
groupCell.groupbox.title()
groupCell.groupbox will contain all of the methods that the original GroupBox did, but you'll also have access to groupCell.checkbox.
The latter solution is what I would implement if I were coding this.
Call base class constructor using super:
class GroupBoxWithCheckbox(QtGui.QGroupBox):
def __init__(self, basegroupbox, checkbox):
super(GroupBoxWithCheckbox, self).__init__()
self.checkbox = checkbox
self.basegroupbox = basegroupbox
def title(self):
return self.basegroupbox.title()