Stuck with Class Function in Kivy - python

So I 've been creating this Kivy App, but this happened
class MyScreenManager(ScreenManager):
def __init__(self, **kwargs):
super(MyScreenManager, self).__init__(**kwargs)
'''conn = sqlite3.connect('first_db.db')
c = conn.cursor()
c.execute("SELECT name FROM sqlite_master WHERE type='table';")
screens = c.fetchall()
for i in range(len(screens)):
self.add_note()
c.close()'''
self.add_note()
def add_note(self, name = ""):
self.add_widget(NoteScreen(name = name))
print(self.parent)
my .kv file
MDNavigationLayout:
x: toolbar.height
size_hint_y: 1.0 - toolbar.height/root.height
MyScreenManager:
id: screen_manager
NoteScreen:
name: "Home"
When I run the app, instead of printing out the parent of MyScreenManager which is MDNavigationLayout, it printed out "None". I don't know how to fix it. Can anybody help me?

self.parent isn't available at the time of the __init__ method because the class is first instantiated, then added to its parent.
Per documentation:
The parent of a widget is set when the widget is added to another widget
The base Widget.__init_() doesn't set self.parent.
Since Kivy uses an observable property for parent the action can be added in on_parent
def on_parent(self, instance, value):
super(MyScreenManager, self).on_parent(instance, value)
self.add_note()

Related

python - kivy: Call function from another class

I am currently developing a GUI with Python/ Kivy and have some issues when it comes to call a function from another class. I set up a screen, which includes a TextInput widget, that can be used to insert an E-Mail address. By clicking the Submit-Button, the function 'check_mail' is called, which checks the E-Mail using regular expressions and then either prints a text ('E-Mail not valid') or changes the screen (The E-Mail Address will later be transferred to a database, for now its fine to just change the screen after submitting). However, the function does print me the text, if the E-Mail format is not valid, but when it comes to call the change_screen function from the InsertData class, it is not working (AttributeError: 'NoneType' object has no attribute 'ids') If I call the change_screen function within the .kv file {on_release: app.change_screen('home_screen')}, it works fine. How can I access the change_screen function from my InsertData class?
main.py
class HomeScreen(Screen):
pass
class InsertData(Screen):
def check_mail(self):
addressToVerify = self.ids.email_main.text
match = re.match('^[_a-z0-9-]+(\.[_a-z0-9-]+)*#[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$', addressToVerify)
if match == None:
print('Email not valid!')
else:
MainApp().change_screen('home_screen')
GUI = Builder.load_file("main.kv")
class MainApp(App):
def build(self):
return GUI
def change_screen(self, screen_name):
screen_manager = self.root.ids[
'screen_manager']
screen_manager.transition = CardTransition()
screen_manager.transition.direction = 'up'
screen_manager.transition.duration = .3
screen_manager.current = screen_name
MainApp().run()
insert_data.kv
<InsertData>:
FloatLayout:
canvas:
Rectangle:
size: self.size
pos: self.pos
source: "background/background_main.png"
GridLayout:
rows: 1
pos_hint: {"top": 1, "right": 1}
size_hint: 1, .8
TextInput:
id: email_main
hint_text: "E-Mail Address"
LabelButton:
text: "Submit"
on_release:
root.check_mail()
This is how you can call function x for class B
class A:
def __init__(self):
pass
def x(self):
print('x')
class B:
def __init__(self):
A().x()
Update Managed to solve the problem.
Looking at the error I noticed that every time, the function change_screen is executed (due to a not-valid E-Mail format), it will run the following line in change_screen:
screen_manager = self.root.ids['screen_manager']
The problem was, that self.root was referring to the InsertData class, which does not have the attributes. Since self.roots refers to the GUI in the MainApp class, I changed the line in the change_screen function as follows:
screen_manager = GUI.ids['screen_manager']
Now the function is referring to the fixed GUI instead of the self.root and is running without any problems.
I have some solution just to show how does it will work.
class A (self):
def some_method(self):
print("Print Here")
class B (self):
def some_more_method(self):
print("Let see here")
Details:
Let's say you want to use method/function from class B in class A.
'Add this line'
B.some_more_method(self)
It works for me.

Python MainWindow class can't find attribute

I am building a GUI application and I've been trying to debug the code and test it but it keeps throwing me this error saying. I added a button function to the MainWindow class which is 'self.AddtoCart.clicked.connect(self.addCart)' but now it won't open.
'MainWindow' Object has no attribute 'addCart'.
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
QtWidgets.QMainWindow.__init__(self, parent=parent)
self.setupUi(self)
self.Addtocart.clicked.connect(self.addCart)
def addcart(self):
style = self.comboBox_6.currentText()
name = self.lineEdit_10.text()
color = self.lineEdit_11.text()
size = self.comboBox_7.currentText()
text = "style: {style}, name: {name}, color: {color}, size: {size}".format(style=style, name=name, color=color, size=size)
self.plainTextEdit.appendPlainText(text)
In your code, addcart() is not in the scope of MainWindow, since it is at the same indentation level. Indent the functions with a tab and addCart() will become an attribute of the class.

When to bind to attributes that populated with kv-file?

