python - kivy: Call function from another class - python

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.

Related

AttributeError: 'kivy.properties.ObjectProperty' object has no attribute 'text' when used in on_enter Method

so i got a simple screen with a textfield and on enter it reads a file and the text from the file i want to write in my textfield.text. But when i call this in my on_enter methode i get the error
AttributeError: 'kivy.properties.ObjectProperty' object has no attribute 'text'
but only in this specific one. If I use it on, lets say a button press it works just fine...
My python file:
class Login(Screen):
def __init__(self, **kwargs):
super(Login, self).__init__(**kwargs)
self.user = ObjectProperty(None)
self.bt_forgotpassword = ObjectProperty(None)
self.lines=""
self.rememberme = True
def on_enter(self):
self.user = ObjectProperty(None)
print('UI: Loading Login Page')
with open("Backend\\Remember.me","r") as file:
self.lines = file.readlines()
if self.lines:
if self.lines[0].strip() == "True":
self.rememberme = True
self.user.text = self.lines[1]
else:
self.rememberme = False
my .kv File:
<Login>:
user:user
MDCard:
orientation: 'vertical'
BoxLayout:
orientation: 'vertical'
MDTextField:
id: user
mode:"round"
hint_text: "username"
write_tab:False
Found a solution.
So basically on_enter is called before the .kv file is laded and any ObjectProperties are applied.
I used a similar event called on_kv_post which is used like this
def on_kv_post(self, base_widget):
# your Code Here
return super().on_kv_post(base_widget)
This event is called after properties are applied so you can call all your widgets with their id like always : self.ids['your_id'] or by the variable you assigned the object properties to.
The event sequence of events is on_pre_enter then on_enter and after the .kv is loaded on_kv_post.

add_widget and remove_widget for a class in kivy

Im new to Kivy, Im sorry for asking 2 questions in a post..
First, why the remove_widget() does not work? It said AttributeError: 'MyCard' object has no attribute 'remove_card' but I tried to put them in other classes and it still does not work.
Second, why my widgets are still have "focus behavior" and my buttons are still clickable even when I put a card whose color has little transparent on it
This is my main.py file
class MyCard(Screen):
pass
class HomeScreen(Screen):
def add_card(self):
self.add_widget(MyCard())
def remove_card(self):
self.remove_widget(MyCard(name='cardd'))
class AVCard(Screen):
pass
class ScreenApp(MDApp):
def build(self):
def build(self):
sm = ScreenManager(transition=FadeTransition(duration=0.2))
sm.add_widget(HomeScreen(name='home'))
sm.add_widget(AVCard(name='av'))
return sm
and this is my home.kv file (AVCard class has its own .kv file)
<HomeScreen>:
name: 'home'
MDIconButton:
on_release: root.add_card()
...
<MyCard>:
name: 'cardd'
MDCard: #-> I put this card is to not allow user click on widgets behind it but it does not work
md_bg_color: 0, 0, 0, .3
...
MDCard: #-> Thís card is like a window which includes small widgets in it
...
Screen:
MDIconButton: #-> The close button
icon: "close-circle"
...
on_release:
root.remove_card()
Thank you very much.
In your kv, the root.remove_card() is trying to reference the remove_card() method of root. In this instance root refers to the root of the rule in which it appears, which is the MyCard. That is why you see the error message, the remove_card() is not in the MyCard object. The fix is to use a reference to the correct object that does contain the remove_card() method, like this:
<MyCard>:
MDCard: #-> I put this card is to not allow user click on widgets behind it but it does not work
md_bg_color: 0, 0, 0, .3
MDCard: #-> Thís card is like a window which includes small widgets in it
Screen:
MDIconButton: #-> The close button
icon: "close-circle"
on_release:
app.root.get_screen('home').remove_card(root)
Note the use of app.root.get_screen('home').remove_card(root), this gets a reference to the HomeScreen object (assuming the name used is home), and calls its remove_card() method with root (the MyCard instance) as an arg.
Then , in the HomeScreen class, the remove_card() method can be:
def remove_card(self, card):
self.remove_widget(card)

How to use OOP in Kivy Python?

