Kivy how to give all objects same basic functionality - python

Lets say I create a custom Button class called MyButton. I want all created MyButtons to play the same sound when they are pressed. But I also want to add different functionality for specific buttons, for example I want some buttons to change label text, but I also want them to play that sound. Is there a way to do it with inheritance so I don't have to keep in mind that I have to add play sound functionality to every created button?
EDIT: Lets say I have a class MyButton declared as bellow:
class MyButton(Button):
def generic_function_for_all_buttons(self):
print('GENERIC FUNCTION')
now when I try to create a MyButton somewhere else in the code like this:
class TestClass(BoxLayout):
def __init__(**kwargs):
self.buttons = []
self.set_layout()
def button_action(self,button):
button.generic_function_for_all_buttons()
print(button.text)
def set_layout(self):
for i in range(0,100):
button = MyButton(text=i)
button.on_press = functools.partial(button_action, button)
self.buttons.append(button)
self.add_widget(button)
This is not runnable code, just a demonstration of what I want to achieve. Now each time I press MyButton from TestClass, it prints GENERIC FUNCTION and a number between 0-99 based on which button was pressed. But I had to add button.generic_function_for_all_buttons() line and I want to avoid it if possible. If each of the 100 buttons had its own different action like this:
def action_1(self,button):
button.generic_function_for_all_buttons()
print('1')
def action_2(self,button):
button.generic_function_for_all_buttons()
print('2')
def action_3(self,button):
button.generic_function_for_all_buttons()
print('3')
...
That button.generic_function_for_all_buttons() is 100 lines of code I want to avoid. I thought it must be possible somehow with inheritance,e.g I add on_press method to MyButton class like this:
class MyButton(Button):
def on_press(self):
print('GENERIC FUNCTION')
but then it simply ignores it.

Solution
Implement on_touch_down method in class MyButton
Check for collision using collide_point() function
Snippets
class MyButton(Button):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
print('GENERIC FUNCTION')
print(f"MyButton.text={self.text}")
return True # consumed touch and stop propagation / bubbling
return super(MyButton, self).on_touch_down(touch)
class TestClass(GridLayout):
def __init__(self, **kwargs):
super(TestClass, self).__init__(**kwargs)
self.cols = 20
self.buttons = []
self.set_layout()
def set_layout(self):
for i in range(0,100):
button = MyButton(text=str(i))
self.buttons.append(button)
self.add_widget(button)
Kivy » Touch event basics
By default, touch events are dispatched to all currently displayed
widgets. This means widgets receive the touch event whether it occurs
within their physical area or not.
...
In order to provide the maximum flexibility, Kivy dispatches the
events to all the widgets and lets them decide how to react to them.
If you only want to respond to touch events inside the widget, you
simply check:
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
# The touch has occurred inside the widgets area. Do stuff!
pass
Output

I would create a base class called MyButton and create child button classes that inherit from MyButton.
Then using Inheritance and Polymorphism you can keep the attributes & methods, such as the sound, the same across all child buttons, but have different labels without needing to have unique attributes for each child.
Polymorphism will also allow you to iterate through all the children, as the attributes have the same name.
See article linked describing this a little more:
https://www.pythonspot.com/polymorphism/

Related

Expand Python Tkinter Object Types

