How to reference to an instance created in KV file - python

I want to call a method add_category_to_tree() from TreeCategory class, when pressing SAVE button in AddCategoryPopup class. However, I have problems how to reference to the TreeCategory instance, which is created in KV file.
I was trying to search solution, but nothing works. Currently i get AttributeError: 'super' object has no attribute '__getattr__' error.
How should I do this properly ? Thank you for help
class TreeCategory(TreeView):
def __init__(self, **kwargs):
super(TreeCategory, self).__init__(**kwargs)
def add_category_to_tree(self, name):
self.add_node(TreeViewLabel(text = name.upper()))
class AddCategoryPopup(Popup):
def save(self):
self.ids.tree.add_category_to_tree(self.ids.entry.text) # ????
db.adding_to_db('kategorie', 'nazwa', self.ids.entry.text)
self.dismiss()
def close(self):
self.dismiss()
class MainScreen(BoxLayout):
tree = ObjectProperty(None)
def add_category_button(self):
popup = AddCategoryPopup(title = 'Dodawanie nowej kategorii')
return popup.open()
class GuiCookBookApp(App):
def build(self):
self.title = "Książka kucharska"
return MainScreen()
if __name__ == "__main__":
db = DatabaseManager("cookbook.sqlite")
GuiCookBookApp().run()
KV file:
<AddCategoryPopup>:
BoxLayout:
orientation: 'vertical'
TextInput:
id: entry
multiline: False
hint_text: 'Podaj nazwę kategorii...'
BoxLayout:
orientation: 'horizontal'
Button:
text: 'SAVE'
on_press: root.save()
Button:
text: 'CANCEL'
on_press: root.close()
<MainScreen>:
orientation: "vertical"
display: entry
tree: tree
BoxLayout:
id: menu
size_hint_y: .1
Button:
text: 'Dodaj kategorię'
on_press: root.add_category_button()
BoxLayout:
id: recipe_view
orientation: "horizontal"
TreeCategory:
id: tree
hide_root: True
size_hint: .25, 1

With self.ids in Python you can only access the ids in KV from that particular class. So self.ids.tree is only possible inside the MainScreen class in Python not in the AddCategoryPopup class.
You could create an ObjectProperty 'topwidget' in your AddCategoryPopup rule and pass in the Main class when you instantiate the popup. Something like:
popup = AddCategoryPopup(topwidget=self)
Then in the 'save' method of your custom popup class you can do something like:
self.topwidget.tree...

You can do it quite a few ways. For instance you can put the .kv in your main .py file.
w = Builder.load_string('''
Widget:
height: self.width / 2. if self.disabled else self.width
x: self.y + 50
''')
https://kivy.org/docs/api-kivy.lang.builder.html
You can simply name the .kv file
guicookbookapp.kv
and leave it in the root directory of your project.
https://kivy.org/docs/examples/gen__application__app_with_kv__py.html
You can also add the following
from kivy.lang import Builder
Builder.load_file('guicookbookapp.kv')
Hopefully I understood your question correctly.

Related

How to use python file to insert elements into kivy?