Please help me, my competition is around the corner
I tried to use OOP in Kivy. This is my simple Python code for testing:
class location:
def __init__(self, total_house, total_land):
self.total_house = total_house
self.total_land = total_land
class test(BoxLayout):
def addNum(self):
App.get_running_app().x.total_house += 1
class testApp(App):
x = location(NumericProperty(10),NumericProperty(5))
testApp().run()
this is my kv file:
<test>:
orientation: 'vertical'
Label:
text: str(app.x.total_house)
Button:
text: 'add'
on_press: root.addNum()
This is the output
I want the output to be 10 and when the button is pressed the number is added by one.
Please help me, I am new to KIVY
One way of getting pure value from Kivy Property is to use the built-in .get(EventDispatcher obj) method from the kivy.properties.Property class:
class test(BoxLayout):
def addNum(self):
App.get_running_app().x.get(EventDispatcher()) += 1
But before that, you need to import the EventDispatcher class first:
from kivy._event import EventDispatcher
Also please note that while this works in theory and it will indeed change the value of the x variable, I would recommend directly changing the label's own text, something like this:
.py
def numberify(*args):
# This functions is for universally changing str to either int or float
# so that it doesn't happen to return something like 8.0 which isn't that great
a = []
for w in range(0, len(args)):
try:
a.append(int(args[w]))
except ValueError:
a.append(float(args[w]))
return a if len(a) > 1 else a[0]
class test(BoxLayout):
def addNum(self):
self.ids.label1.text = str(numberify(self.ids.label1.text) + 1)
.kv
<test>:
orientation: 'vertical'
Label:
id: label1
text: str(app.x.total_house)
Button:
id: button1
text: 'add'
on_press: root.addNum()
Learn more about Kivy Property here and understand how it's not always necessary to use them :)

Kivy: How to change ScreenManager's "current" property on __init__

My App is a ScreenManager. Depending on the circumstances, I want the active screen to either be "Screen 1" or "Screen 2" when the app opens. How would I do this the most elegant way? I thought that this is as trivial as changing the current property in the initialization of the app. Sadly, this does not work. Here's what should work imo:
main.py:
MyApp(App):
def build(self):
return Builder.load_file("MyApp.kv")
def __init__(self, **kwargs):
super(MyApp, self).__init__(**kwargs)
if foo: # Here's the problem:
self.root.current = "Screen 1"
else:
self.root.current = "Screen 2"
MyApp.kv:
ScreenManager:
Screen1:
name: "Screen 1"
Screen2:
name: "Screen 2"
<Screen1#Screen>
etc...
But it doesn't. Throws the following error:
self.root.current = "Screen 1"
AttributeError: 'NoneType' object has no attribute 'current'
My guess is that I set the current attribute to early, before root is set. An idea of mine is to 1) create a property-var for MyApp, 2) set current to be that property, 3) change that property in the init method. That's a lot of effort and code-cluttering just to change a screen on initialization.
How would I do it? Thanks a lot in advance!
That's simply because you don't have self.root object specified. Why would you need to change Screens during __init__ ? You should use build function for that.
My example:
import random
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager
Builder.load_string('''
<Root>:
Screen:
name: "Screen 1"
Label:
text: "Screen 1!"
Screen:
name:"Screen 2"
Label:
text: "Screen 2!"
''')
class Root(ScreenManager):
pass
class MyApp(App):
def build(self):
self.root = Root()
foo = random.randint(0,1)
if foo:
self.root.current = "Screen 1"
else:
self.root.current = "Screen 2"
return self.root
MyApp().run()
self.root.cureent_screen property will be changed before self.root object will be visible
Only this is working.
I said above statement because I tryed many codes(but not works)
class NextScreen(ScreenManager):
def __init__(self,**kwargs):
super(NextScreen,self).__init__(**kwargs)
#code goes here and add:
Window.bind(on_keyboard=self.Android_back_click)
def Android_back_click(self,window,key,*largs):
# print(key)
if key == 27:
if self.current_screen.name == "Screen1" or self.current_screen.name == "Screen_main":
return False
elif self.current_screen.name == "Screen2":
try:
self.current = "Screen1"
except:
self.current = "Screen_main"
return True
Use 'esc' button to get key(27) which means back in android

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