There is this question to discover existing types:
Getting Python Tkinter Object Type
However I've just developed tooltips (balloons) I've assigned to some buttons and would like to be able to recall all of them as a unique type. Also down the road I'd like to hand-craft canvas objects which will operate like pseudo buttons with <enter>, <leave>, <active>, <press> and <release> events. How might I declare a new object type for them?
If I understand your question correctly you want to check if an instance created is a instance of the custom class, it can be directly done using isinstance:
class CustomTooltip():
pass
cwidget = CustomTooltip()
btn = tk.Button(root)
print(isinstance(cwidget, CustomTooltip)) # True
print(isinstance(b, CustomTooltip)) # False
print(type(cwidget) == CustomTooltip) # Not recommended
It is recommended to use isinstance rather than type, from the docs:
The isinstance() built-in function is recommended for testing the type of an object, because it takes subclasses into account.
I did not quite get your 2nd question, also it is better to keep a single question focused around one main question rather than asking more than one question.
Object Oriented Programming is probably the solution.
If I want to create a "new type" of tkinter button I can sub-class it.
class MySpecialButton(tkinter.Button):
pass
This doesn't do anything special at the moment but will give it a unique type (If it have understood your interpretation correctly)
The following example from another one of my answers, creates a special button with custom behaviour for hovering over the button
class HoverButton(tk.Button):
def __init__(self, master, **kw):
tk.Button.__init__(self,master=master,**kw)
self.defaultBackground = self["background"]
self.bind("<Enter>", self.on_enter)
self.bind("<Leave>", self.on_leave)
def on_enter(self, e):
self['background'] = self['activebackground']
def on_leave(self, e):
self['background'] = self.defaultBackground
With regard to canvas object, You can obviously create classes for these too which can contain methods for moving/resizing the object. As to how to create custom events for these, you can use tag_bind(item, event=None, callback=None, add=None) to bind a call back to a canvas object. A quick example below
import tkinter as tk
class CanvasShape:
def __init__(self, canvas, callback = None):
self.canvas = canvas
self.id = canvas.create_oval(10,10,50,50,fill='blue')
self.canvas.tag_bind(self.id,'<Button-1>',callback)
def clicked(e):
print("You clicked on a shape")
root = tk.Tk()
c = tk.Canvas(root,width=200,height=200)
c.grid()
shape = CanvasShape(c,callback=clicked)
root.mainloop()
This will create a circle that when you click on it will fire an event that is received by the clicked function.

python tkinter inside class - getting state of widgets

I've written a program and I'm learning about classes and thus, rewriting the code to take advantage of classes and objects.
My problem is if I create a tkinter widget "dropdown menu" from a class, I can not retrieve the get.state variable.
class ...:
def make_drop_menu(self,parent,drop_opts,drop='empty',state=''):
self.parent = parent
self.drop = drop
self.drop_opts = drop_opts
self.state = tk.StringVar()
self.state.set(self.drop_opts[0]) # sets default value on drop (drop down menu)
self.drop = OptionMenu(self.parent, self.state, *self.drop_opts) # completed drop
return self.drop
----------------
class main:
self.widget = self.var.make_drop_menu(self.obj_mainwindow.btm_frame, self.col_opts)
Added for clearity:
window = windowclass() #windowclass is the style for tkinter. Makes a tkwindow.
self.widget = self.windowclass.var.make_drop_Menu() #
I've left out some code to simplify. But my main problem is that I create a widget called self.widget which builds my drop down menu. However, I have NO idea how to get the state of the drop down.
Hopefully the code makes sense.
If your class is named MyClass, then to access the state variable you would use the instance of the class. In your case it appears that self.var is the instance of your class, so it would look something like this:
self.var = MyClass()
self.var.make_drop_menu(...)
...
print(self.var.state.get())
Made the make_drop_menu class independent of any other class object. That way I could make a drop menu, assign the parent tk window, and create a method for getting the state variable when called.
Thanks to Bryan Oakley for the help!

It’s working but I don’t understand why. It's about arguments to callbacks

It’s working but I don’t understand why. I was able to play around until I found something that works and it depends upon hidden “default” arguments in callbacks, but I thought the “event” was the only hidden argument. I wanted to pass the class “self” also. I want an event in a tkinter class to call an external function (external to the class) and I want to pass the event and the class (“self”) as arguments. To do this I call internal functions which then call the external functions.
The confusing point is that I must do it differently for a call from a bind and a call from a button command. It works and does what I want but I also want to understand what is happening. I also would like to know if there is a better way. The following code is all within the class.
self.B1 = Button(self.frame_controls, text = "Go", command=lambda: self.process_go_internal(self))
self.canvas.bind('<Configure>', self.process_configure_internal)
def process_go_internal(event, rt):
process_go_external(rt, event)
def process_configure_internal(self, event):
process_configure(self, event)
Hmmm, it's difficult to see what you are doing when you are not supplying working code. However I'll provide an example of how you could do what I think you are trying to do:
The bind() function generates an event but the Button() does not, so you will have to treat them accordingly. I have them call different methods within the class.
In the button_press() method I supply a default value for event as there is no event passed by the Button.
As for passing a reference to the instance you can just pass it as you would any name.
from tkinter import *
root = Tk()
def external_function(instance, event):
print('external_function instance:', instance)
print('external_functionevent:', event)
print()
instance.do_it() # Try to call the Cheese instance
class Cheese():
def __init__(self):
root.bind('<Configure>', self.configure)
B1 = Button(root, text='Go', command=self.button_press)
B1.pack(padx=100, pady=20)
def configure(self, event):
print('<Configure>')
external_function(self, event) # Passing instance ref and event
def button_press(self, event=None):
print('Button press')
print()
external_function(self, event) # Passing instance ref and event
def do_it(self):
print("I'm doing it!")
print()
c = Cheese()
root.mainloop()
Was this helpful?

