Kivy TabbedPanel. How to call switch_to from another class? - python

In below code I need the button on the edit_button_tab to switch to edit_input_tab. I really need to switch it that way as I need to switch between predefined classes EditButton and EditInput. This is a part of a bigger program with few Buttons in different location of a layout and I cant define them within <MainTabbedPanel> class. I've tried many ways to call switch_to (example in the quotes) but they didn't work.
CODE
from kivy.animation import Animation
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelStrip
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelHeader
from kivy.factory import Factory
theRoot = """
#:import Factory kivy.factory.Factory
<EditButton>
orientation: 'vertical'
Button:
text: 'Switch to Edit Screen'
on_press: root.change_tab('edit_screen')
<EditInput>
orientation: 'vertical'
TextInput:
<UnCloseableHeader>
color: 0,0,0,1
disabled_color: self.color
# variable tab_width
text: 'tabx'
size_hint_x: None
width: self.texture_size[0] + 40
BoxLayout:
pos: root.pos
size_hint: None, None
size_y: 20
padding: 3
Label:
id: lbl
text: root.text
<MainTabbedPanel#BoxLayout>
size_hint: (1, 1)
default_tab: edit_button_tab
tab_width: 130
FloatLayout:
EditButton:
id: edit_button
EditInput:
id: edit_input
UnCloseableHeader:
id: edit_button_tab
text: 'Edit'
content: edit_button.__self__
UnCloseableHeader:
id: edit_input_tab
text: 'Edit Tab'
content: edit_input.__self__
MainTabbedPanel:
"""
class EditInput(BoxLayout):
def __init__(self, **kwargs):
super(EditInput, self).__init__(**kwargs)
class EditButton(BoxLayout):
def __init__(self, **kwargs):
super(EditButton, self).__init__(**kwargs)
def change_tab(self, tab):
print('TAB', tab)
#call switch method from MainTabbedPanel
'''the way I've tried
mtp = MainTabbedPanel
mtp.switch_to('edit_input_tab')'''
class MainTabbedPanel(TabbedPanel):
def __init__(self, **kwargs):
super(MainTabbedPanel, self).__init__(**kwargs)
def switch(self, tab):
print("SWITCH TO", tab, self.ids.keys())
self.switch_to(self.ids[tab])
class UnCloseableHeader(TabbedPanelHeader):
pass
Factory.register('UnCloseableHeader', cls=UnCloseableHeader)
sm = Builder.load_string(theRoot)
class TabbedPanelApp(App):
def build(self):
return sm
if __name__ == '__main__':
TabbedPanelApp().run()
EDIT
I've tried with below snippet. It prints IDS of MainTabbedPanel but does not change the tabs.
class EditButton(BoxLayout):
def __init__(self, **kwargs):
super(EditButton, self).__init__(**kwargs)
def change_tab(self, tab):
print('TAB', tab)
MainTabbedPanel.tab = tab
MainTabbedPanel()
#call switch method from MainTabbedPanel
'''the way I've tried
mtp = MainTabbedPanel
mtp.switch_to('edit_input_tab')'''
class MainTabbedPanel(TabbedPanel):
tab = ''
def __init__(self, **kwargs):
super(MainTabbedPanel, self).__init__(**kwargs)
self.tabs_showing = True
if self.tab != '':
Clock.schedule_once(self.switch)
def switch(self, dt):
print("SWITCH TO", self.tab, self.ids.keys())
self.switch_to(self.ids[self.tab])

Use App.get_running_app() to get an instance of your app
Use root to get an instance of your root
Snippets
def change_tab(self, tab):
print('TAB', tab)
mtp = App.get_running_app().root
mtp.switch_to(mtp.ids.edit_input_tab)
Notes
In your kv, you defined a Dynamic class,
<MainTabbedPanel#BoxLayout>. It should be a class rule,
<MainTabbedPanel> because in your Python code, you have defined
class MainTabbedPanel(TabbedPanel): i.e. inheritance mismatch and
class type mismatch.

Related

Popup - code conversion to KV and PY files

