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

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)

Related

Tkinter: How do I properly set parameters of extended Button class?

I am experimenting with creating a customer relationship management program using tkinter and python and would like to have buttons generated based on tables in the database.
The idea is that there is a button for each table in the database, so that the table can be viewed and edited if needed.
I want to have each button look the same and, when clicked, generate a list of table entries into the main frame of my program. To do this, I want to extend the Button() class so that I can keep some attributes concurrent while also defining the display_items function:
class TabButton(Button):
def __init__(self, *args, **kwargs):
super().__init__(Button)
self['bg'] = '#a1a1a1'
self['font'] = ('Agency', 24)
def display_items(self, tab):
pass
#mycursor.execute('SELECT * FROM (%s)', tab)
This last line (above) is what selects data from the correct table in my database - I have commented it out while I figure out the rest of the class. I know what *args and **kwargs do, but I'm not sure what purpose they have in this __init__ function (I'm not very familiar with classes and copied this class from another Stack Overflow post).
To generate the buttons, I referenced a dict instance and assigned each key to a button:
tabs = {
'Table1': '',
'Table2': '',
'Table3': '',
}
for tab in tabs:
row = 0
tabs[tab] = TabButton(side_frame, command=lambda: TabButton.display_items(tab))
tabs[tab].grid(row=row, column=0)
row += 1
The problem is, when I run the program I get this error:
AttributeError: type object 'Button' has no attribute 'tk'
Any and all guidance is welcome!
If you notice any other mistakes in my code, could you please point them out? I'm very new to programming and it will save me making another post on Stack Overflow. :p
Thanks,
J
Super returns a temporary object of that class and let you access its content. Super itself dosent accept any arguments.
Also see the prupose of self in that context.
self represents the instance of the class
Often, the first argument of a method is called self. This is nothing
more than a convention: the name self has absolutely no special
meaning to Python. Note, however, that by not following the convention
your code may be less readable to other Python programmers
Another issue is your use of lambda. Your argument tab will be overwritten (if it isnt stored) by each iteration of your loop. Another issue that you might not intent to use the class for this method you rather want to be calles by the instance, therefor I added the argument self to your method and changed your lambda to make use of the instance.
import tkinter as tk
tabs = {'Table1': '',
'Table2': '',
'Table3': '',
}
root=tk.Tk()
class TabButton(tk.Button):
def __init__(self,master, *args, **kwargs):
#self is a reference to the instance of that object
super().__init__(master)#super dosent need a reference
self['bg'] = kwargs.get('bg','#a1a1a1')
self['font'] = kwargs.get('font',('Agency', 24))
def display_items(self,item):
print(f'{item} to show')
for tab in tabs:
b = TabButton(root) #returns a reference to that object
b.configure(command=lambda btn=b, argument=tab:btn.display_items(argument))
b.pack()
root.mainloop()

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?

Getting tkinter.Widget bindings

Is there a way to get the default binding of tk.Widget class and then call it?
I need to do this because I need to call the default binding before my custom binding. It is called as the last by default.
So what I want to do is: bind my widget to my own callback, get the default binding function, call the default binding function, call my fuction (custom binding)
If you want to simply reverse the order that the bindings are processed, you can do that by changing the binding tags without needing to know what the actual bindings are. The tags are what determines the order that events are processed. By default the value is the widget, the widget class, the toplevel window, and then "all".
The following example changes the order so that the class binding ("Entry") is handled before the binding of the widget:
import tkinter as tk
...
e = tk.Entry(...)
e.bindtags((
e.winfo_class(),
str(e),
e.winfo_toplevel(),
"all")
)

Kivy - Accessing id of widget outside of the root class

I wish to retrieve the id of a widget in order to access its text attribute, in a widget class outside of the root class. I understand in order to affect or retrieve items with an id from kv-lang, the following syntax may be used:
self.ids.some_id.some_attribute = new_value
This works perfectly fine when used in the root class, as access is available to all its children. However in a widget class, the class only refers to the widget declared, so any ids outside of it are out of scope.
<Root>:
...
SomeButton:
...
TextInput:
id: some_id
What doesn't work:
class SomeButton(Button):
def on_press(self):
print(self.ids.some_id.text)
As I mentioned, this is understandable. But I don't know then what is used in this instance. Any assistance would be appreciated :)
The issue is that ids are local to the rule, not to the widget.
here your rule is declared for <Root>, so to access it you have to use a reference to this widget, not to the button.
If you want to give a reference to some_id to button, you can add a property to your button.
class SomeButton(Button):
target = ObjectProperty()
def on_press(self):
print self.target.text
and link them together in kv.
<Root>:
...
SomeButton:
target: some_id
TextInput:
id: some_id
I'm new in kivy but i think TextInput is not a child widget of a SomeButton but you are trying to access it from Button anyway. That's your problem.
Try self.parent.ids.some_id.text

Calling a method with an event handler

Why, if I put a "normal" call to the method button1Click() in the bind call, does my program not even start? By removing the parenthesis the problem is solved.
I'm using this program as reference: Thinking in Tkinter
Also, why should I add the event argument to my button1Click() method?
from Tkinter import *
class MyApp:
def __init__(self, parent):
self.myParent = parent
self.myContainer1 = Frame(parent)
self.myContainer1.pack()
self.button1 = Button(self.myContainer1)
self.button1.configure(text="OK", background= "green")
self.button1.pack(side=LEFT)
self.button1.bind("<Button-1>", self.button1Click) # <--- no () !
def button1Click(self, event):
self.button2 = Button(self.myContainer1, text="lol")
self.button2.bind("<Button-1>", self.button1Click)
self.button2.pack()
root = Tk()
myapp = MyApp(root)
root.mainloop()
bind() expects something that is callable and that expects an argument.
If you pass self.button1Click(), you effectively pass it None, because that is what is returned by this call.
As the call is to be performed by the clickable object, you are not supposed to call it yourself.
So, next step: You pass it self.button1Click, and you clock the button. Then the given "thing" is tried to be called with an event object as argument, but that fails, because the method is not prepared for that.
So you have 2 options:
Either you modify the method so it can be called with an event object, such as def button1Click(self, event):,
or you wrap it in a lambda call: lambda event: self.button1Click().
In the latter case, you give the bind() method a callable which accepts exactly one argument, and does the call as wanted at the time of calling (thus the ()).
You can call the method button1Click "normally" using lambda. What might be happening right now is that it would be getting called anyhow.
For ex: command=lambda:self.button1Click()
You can pass more arguments if you like by putting them in the parenthesis.
You need to use the event argument because whenever you bind a method you are passing event object too automatically. In your case,its two arguments-the event object & self.

Categories

Resources