test.kv
<RootWidget>:
test: test
Label:
id: test
text: "some"
test.py
class RootWidget(Widget):
test = ObjectProperty(None)
def __init__(self, **kwargs):
# 1:
print(self.test) # None
# 2:
def after_tick(*args):
print(self.test) # Label object
Clock.schedule_once(after_tick, 0)
super().__init__()
If I'll try to bind something to self.test directly inside __init__ (1), I would get AttributeError since kv rules aren't applied yet and self.test is None. Possible workaround would be to bind to attribute after first event loop iteration (2).
This solution seems to be duct tape. Is there any better way?
I know 3 other ways.
First, make the after_tick a method and execute it in init:
class RootWidget(Widget):
test = ObjectProperty(None)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.delayed_init()
#mainthread
def delayed_init(self):
print(self.text)
Second, use a built-in method that is always executed after the first loop frame:
class RootWidget(Widget):
test = ObjectProperty(None)
def add_widget(self, widget, index=0):
super().add_widget(widget, index)
if widget.__class__.__name__ == 'Label':
widget.text = self.test.text
This is useful if you have a lot of same type widgets to bind.
Third, use a property's on_x callback:
class RootWidget(Widget):
test = ObjectProperty(None)
def on_test(self, *args):
print(self.test)

Kivy - Why Label does not update?

I want to change a text of a label but I can't do it, I can see it changing on the shell but not on the UI. I even directly change the text of the label by referencing its id but still its not updating. Anyone knows how to do this?
class MainApp(Screen, EventDispatcher):
title = "Top 10 Plays of 2015"
def __init__(self,*args,**kwargs):
super(MainApp, self).__init__(*args, **kwargs)
def change_curr_title(self, title, *args):
self.title = title
self.ids.lblTitle.text = self.title
print(self.ids.lblTitle.text)
pass
class OtherVideos(BoxLayout, EventDispatcher):
def __init__(self, *args, **kwargs):
super(OtherVideos,self).__init__(*args, **kwargs)
self.loadVideos()
def loadVideos(self):
self.clear_widgets()
con = MongoClient()
db = con.nba
vids = db.videos.find()
vidnum = 1
for filename in vids:
myid = "vid" + str(vidnum)
getfilename = filename['filename']
button = Button(id=myid,
text=getfilename,
color=[0,0.7,1,1],
bold=1)
button.bind(on_release=partial(self.change_Title, getfilename))
self.add_widget(button)
vidnum += 1
def change_Title(self, title, *args):
main = MainApp()
main.change_curr_title(title)
This is the construction of my kivy:
<MainApp>:
....
BoxLayout:
....
BoxLayout:
....some widgets
BoxLayout:
OtherVideos:
...this is where the buttons are generated...
BoxLayout:
Label:
id: lblTitle
text: root.title
Is there anyway to upload my whole code on this? like the file itself, so you guys can look at it.
EDIT: I can easily update the label when I'm making a new method like this without a parameter and binding it to a button through kivy
def update_label(self):
self.ids.lblTitle.text = "New Title"
I don't know why buttons with events created dynamically doesn't work.
Here:
def change_Title(self, title, *args):
main = MainApp() # !
main.change_curr_title(title)
you are creating a new object of screen (MainApp), which isn't connected to anything. To make it work, main should link to the existing instance of MainApp screen.
The OtherVideos box layout needs to have a reference to it, preferably in kv file.
Edit
In order to create a link from MainApp to OtherVideos, create an ObjectProperty:
class OtherVideos(BoxLayout):
main = ObjectProperty()
def __init__(self, *args, **kwargs):
super(OtherVideos,self).__init__(*args, **kwargs)
self.loadVideos()
...
which will be populated in kv file:
OtherVideos:
main: root
...this is where the buttons are generated...
Then, in the change_Title function, use this reference:
def change_Title(self, title, *args):
self.main.change_curr_title(title)

Kivy: Self-updating label text

Let's say I have 3 classes: a "woking class" where stuff takes place, a label class and a class to contain them.
For example the label class could be a status bar showing the status of something going on the working class. I wish I could find a way to make the label self-update the value to show, since this value is a value of the working class being changed inside the latter.
Here I have an example code
Builder.load_string('''
<CustomLabel>
text: 'Value is {}'.format(root.value)
<WorkingClass>:
orientation: 'vertical'
Button:
text: 'Update'
on_release: root.update()
<MainLayout>
orientation: 'vertical'
''')
class CustomLabel(Label):
value = NumericProperty()
class WorkingClass(BoxLayout):
def __init__(self, *args, **kwargs):
super(WorkingClass, self).__init__(*args, **kwargs)
self.a = 5
def update(self):
self.a += 1
print(self.a)
class MainLayout(BoxLayout):
def __init__(self, *args, **kwargs):
super(MainLayout, self).__init__(*args, **kwargs)
self.workingClass = WorkingClass()
self.customLabel = CustomLabel(value=self.workingClass.a)
self.add_widget(self.customLabel)
self.add_widget(self.workingClass)
class MyApp(App):
def build(self):
return MainLayout()
if __name__ == "__main__":
MyApp().run()
Is there a way of doing it with properties or whatever? Becouse I don't want to need to manually update (sommehow) the label each time I change the value. Anyway to achieve this?
You're updating a property on WorkingClass, but that doesn't update the value on CustomLabel since you did a direct assignment instead of binding it. But yes, you can use Propertys to make everything work automatically.
In WorkingClass:
class WorkingClass(BoxLayout):
a = NumericProperty()
def __init__(self, **kwargs): ...
This makes a into a Property which you can bind to.
Then in MainLayout's constructor:
self.workingClass = WorkingClass()
self.customLabel = CustomLabel(value=self.workingClass.a)
self.workingClass.bind(a=self.customLabel.setter('value'))
The last line says: "when the value of property a on self.workingClass changes, set the value property of self.customLabel to the same value"
Alternatively, you could just add the Property to WorkingClass above, then get rid of MainLayout's constructor and use kv instead:
<MainLayout>:
orientation: 'vertical'
WorkingClass:
id: working_class
CustomLabel:
value: working_class.a # assigning one property to another in kv automatically binds

Categories