I would like to have code in KV and PY files that will display a YES/NO popup that I can instantiate anywhere in my app to pop the question.
Will a good kindly person help me to convert code I found on internet to KV file and PY file content.
I would like to be able to create an instance of this popup at any point in my app for display.
This is the sample I am working from:
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
from kivy.properties import StringProperty
Builder.load_string('''
<ConfirmPopup>:
cols:1
Label:
text: root.text
GridLayout:
cols: 2
size_hint_y: None
height: '44sp'
Button:
text: 'Yes'
on_release: root.dispatch('on_answer','yes')
Button:
text: 'No'
on_release: root.dispatch('on_answer', 'no')
''')
class ConfirmPopup(GridLayout):
text = StringProperty()
def __init__(self,**kwargs):
self.register_event_type('on_answer')
super(ConfirmPopup,self).__init__(**kwargs)
def on_answer(self, *args):
pass
class PopupTest(App):
def build(self):
content = ConfirmPopup(text='Do You Love Kivy?')
content.bind(on_answer=self._on_answer)
self.popup = Popup(title="Answer Question",
content=content,
size_hint=(None, None),
size=(480,400),
auto_dismiss= False)
self.popup.open()
def _on_answer(self, instance, answer):
print "USER ANSWER: " , repr(answer)
self.popup.dismiss()
if __name__ == '__main__':
PopupTest().run()
I have tried the following:
KV file:
<YesNoPopup>:
cols:1
Label:
text: root.text
GridLayout:
cols: 2
size_hint_y: None
height: '44sp'
Button:
text: 'Yes'
on_release: root.dispatch('on_answer','yes')
Button:
text: 'No'
on_release: root.dispatch('on_answer', 'no')
and PY file:
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
from kivy.properties import StringProperty
class YesNoPopup(GridLayout):
text = StringProperty()
def __init__(self, **kwargs):
self.register_event_type('on_answer')
super(YesNoPopup, self).__init__(**kwargs)
def on_answer(self, *args):
pass
class PopupTest:
def build(self):
content = YesNoPopup(text='Are you sure?')
content.bind(on_answer=self._on_answer)
self.popup = Popup(title='PIN',
content=content,
size_hint=(0.6, 0.4),
auto_dismiss=False)
self.popup.open()
def _on_answer(self, instance, answer):
print
"USER ANSWER: ", repr(answer)
self.dismiss()
Then in the App
def pin_action(self):
pop = PopupTest()
pop.build()
But this only shows popup with Title and separator bar.
I will appreciate explanations of why you do what you do so that I as an 82 year old can learn and understand since my OO education is 22 years old and my Python Kivy is still in the learning stage.
Thanks a million
You can simplify your code by just calling the _on_answer() method directly from the on_release: of the Button. Here is a modified version of your code that does that:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
from kivy.properties import StringProperty
Builder.load_string('''
<ConfirmPopup>:
cols:1
Label:
text: root.text
GridLayout:
cols: 2
size_hint_y: None
height: '44sp'
Button:
text: 'Yes'
on_release: app._on_answer(root, 'yes')
Button:
text: 'No'
on_release: app._on_answer(root, 'no')
''')
class ConfirmPopup(GridLayout):
text = StringProperty()
class PopupTest(App):
def build(self):
content = ConfirmPopup(text='Do You Love Kivy?')
self.popup = Popup(title="Answer Question",
content=content,
size_hint=(None, None),
size=(480, 400),
auto_dismiss=False)
self.popup.open()
def _on_answer(self, instance, answer):
print("USER ANSWER: ", repr(answer))
self.popup.dismiss()
if __name__ == '__main__':
PopupTest().run()
Now that I understand your question, the main problem is just that the bind() method creates a WeakMethod for the bound method. In your code:
def pin_action(self):
pop = PopupTest()
pop.build()
An instance of PopupTest is created (which contains the bound _on_answer() method). But after the build() method of PopupTest is executed, the Popup is opened and the pop variable gets garbage collected. So the _on_answer() method is also garbage collected as a result. Since bind() creates a WeakMethod, there is no method left for the dispatched event to trigger.
There are two ways (that I know of) to fix this issue. One is to just modify the pin_action() method to retain a reference to PopTest so that is does not get garbage collected:
def pin_action(self):
self.pop = PopupTest()
self.pop.build()
Another way is to use fbind() instead of bind(), since fbind() keeps a reference to the bound method, so it cannot be garbage collected:
class PopupTest:
def build(self):
content = YesNoPopup(text='Are you sure?')
# content.bind(on_answer=self._on_answer)
content.fbind('on_answer', self._on_answer)

