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

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?

Related

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!

Kivy how to give all objects same basic functionality

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/

Kivy: How to attach a callback to a widget created in kvlang

When you want to attach a callback to a kivywidget, for example a textinput you can use the bind() function. Example from Kivy docs for a textinput:
def on_text(instance, value):
print('The widget', instance, 'have:', value)
textinput = TextInput()
textinput.bind(text=on_text)
But how do I attach it to an element that was created in the kvlang file?
Get a reference to the element, then call bind as normal. For instance, for the root widget of the application you can use App.get_running_app().root.bind, or for others you can navigate the widget tree via kv ids.
You can call the bind() on the widget referenced by self.ids['id_from_kvlang']. However this cannot be done on class level, you need to operate on the instance. So you need to put it in a function of the class.
The __init__ function is called at the instantiation of the object so you can put it there. However you need to schedule it, so it won't happen instantly, the widgets you are binding to are not there yet so you have to wait a frame.
class SomeScreen(Screen):
def __init__(self,**kwargs):
#execute the normal __init__ from the parent
super().__init__(**kwargs)
#the callback function that will be used
def on_text(instance, value):
print('The widget', instance, 'have:', value)
#wrap the binding in a function to be able to schedule it
def bind_to_text_event(*args):
self.ids['id_from_kvlang'].bind(text=update_price)
#now schedule the binding
Clock.schedule_once(bind_to_text_event)

PyQt Subclass from QTableWidgetItem and QWidget

Im making a QTableWidget in Pyqt and ran into a bit of an annoying hiccup.
I need to use widgets in my table for its functionality, so im using setCellWidget to add them to the table. However, widgets dont have the same methods available as QTableWidgetItem's do (especially regarding selection in the table).
Im wondering if its possible to do something subclassing both items, so i can have the methods of both, and how i woulda dd that to the table.
Something like:
class TableItem(QtGui.QTableWidgetItem, QtGui.QWidget):
def __init__(self, parent=None):
super(TableItem, self).__init__(parent)
self.check = QtGui.QCheckBox()
self.label = QtGui.QLabel('Some Text')
self.h_box = QtGui.QHBoxLayout()
self.h_box.addWidget(self.check)
self.h_box.addWidget(self.label)
and then somehow add that to my table as a TableWidgetItem so it displays widgets and also has selection methods available.
Any ideas here?
For reference:
setCellWidget: http://pyqt.sourceforge.net/Docs/PyQt4/qtablewidget.html#setCellWidget
QWidget: (easy to find, i cant post more than 2 links)
-Which doesnt have the nice methods for a table
QTableWidgetItem: http://pyqt.sourceforge.net/Docs/PyQt4/qtablewidgetitem.html#type
with isSelected and setSelected (Methods not avialble from a widget used in setCellWidget.
To return the widget in a cell you can use table.cellWidget(row, column) and then use your widgets methods on that. But beacuse setSelected and isSelected arent methods of a widget, you cant check for selection. I was hoping to subclass the two together to allow for both
--Basically I need to know how to get my class to 'return' the proper type when i call it to add to the table with setItem
I am not sure what you want to do but you could "inject" a method like:
class TableWidgetItem(QtGui.QTableWidgetItem):
def __init__(self, parent=None):
QtGui.QTableWidgetItem.__init__(self)
def doSomething(self):
print "doing something in TableWidgetItem"
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self)
tableWidgetItem = TableWidgetItem()
widget = Widget()
def widgetFunction(self):
tableWidgetItem.doSomething()
# as an instance method
settatr(widget, "widgetFunction", MethodType(widgetFunction, widget, type(widget)))
# or as a class method
settatr(widget, "widgetFunction", widgetFunction)
Then you can:
>>>widget.widgetFunction()
doing something in TableWidgetItem
(not tested)

How to use update and paintEvent in a QWidget in pyside?

Being new to pyside I am still having trouble understanding some GUI concepts, even aware of decent documentation.
I have a widget, dervied from QWidget, which I want to draw inside a function paintEvent, which is the function called to paint the widget (as far as I understood the documentation). Also, the method update should be used to update the widget, which calls the method paintEvent (as far as I understood the documentation).
In the following code skeleton I give a short overview of my code which should do two things:
- when initialized the widget should be drawn
- if required, the widget should be redrawn by a call to update inside the derived class
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
self.update() # update the widget for the first time
def paintEvent(self, x):
self.setGeometry(300, 200, 970, 450)
self.setWindowTitle("My Window")
...
self.table_model = MyTableModel(...)
self.view = QTableView()
...
self.setLayout(layout)
def do_something(self):
....
self.update()
When running the code the widget is drawn as expected. But once the call to update is made inside do_something nothing happens, the widget is NOT redrawn! I also tried to use the method repaint instead, but the widget is still not redrawn.
How to fix the code to ensure the widget is redrawn from scratch by calling paintEvent?

Categories

Resources