What affect does inheriting from Kivy Widget class have on the child?

First, a heads up - I am not too familiar with OOP concepts, so this may just be some form of python functionality that I am not aware of.
In Kivy we can modify the behaviour and appearance of widgets by creating classes child to the widgets whose functions we want to alter, for instance:
class MyWidget(Button):
def __init__(self, **kwargs):
super(MyWidget, self).__init__(**kwargs)
self.size_hint = None
self.size = 200, 100
Then use MyWidget: or <MyWidget> to instantiate the widget in kv. This is good if I wish for my widgets to be parent to the root widget, but there are times when this is not wanted for instance when requiring a temporary Popup.
In which case I would just create a typical class which is instantiated when an event is triggered. Like so:
class Interface():
def __init__(self):
btn = Button()
btn.bind(on_press=self.some_callback)
self.popup = Popup(content=btn)
self.popup.open()
def some_callback(self, instance):
print('woo')
And this, to all appearances, looks fine, but the widget events (on_press here) don't trigger callback functions??? The bind() function will call the callback on the event, since I will be notified if the callback function definition has incorrect syntax, but for some reason the contents of the callback are not executed - only when Interface() inherits from the Widget() class (Interface(Widget): ...).
From the docs:
Widget interaction is built on top of events that occur. If a property changes, the widget can respond to the change in the ‘on_’ callback. If nothing changes, nothing will be done.
btn is the widget, it is instance of Button() which itself is a child of Widget, its method has no connection at all to what may be the parent class of Interface so why then is the callback only fully executed when Widget is the parent class of Interface? What am I missing?

Unused parameters when using an interface

I'm creating a GUI for a project of mine using Python. Even though this is a private project, I would like to use good coding practices. First, let me introduce a simplified version of my GUI module:
# Just a box, can have borders or it can be filled
class Box(object):
def __init__(self):
# Set initial state
def update(self, xy, press):
# I'm just a dummy box, I don't care about xy or press
pass
def draw(self):
# Draw
# Like a box but with special functionality
class Button(Box):
def __init__(self):
super(Button, self).__init__()
# Set initial state
def update(self, xy, press):
# Do something with xy and press
# Like a box but with special functionality
class Status(Box):
def __init__(self):
super(Status, self).__init__()
# Set initial state
def update(self, xy, press):
# Do something with xy, ignore press
# A box which can hold boxes inside it to group them
class Container(Box):
def __init__(self):
super(Container, self).__init__()
self.childs = deque()
def update(self, xy, press):
for c in self.childs:
c.update(xy, press)
# Container draws itself like a Box but also draws boxes inside it
def draw(self):
super(Container, self).draw()
for c in self.childs:
c.draw()
Every GUI component is in a container. Container's update() gets called every cycle to update the state of those components with the latest input information.
I like this solution since it allows me to use an interface to update the whole GUI in one loop and it saves a bit of code. My problem is that some of these childs need more information than others to update their state which leads into unused parameters with the use of the interface.
So, is the use of unused parameters considered a bad practice in this case and should I just give up using the interface?
The usual way to do this is called cooperative inheritance, which is essentially just a buzzword to say that both the super- and the sub- class expect each other to be around and be passed information it may not need. Methods of this type tend to look like:
def foo(self, specific, arguments, *args, **kwargs):
do_something_with(specific, arguments)
super(MyClass, self).foo(*args, **kwargs)
in other words, each more-specific Container handles what's special about it, but if there's default functionality that's common to all Containers (and if there isn't -- why are we using inheritance?!) then you define that only in the superclass and use super to defer to it in the subclass.

Categories

Resources