Interrupting Kivy Toggle Button State Change on Click

I've got a toggle button that I'm using to start and stop a process. When the process is being stopped, I have a popup box that comes up, asking the user to enter a password to confirm they want to end the process.
Only once the correct password is provided do I want the state of the toggle button to change from "down" to "normal" as the process being ended uses the toggle button state to determine if the process should continue running.
The problem I'm having at the moment is that the moment the toggle button is pressed, the state changes from "down" to "normal" and thus ends the process before the password box to authenticate the user is shown.
Any advice on how to interrupt the state change of the toggle button when it is clicked?
EDITED!
main.py:
# import kivy modules
from kivy.app import App
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.properties import ObjectProperty
from kivy.config import Config
Config.set('kivy', 'exit_on_escape', '0')
# import self defined backend class
from ProcessPanel import ProcessPanel
class Automation(App):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def build(self):
return AppPanels()
class AppPanels(TabbedPanel):
process_tab = ObjectProperty(None)
process_tab_panel = ObjectProperty(None)
def __init__(self, **kwargs):
super().__init__(**kwargs)
if __name__ == '__main__':
Automation().run()
ProcessPanel.py:
# import kivy modules
from kivy.properties import Clock
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.togglebutton import ToggleButton
from kivy.properties import ObjectProperty
# import self defined backend classes
from ToolStopWindow import ToolStopWindow
class ProcessPanel(BoxLayout):
process_toggle = ObjectProperty()
def __init__(self, **kwargs):
# inherit BoxLayout Attributes and Methods
super().__init__(**kwargs)
self.num = 0
# create the custom query pop up window
self.TSW = ToolStopWindow(passwd="testPassword",
title_text="Are you sure you want to stop the process?",
sub_title_text="Enter Password to Stop Process...",
external_button=self.process_toggle)
self.TSW.confirm_btn.bind(on_release=self.end_process)
self.process_toggle.bind(_do_press=self.toggle_switch_state())
self.process_schedule = []
# Determine if process has been activated
def toggle_switch_state(self):
if self.process_toggle.state == "normal":
self.process_schedule = Clock.schedule_interval(self.my_process, 5)
self.process_toggle._do_unpress()
else:
self.TSW.open()
def my_process(self, dt):
self.num = self.num + 1
print(self.num)
def end_process(self):
self.process_schedule.cancel()
class StartStopToggle(ToggleButton):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def _do_unpress(self):
if (not self.allow_no_selection and
self.group and self.state == 'down'):
return
self._release_group(self)
self.state = 'normal' if self.state == 'down' else 'down'
ToolStopWindow.py:
import time
from kivy.properties import ObjectProperty
from kivy.uix.popup import Popup
class ToolStopWindow(Popup):
passwd_box = ObjectProperty()
passwd = ObjectProperty()
confirm_btn = ObjectProperty()
confirm_btn_callback = ObjectProperty()
cancel_btn = ObjectProperty()
title_text = ObjectProperty()
sub_title = ObjectProperty()
sub_title_text = ObjectProperty()
external_button = ObjectProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.is_true = False
def check_pass(self):
if self.passwd_box.text == self.passwd:
self.sub_title.text = "Password Correct!"
time.sleep(1)
self.external_button._do_unpress()
self.dismiss()
else:
self.is_true = False
self.passwd_box.text = ""
self.sub_title.text = "Invalid Password!"
return
def reset_label_text(self):
if self.sub_title.text != self.sub_title_text:
self.sub_title.text = self.sub_title_text
Automation.kv:
<AppPanels>:
process_tab: process_tab
process_tab_panel: process_tab_panel
id: Tab_Level
size_hint: 1, 1
pos_hint: {'center_x': .5, 'center_y': .5}
do_default_tab: False
tab_pos: 'top_mid'
tab_width: root.width/5
font_size: 24
TabbedPanelItem:
id: process_tab
text: "Process Tab"
ProcessPanel:
id: process_tab_panel
<ProcessPanel>:
orientation: "vertical"
process_toggle: process_toggle
Button:
text: "normal button"
on_release: root.my_process(self)
StartStopToggle:
id: process_toggle
on_state: root.toggle_switch_state()
<StartStopToggle>:
text: "Start Process Schedule"
font_size: 36
<ToolStopWindow>:
id: close_window
auto_dismiss: False
title: root.title_text
size_hint: 0.8, 0.8
passwd_box: passwd_box
confirm_btn: confirm_btn
cancel_btn: cancel_btn
sub_title: sub_title
BoxLayout:
id: main_panel
orientation: "vertical"
Label:
id: sub_title
text: root.sub_title_text
font_size: 36
TextInput:
id: passwd_box
multiline: False
password: True
on_text: root.reset_label_text()
on_text_validate: root.check_pass()
BoxLayout:
id: Buttons
orientation: "horizontal"
Button:
id: confirm_btn
text: "Confirm!"
on_release: root.check_pass()
Button:
id: cancel_btn
text: "Cancel"
on_release: root.dismiss()
What I would like to do is bind the _do_press function to a function in the ProcessPanel class, but I keep getting an attribute error saying "'None type' object has no attribute 'bind'", i assume this is becuase the id's i'm assigning to the UI objects are assigned after init?
Additionally, there i'm a bit stuck on how to cancel the clock i have created to periodically call the my_process function when the correct password is given.
I hope the addition of this example is helpful!
I would probably subclass the ToggleButton to override the _do_press methods do do the work of opening your popup and then
https://github.com/kivy/kivy/blob/master/kivy/uix/behaviors/togglebutton.py#L112-L115
class ConfirmPopup(Popup):
button = ObjectProperty()
password = StringProperty()
def check_password(self, pass):
if self.ids.password.text == self.password:
self.button._do_unpress()
self.dismiss()
class PasswordCheckedToggleButton(ToggleButton):
def _do_press(self):
ConfirmPopup(button=self, password="secret").open()
def _do_unpress(self):
if (not self.allow_no_selection and
self.group and self.state == 'down'):
return
self._release_group(self)
self.state = 'normal' if self.state == 'down' else 'down'
with something like
<ConfirmPopup>:
title: "password!"
BoxLayout:
orientation: "vertical"
TextInput:
id: password
Button:
text: "it’ a me!"
on_release: root.check_password()
Hi I figured out how to use your answer in my code, posted answer below in case anyone finds it useful.
Not sure why but in a stand alone example, the on_request_close feature isnt working, despite the code being the same as the project i'm working on and it working there, spent a while trying to locate the issue but can't find it.... Either way its working for me now so that even when i try to close the app, i am asked for a password to stop the process before the app will close
main.py:
# import kivy modules
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.properties import ObjectProperty
from kivy.config import Config
Config.set('kivy', 'exit_on_escape', '0')
# import self defined backend class
from StartStopToggle import StartStopToggle
from ProcessPanel import ProcessPanel
from ToolStopWindow import ToolStopWindow
class Automation(App):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def build(self):
Window.bind(on_request_close=self.on_request_close)
return AppPanels()
def on_request_close(self, *args):
if self.root.process_tab_panel.process_toggle.state == "down":
self.root.process_tab_panel.process_toggle.TSW.opening_object = self
self.root.process_tab_panel.process_toggle.TSW.open()
else:
self.stop()
return True
def do_tsw_function(self):
self.stop()
class AppPanels(TabbedPanel):
process_tab = ObjectProperty(None)
process_tab_panel = ObjectProperty(None)
def __init__(self, **kwargs):
super().__init__(**kwargs)
if __name__ == '__main__':
Automation().run()# import kivy modules
from kivy.app import App
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.properties import ObjectProperty
from kivy.config import Config
Config.set('kivy', 'exit_on_escape', '0')
# import self defined backend class
from ProcessPanel import ProcessPanel
class Automation(App):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def build(self):
return AppPanels()
class AppPanels(TabbedPanel):
process_tab = ObjectProperty(None)
process_tab_panel = ObjectProperty(None)
def __init__(self, **kwargs):
super().__init__(**kwargs)
if __name__ == '__main__':
Automation().run()
ProcessPanel.py:
# import kivy modules
from kivy.properties import Clock
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.togglebutton import ToggleButton
from kivy.properties import ObjectProperty
# import self defined backend classes
from ToolStopWindow import ToolStopWindow
class ProcessPanel(BoxLayout):
process_toggle = ObjectProperty()
def __init__(self, **kwargs):
# inherit BoxLayout Attributes and Methods
super().__init__(**kwargs)
self.num = 0
# create the custom query pop up window
self.process_schedule = []
# Determine if process has been activated
def toggle_switch_state(self):
if self.process_toggle.state == "down":
self.process_schedule = Clock.schedule_interval(self.my_process, 5)
self.process_toggle.text = "Stop Process Schedule"
else:
self.process_schedule.cancel()
self.process_toggle.text = "Start Process Schedule"
def my_process(self, dt):
self.num = self.num + 1
print(self.num)
def end_process(self):
self.process_schedule.cancel()
ToolStopWindow.py
import time
from kivy.properties import ObjectProperty
from kivy.uix.popup import Popup
class ToolStopWindow(Popup):
passwd_box = ObjectProperty()
passwd = ObjectProperty()
confirm_btn = ObjectProperty()
confirm_btn_callback = ObjectProperty()
cancel_btn = ObjectProperty()
title_text = ObjectProperty()
sub_title = ObjectProperty()
sub_title_text = ObjectProperty()
opening_object = ObjectProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
def check_pass(self):
if self.passwd_box.text == self.passwd:
self.passwd_box.text = ""
self.sub_title.text = "Password Correct!"
time.sleep(1)
self.dismiss()
self.opening_object.do_tsw_function()
else:
self.passwd_box.text = ""
self.sub_title.text = "Invalid Password!"
def reset_label_text(self):
if self.sub_title.text != self.sub_title_text:
self.sub_title.text = self.sub_title_text
StartStopToggle.py:
from kivy.uix.togglebutton import ToggleButton
from ToolStopWindow import ToolStopWindow
class StartStopToggle(ToggleButton):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.TSW = ToolStopWindow(passwd="password",
title_text="Are you sure you want to stop the process?",
sub_title_text="Enter Password to Stop Process...")
def _do_press(self):
self.TSW.opening_object = self
self.TSW.open()
def do_tsw_function(self):
self._do_actual_press()
def _do_actual_press(self):
if (not self.allow_no_selection and
self.group and self.state == 'down'):
return
self._release_group(self)
self.state = 'normal' if self.state == 'down' else 'down'
Automation.kv
<AppPanels>:
process_tab: process_tab
process_tab_panel: process_tab_panel
id: Tab_Level
size_hint: 1, 1
pos_hint: {'center_x': .5, 'center_y': .5}
do_default_tab: False
tab_pos: 'top_mid'
tab_width: root.width/5
font_size: 24
TabbedPanelItem:
id: process_tab
text: "Process Tab"
ProcessPanel:
id: process_tab_panel
<ProcessPanel>:
orientation: "vertical"
process_toggle: process_toggle
Button:
text: "normal button"
on_release: root.my_process(self)
StartStopToggle:
id: process_toggle
on_state: root.toggle_switch_state()
<StartStopToggle>:
text: "Start Query Schedule"
<ToolStopWindow>:
id: close_window
auto_dismiss: False
title: root.title_text
size_hint: 0.8, 0.8
passwd_box: passwd_box
confirm_btn: confirm_btn
cancel_btn: cancel_btn
sub_title: sub_title
BoxLayout:
id: main_panel
orientation: "vertical"
Label:
id: sub_title
text: root.sub_title_text
font_size: 36
TextInput:
id: passwd_box
multiline: False
password: True
on_text: root.reset_label_text()
on_text_validate: root.check_pass()
BoxLayout:
id: Buttons
orientation: "horizontal"
Button:
id: confirm_btn
text: "Confirm!"
on_release: root.check_pass()
Button:
id: cancel_btn
text: "Cancel"
on_release: root.dismiss()