I'm trying to make an app with kivy and I want to be able to add a changeable amount of buttons with changeable text to a screen. Here's my code:
kv = Builder.load_file('main.kv')
class MyScreen(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
label = Label(text="hello world!")
for i in range(5):
self.ids.FillIn.add_widget(label)
class MainApp(App):
def build(self):
return kv
if __name__ == "__main__":
MainApp().run()
and kivy:
<WindowManager#ScreenManager>:
<MainPage#Screen>:
Button:
on_release: app.root.current = "MyPrayers"
<MyScreen#Screen>:
name: 'MyScreen'
BoxLayout:
orientation: "vertical"
BoxLayout:
orientation: "vertical"
id: FillIn
WindowManager:
MainPage:
MyScreen:
The main problem with your code is that the line of code:
kv = Builder.load_file('main.kv')
is executed before the definition of MyScreen is loaded. So the NyScreen instance that is created by the Builder.load_file() has none of the attributes of the later defined MyScreen class. Other issues involve trying to access ids in the __init__() method (which is executed before ids are assigned) and trying to add the same Label widget multiple times (any widget can have only one parent).
Here is a modified version of your python code that fixes the above issues:
class MyScreen(Screen):
def __init__(self, **kwargs):
super(MyScreen, self).__init__(**kwargs)
Clock.schedule_once(self.add_widgets)
def add_widgets(self, *args):
for i in range(5):
label = Label(text="hello world!")
self.ids.FillIn.add_widget(label)
class MainApp(App):
def build(self):
kv = Builder.load_file('main.kv') # load kv here, after MyScreen is defined
return kv
if __name__ == "__main__":
MainApp().run()
And the slightly modified kv:
<WindowManager#ScreenManager>:
<MainPage#Screen>:
Button:
text: 'Go To MyScreen'
on_release: app.root.current = "MyScreen"
<MyScreen>: # the #Screen is not needed since the MyScreen class is defined in the python code
name: 'MyScreen'
BoxLayout:
orientation: "vertical"
BoxLayout:
orientation: "vertical"
id: FillIn
WindowManager:
MainPage:
MyScreen:

Setting Global variables Kivy

I am using the Screen Manager to manage several different screens. One screen has two buttons that both lead to another screen, but depending on which button was pressed, I would like a label to display different text on the second screen. Here is my code:
.py
MY_GLOBAL = "test"
class ChooseProgScreen(Screen):
global MY_GLOBAL
def setTitle(self, newTitle):
MY_GLOBAL = newTitle
print(MY_GLOBAL)
class SwitchScreen(Screen):
global MY_GLOBAL
def getTitle(self):
return MY_GLOBAL
class ScreenManagement(ScreenManager):
pass
class MainApp(App):
def build(self):
presentation = Builder.load_file("kivy.kv")
return presentation
.kv
ScreenManagement:
transition: FadeTransition()
HomeScreen:
ChooseProgScreen:
SwitchScreen:
NewProgScreen:
<ChooseProgScreen>:
name: "chooseprog"
FloatLayout:
Button:
text: "test1"
on_release:
root.setTitle("test1")
app.root.current = "switch"
color: 1,1,1,1
font_size: 25
size_hint: 0.15,0.15
pos_hint: {"center_x":.1, "center_y":.9}
Button:
text: "test2"
on_release:
root.setTitle("test2")
app.root.current = "switch"
color: 1,1,1,1
font_size: 25
size_hint: 0.15,0.15
pos_hint: {"center_x":.3, "center_y":.9}
<SwitchScreen>:
name: "switch"
FloatLayout:
Label:
text: root.getTitle()
pos_hint: {"center_x":.1, "center_y":.1}
font_size: 25
In ChooseProgScreen in .kv, when the button is released, I call a method from the .py file that sets the global variable to a new screen and prints it. When you press one of the buttons, the print part works fine, and the global variable prints as the new string, but the SwitchScreen label still shows
"test"
and not
"test1" or "test2"
I think global variables is probably a terrible way of doing this, but I am at a loss for how else to do it using the Screen manager and the kivy language. If someone could help use global variables properly, or suggest a better way to do this, that would be greatly appreciated.
EDIT
Problem was that the screen only updated upon the first load. I added an update method to SwitchScreen:
def update(self):
self.ids.switchtitle.text = self.getTitle()
and updated the SwitchScreen in the .kv file:
<SwitchScreen>:
on_enter:
root.update()
Label:
id: switchtitle
text: root.getTitle()
pos_hint: {"center_x":.1, "center_y":.1}
font_size: 25
Solution
Define a class attribute, MY_GLOBAL of type StringProperty in your root widget, class ScreenManagement().
Update MY_GLOBAL in kv file.
Example
main.py
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
from kivy.properties import StringProperty
class ChooseProgScreen(Screen):
pass
class SwitchScreen(Screen):
pass
class ScreenManagement(ScreenManager):
MY_GLOBAL = StringProperty('test')
class MainApp(App):
def build(self):
return Builder.load_file("kivy.kv")
if __name__ == "__main__":
MainApp().run()
kivy.kv
#:kivy 1.10.0
#:import FadeTransition kivy.uix.screenmanager.FadeTransition
ScreenManagement:
transition: FadeTransition()
ChooseProgScreen:
SwitchScreen:
<ChooseProgScreen>:
name: "chooseprog"
FloatLayout:
Button:
text: "test1"
on_release:
root.manager.MY_GLOBAL = self.text
root.manager.current = "switch"
color: 1,1,1,1
font_size: 25
size_hint: 0.15,0.15
pos_hint: {"center_x":.1, "center_y":.9}
Button:
text: "test2"
on_release:
root.manager.MY_GLOBAL = self.text
root.manager.current = "switch"
color: 1,1,1,1
font_size: 25
size_hint: 0.15,0.15
pos_hint: {"center_x":.3, "center_y":.9}
<SwitchScreen>:
name: "switch"
FloatLayout:
Label:
text: root.manager.MY_GLOBAL
pos_hint: {"center_x":.1, "center_y":.1}
font_size: 25
Output
class ChooseProgScreen(Screen):
def setTitle(self, newTitle):
global MY_GLOBAL
MY_GLOBAL = newTitle
print(MY_GLOBAL)
That should resolve your issue. MY_GLOBAL would technically be available via self.MY_GLOBAL, instead you assigned it to a new variable under the method but didn't assign it to the global variable object.

Kivy callback custom widget call from ids raise AttributeError

I'm trying to call a custom widget method from its ids.
But I received an AttributeError: 'LabelBox' object has no attribute 'change_first_text'.
As a simple as possible working example can be found here with the PanelApp.py file:
from kivy.app import App
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
Builder.load_file("panel.kv")
class LabelBox(BoxLayout):
def __init__(self, *args, **kwargs):
super(LabelBox, self).__init__(*args, **kwargs)
def change_first_text(self, text):
self.ids.first.text = text
class ButtonList(BoxLayout):
pass
class Test(TabbedPanel):
pass
class TabbedPanelApp(App):
def build(self):
self.test = Test()
self.btn_list = ButtonList()
self.vbox = BoxLayout(orientation="vertical")
self.vbox.add_widget(self.btn_list)
self.vbox.add_widget(self.test)
return self.vbox
def change_first(self, value):
print("Button clicked and new value is: '{}'".format(value))
self.test.ids.lbs.change_first_text(value)
if __name__ == '__main__':
TabbedPanelApp().run()
and the panel.kv file:
<ButtonList#ButtonList>:
orientation: "horizontal"
Button:
text: "change fisrt to me"
on_press: app.change_first(self.text)
Button:
text: "change two to me"
<LabelBox#BoxLayout>:
Label:
id: first
text: "first"
Label:
id: two
text: "two"
<Test>:
size_hint: .5, .5
pos_hint: {'center_x': .5, 'center_y': .5}
do_default_tab: False
TabbedPanelItem:
text: 'first tab'
LabelBox:
id: lbs
Calling the script cause a runtime error that I can't understand.
Have you any clue on how to manage such event callback through the application?
The problem in your case is that you are creating 2 classes called LabelBox:
1.
class LabelBox(BoxLayout):
def __init__(self, *args, **kwargs):
super(LabelBox, self).__init__(*args, **kwargs)
def change_first_text(self, text):
self.ids.first.text = text
2.
<LabelBox#BoxLayout>:
Label:
id: first
text: "first"
Label:
id: two
text: "two"
I understand that you only want to have a class so it is appropriate to do the creation with the inheritance in the .py and only the implementation of children in the .kv. The solution is to change delete #BoxLayout in the .kv
<LabelBox>:
Label:
id: first
text: "first"
Label:
id: two
text: "two"

python/kivy : access attribute value in .kv file

When I click on Account(root.display_account()) then call display_account().After that RVACCOUNT() function call .After that when i click on +Add Account then def add_account(self): call
I have a class AccountPopup which define a attribute state_text and assign value text:'Testing' in .kv file
How to get value of state_text 'Testing' and pass in on_text: root.filter(self.text,state_text) and print in def filter function.
test.py
class AccountPopup(Popup):
state_text = ObjectProperty(None)
popupAccountCity = ObjectProperty(None)
def display_cities_treeview_account(self, instance):
if len(instance.text) > 0:
#if self.popupAccountCity is None:
self.popupAccountCity = TreeviewCityAccount(self.state_text.text)
self.popupAccountCity.filter(instance.text,self.state_text.text)
self.popupAccountCity.open()
class TreeviewCityAccount(Popup):
state_text = ObjectProperty(None)
def __init__(self,state_text, **kwargs):
print(state_text)
def filter(self, f,state):
print(state)
class RVACCOUNT(BoxLayout):
def add_account(self):
self.mode = "Add"
popup = AccountPopup(self)
popup.open()
class MainMenu(BoxLayout):
def display_account(self):
self.dropdown.dismiss()
self.remove_widgets()
self.rvaccount = RVACCOUNT()
self.content_area.add_widget(self.rvaccount)
class FactApp(App):
title = "Test"
def build(self):
self.root = Builder.load_file('test.kv')
return MainMenu()
if __name__ == '__main__':
FactApp().run()
test.kv
<AccountPopup>:
state_text:state_text
TextInput:
id:state_text
text:'Testing'
<TreeviewCityAccount>:
BoxLayout
orientation: "vertical"
TextInput:
id: treeview
size_hint_y: .1
on_text: root.filter(self.text,state_text)
<RVACCOUNT>:
BoxLayout:
orientation: "vertical"
Button:
size_hint: .07, .03
text: "+Add Account"
on_press: root.add_account()
<MainMenu>:
content_area: content_area
dropdown: dropdown
BoxLayout:
orientation: 'vertical'
#spacing : 10
BoxLayout:
canvas.before:
Rectangle:
pos: self.pos
size: self.size
MenuButton:
id: btn
text: 'Master'
size : (60,30)
on_release: dropdown.open(self)
CustDrop:
DropdownButton:
text: 'Account'
size_hint_y: None
height: '32dp'
on_release: root.display_account()
Can someone help me?
You should reference it as self.state_text everywhere, also make it a StringProperty in the py file and can than access it as
on_text: root.filter(self.text,root.state_text)
root in kv refers to the most left widget aka <TreeviewCityAccount>: in your case.
See https://kivy.org/docs/api-kivy.lang.html
Alternatively you can work with ids in the kv file.
the value you are looking for is not in your immediate root which is why this is not working.The say thing to do is get the full path to that property like so:
Snippet:
<AccountPopup>:
id: ac_popup
#bunch of code
<TreeviewCityAccount>:
#chunk of code
TextInput:
id: tree view
on_text:root.filter(self.text,app.ac_popup.state_text
Also,generally,it's a good idea to id your classes mate
Disclaimer:code not tested

Customizing Popup Screen in Kivy 1.10.0

I'm currently trying to create a customized MessageBox in Python 3.6 using Kivy 1.10.0. I want to use it first as a MessageBox for error message when user entered incorrect username or password.
I'm getting an attribute error whenever I call the open function from CalcPopUp class using the nextScreen function from CalcRoot class.
This is the codes that I have:
class CalcRoot(BoxLayout):
def __init__(self,**kwargs):
super(CalcRoot,self).__init__(**kwargs)
self.calc_popup = CalcPopUp(**kwargs)
def nextScreen(self, next_screen):
#I have some conditions inside this function which works fine
CalcPopUp.open(self, "Incorrect Login", True)`
class CalcPopUp(Popup):
popup_message = ObjectProperty()
popup_button = ObjectProperty()
def __init__(self, *args, **kwargs):
super(CalcPopUp,self).__init__(*args, **kwargs)
def open(self, app_message, with_button=True):
#if user selected the button attribute as true show button else remove
if with_button:
if self.popup_button in self.content.children:
self.content.remove_widget(self.popup_button)
# if answer is wrong, display button if not visible
else:
if self.popup_button not in self.content.children:
self.content.add_widget(self.popup_button)
#display text message
self.message.text = app_message
#display pop up
super(CalcPopUp, self).open()
This is the error that I'm getting:
AttributeError: 'CalcRoot' object has no attribute 'popup_button'
This is the content of the kivy file associated to my screenpop:
<CalcPopUp>:
size_hint: .8, .4
title: "Message"
title_size: root.height *.05
auto_dismiss: False
separator_color: COLOR("#fcfc02") #yellow
popup_button: popup_button
popup_message: popup_message
BoxLayout:
orientation: 'horizontal'
padding: root.width * .02, root.height * .02
spacing: min(root.height, root.width) * .02
Label:
id: popup_message
text: ""
halign: 'left'
font_size: root.height / 10
center_y: .5
markup: True
Button:
id: popup_button
text: 'Ok'
size_hint: 1, None
height: root.height / 20
on_release: root.dismiss()
Here's what I did:
First of all, remove lines 7 and 8 in the .kv file. I'm not sure whether there is an indentation error in your original post, but here's how the .kv file should look now:
<CalcPopUp>:
size_hint: .8, .4
title: "Message"
title_size: root.height *.05
auto_dismiss: False
BoxLayout: # this is indented to be inside CalcPopUp
orientation: 'horizontal'
... # etc., no changes other than indentation...
I've changed the .py file structure quite a bit, take a look and tell me if there's anything I need to make explain:
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.base import runTouchApp
from kivy.base import Builder
class CalcRoot(BoxLayout):
def __init__(self, **kwargs):
super(CalcRoot, self).__init__(**kwargs)
def nextScreen(self, next_screen):
# I have some conditions inside this function which works fine
popup = CalcPopUp("Incorrect Login", True)
popup.open()
class CalcPopUp(Popup):
popup_message = ObjectProperty()
popup_button = ObjectProperty()
def __init__(self, app_message, with_button=True, **kwargs):
super().__init__(**kwargs)
# if user selected the button attribute as true show button else remove
if with_button:
if self.popup_button in self.content.children:
self.content.remove_widget(self.popup_button)
# if answer is wrong, display button if not visible
else:
if self.popup_button not in self.content.children:
self.content.add_widget(self.popup_button)
# display text message
self.ids.popup_message.text = app_message
Builder.load_file("calcpopup.kv")
root = CalcRoot()
root.nextScreen(next_screen=None)
runTouchApp(root)

Categories

Resources