AccordionItem title property is empty even though its set in kv file?

I am trying to access title property of AccordionItem in python code, that is being set in kv file. The property is empty string even though it is correctly displayed in GUI.
Is there a way to access value of this property or am I missing something here?
kv file
<TableModifications>:
accordion: accordion
MDAccordion:
id: accordion
orientation: 'vertical'
size_hint_x: None
width: '240dp'
TableAccordionItem:
title:'Add' # I want to access this in python code
icon: 'plus'
TableAccordionItem:
title:'Edit'
icon: 'pencil'
TableAccordionItem:
title:'Remove'
icon: 'delete'
python code
class TableModifications(BoxLayout):
pass
class TableAccordionItem(MDAccordionItem):
def __init__(self, **kwargs):
super(TableAccordionItem, self).__init__(**kwargs)
print(self.title) # this is empty, why?
def add_widget(self, widget, *args):
super(TableAccordionItem, self).add_widget(widget, *args)
print(self.title) # this is empty, why?
You need to use kivy's ObjectProperty that will magically bind to widgets in your kv. Also, if a widget has already been added to your widget tree in kv lang, then you don't need to add that widget again in python. add_widget is not a method you need to override. If you want to add a widget you do something like: self.add_widget(Label(text="Hello")).
Example:
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.properties import ObjectProperty
from kivy.app import App
from kivy.lang import Builder
Builder.load_string("""
<RootWidget>:
accord_1: accord_1
accord_2: accord_2
accord_3: accord_3
Accordion:
orientation: "vertical"
AccordionItem:
id: accord_1
title: "Title 1"
Label:
text: "Accordion One Content."
AccordionItem:
id: accord_2
title: "Title 2"
Label:
text: "Accordion Two Content."
AccordionItem:
id: accord_3
title: "Title 3"
""")
class RootWidget(BoxLayout):
accord_1 = ObjectProperty(None)
accord_2 = ObjectProperty(None)
accord_3 = ObjectProperty(None)
def __init__(self, *args, **kwargs):
super(RootWidget, self).__init__(*args, **kwargs)
self.accord_3.add_widget(Label(text="Accordion Three Content."))
for ac in [self.accord_1, self.accord_2, self.accord_3]:
print ac.title
class TestApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
TestApp().run()

Can the Kivy language access inherited layouts and widgets?

Can the kivy language access inherited layouts and widgets? I want to create one basic BoxLayout that contains the styling and title Label for my widget. I want to be able to inherit from this widget and add additional widgets in different positions.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
Builder.load_string('''
<SimpleBar>:
canvas.before:
Color:
rgba: 0, 0.5, 0.5, 1
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
id: my_layout
Label:
text: "hi"
<NewBar>:
Label:
text: "2"
''')
class SimpleBar(BoxLayout):
def log(self, value):
print(value)
class NewBar(SimpleBar):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
print(dir(self))
class GeneralApp(App):
def build(self):
return NewBar()
if __name__ == '__main__':
GeneralApp().run()
Above is my basic running widget.
I want NewBar's "2" Label to be located before SimpleBar's 'hi' Label like below.
<NewBar>:
BoxLayout:
id: my_layout
Label:
text: "2"
Label:
text: "hi"
I know that - can negate items. However, <-NewBar> removes all of my styling.
Is there any way to do this in the kivy language?
Here's a fun thing: you don't need to specify all classes used in kv lang in the lang itself - you can also add them using Factory.register method later in code. Here's an example:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.lang import Builder
from kivy.factory import Factory
from functools import partial
Builder.load_string('''
<MyWidget>:
Foo
Bar
''')
class MyWidget(BoxLayout):
pass
class MyApp(App):
def build(self):
Factory.register('Foo', cls=partial(Label, text='foo'))
Factory.register('Bar', cls=partial(Label, text='bar'))
return MyWidget()
if __name__ == '__main__':
MyApp().run()
Let's use it to create a template base widget we later fill with various content. We use a placeholder that we later replace with another widget:
<BaseWidget>:
orientation: 'vertical'
Label:
size_hint: None, 0.1
text: 'title'
Placeholder
In the Python code we register a placeholder class in the __init__ method of this base template class.
class BaseWidget(BoxLayout):
def __init__(self, **args):
# unregister if already registered...
Factory.unregister('Placeholder')
Factory.register('Placeholder', cls=self.placeholder)
super(BaseWidget, self).__init__(**args)
Now let's define a content class.
<TwoButtonWidget>:
Button:
text: 'button 1'
Button:
text: 'button 2'
And finally create a customized class that use our base class as a template and replaces its placeholder with a content class. This class don't have its own kivy rules (these are moved into content class) so when we inherite from our base template, no extra widgets are inserted.
# content class
class TwoButtonWidget(BoxLayout):
pass
# Base class subclass
class CustomizedWidget(BaseWidget):
placeholder = TwoButtonWidget # set contetnt class
A full example:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.factory import Factory
Builder.load_string('''
<BaseWidget>:
orientation: 'vertical'
widget_title: widget_title
placeholder: placeholder
Label:
size_hint: None, 0.1
id: widget_title
Placeholder
id: placeholder
<TwoButtonWidget>:
button1: button1
Button:
text: 'button 1'
id: button1
Button:
text: 'button 2'
<ThreeButtonWidget>:
orientation: 'vertical'
Button:
text: 'button a'
Button:
text: 'button b'
Button:
text: 'button c'
''')
class BaseWidget(BoxLayout):
def __init__(self, **args):
# unregister if already registered...
Factory.unregister('Placeholder')
Factory.register('Placeholder', cls=self.placeholder)
super(BaseWidget, self).__init__(**args)
class TwoButtonWidget(BoxLayout):
pass
class ThreeButtonWidget(BoxLayout):
pass
class CustomizedWidget1(BaseWidget):
placeholder = TwoButtonWidget
class CustomizedWidget2(BaseWidget):
placeholder = ThreeButtonWidget
class MyApp(App):
def build(self):
layout = BoxLayout()
c1 = CustomizedWidget1()
# we can access base widget...
c1.widget_title.text = 'First'
# we can access placeholder
c1.placeholder.button1.text = 'This was 1 before'
c2 = CustomizedWidget2()
c2.widget_title.text = 'Second'
layout.add_widget(c1)
layout.add_widget(c2)
return layout
if __name__ == '__main__':
MyApp().run()
You can easily extend it and for example, have multiple placeholders.
Applying this to your case:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.lang import Builder
from kivy.factory import Factory
from functools import partial
Builder.load_string('''
<SimpleBar>:
canvas.before:
Color:
rgba: 0, 0.5, 0.5, 1
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
Placeholder
Label:
text: "hi"
<NewBarContent>:
Label:
text: "2"
''')
class SimpleBar(BoxLayout):
def __init__(self, **args):
# unregister if already registered...
Factory.unregister('Placeholder')
Factory.register('Placeholder', cls=self.placeholder)
super(SimpleBar, self).__init__(**args)
class NewBarContent(BoxLayout):
pass
class NewBar(SimpleBar):
placeholder = NewBarContent
class MyApp(App):
def build(self):
return NewBar()
if __name__ == '__main__':
MyApp().run()
With simple kv no, because if you put something in the widget (e.g. Label: ...), it'll call <widget>.add_widget() method and when such a method is called without additional parameters, it will by default place the widget after whatever was already placed before it. Therefore you can either search through the kivy/lang/parser.py file and add such a functionality(PR welcome), or do it within python in hm... __init__ or wherever you'll like to add the widget (maybe after some event).
To do that within python you can call <widget (or self)>.add_widget(<child>, index=<where>) according to the docs. For example:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ListProperty
Builder.load_string('''
#:import Factory kivy.factory.Factory
<Ninja#Label>:
<SimpleBar>:
BoxLayout:
id: my_layout
Label:
text: "hi"
<ChildWithBenefits>:
placebefore:
[(Factory.Label(text='I am the first!'), 0),
(Factory.Ninja(text='No, I am!'), 2)]
''')
class SimpleBar(BoxLayout):
def log(self, value):
print(value)
class ChildWithBenefits(SimpleBar):
placebefore = ListProperty([])
def __init__(self, *args, **kwargs):
super(ChildWithBenefits, self).__init__(*args, **kwargs)
for child, index in self.placebefore:
print child.text
print type(child)
self.add_widget(child, index=index)
self.log('Hello!')
class GeneralApp(App):
def build(self):
return ChildWithBenefits()
if __name__ == '__main__':
GeneralApp().run()
If you want to do a composite widget, that accept new children and add them in one specific "container" widget, you have to do some python.
Basically the idea is to override add_widget so that once the basic structure of the widget is there, the new widgets are added using the new method.
Let's say you have this NestingWidget
class NestingWidget(BoxLayout):
title = StringProperty()
def activate(self):
# do something
pass
with this rule
<NestingWidget>:
Label:
text: root.title
BoxLayout:
Button:
on_press: root.activate()
and you want to use it like this:
FloatLayout:
NestingWidget:
title: 'first item'
Image:
source: '../examples/demo/pictures/images/Ill1.jpg'
this won't work immediatly, because the Image will be added as a direct child of NestingWidget, so it'll be under the Button.
Howether, you probably remarked that some widgets in kivy can accept new nested widgets, while being already complex on their own.
The trick to do that is — as said before — to override add_widget.
First, let's add an id to our container.
<NestingWidget>:
Label:
text: root.title
BoxLayout:
id: container
Button:
on_press: root.activate()
then, let's use it in add_widget.
class NestingWidget(BoxLayout):
…
def add_widget(self, *args, **kwargs):
if 'container' in self.ids:
return self.ids.container.add_widget(*args, **kwargs)
else:
return super(NestingWidget, self).add_widget(*args, **kwargs)

Not displaying Time in label in kivy

I am kind of new to kivy and i am trying to display time in it
Here is the python code snippet(removed the unnecessary screens/parts for arbitrary reasons):
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
import time
from kivy.uix.label import Label
class MyScreen(Screen):
def update(self, *args):
self.timeb.text = time.asctime()
return time.asctime()
class MyApp(App):
def build(self):
x=MyScreen()
root = ScreenManager()
Clock.schedule_interval(x.update, 1)
root.add_widget(password(name='Screen0'))
root.add_widget(Correct(name='Screena'))
root.add_widget(MyScreen(name='Screen1'))
s=[x,root]
for i in s:
return i
if __name__ == '__main__':
MyApp().run()
.kv file(removed the unnecessary screens/parts for arbitrary reasons):
MyScreen:
<MyScreen>: #
timeb:time_box
BoxLayout:
orientation: "horizontal"
pos_hint: {'top':1}
height: "40dp"
size_hint_y: None
Label:
id:time_box
text:root.update()
size_hint_x: 6
font_size:30
font_name:"Roboto-Light.ttf"
As you can see in the code i have added a few screens but the My screen is the first to come up also if i change the
s=[x,root]
for i in s:
return i
to just
return root
then the time doesn't update itself.
Could anyone help?
Thanks!
Maybe you forgot to type something, most probably your kv is coded wrong. Why is there that hanging MyScreen: and what is it assigned to? You need a main rule somewhere, but I see none. I assigned your MyScreen: to <MyApp>: as a normal return MyScreen() or return some_scrmanager would do and it works.
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.clock import Clock
import time
from kivy.uix.label import Label
from kivy.lang import Builder
Builder.load_string('''
<MyApp>:
MyScreen:
<MyScreen>: #
timeb:time_box
BoxLayout:
orientation: "horizontal"
pos_hint: {'top':1}
height: "40dp"
size_hint_y: None
Label:
id:time_box
text:root.update()
size_hint_x: 6
font_size:30
''')
class MyScreen(Screen):
def update(self, *args):
self.timeb.text = time.asctime()
return time.asctime()
class MyApp(App):
def build(self):
x=MyScreen()
root = ScreenManager()
Clock.schedule_interval(x.update, 1)
root.add_widget(MyScreen(name='Screen1'))
s=[x,root]
for i in s:
return i
if __name__ == '__main__':
MyApp().run()
And the same kv, but more reasonable python code would looke like this:
<ScreenMgr>:
MyScreen:
<MyScreen>:
...
class MyScreen(Screen):
def __init__(self, **kw):
super(MyScreen, self).__init__(**kw)
Clock.schedule_interval(self.update, 1)
def update(self, *args):
self.timeb.text = time.asctime()
return time.asctime()
class ScreenMgr(ScreenManager):
pass
class MyApp(App):
def build(self):
x=MyScreen()
root = ScreenMgr()
root.add_widget(MyScreen(name='Screen1'))
return root
if __name__ == '__main__':
MyApp().run()
Because there's no need to call update() from Screen class in the building function and even outside a class where you want to use it presuming it'll run forever(Do you plan to stop time?).

Categories