remove_widget in kivy doesn't remove widget - kivy

I'm having trouble removing a widget using remove_widget in kivy
I either get TypeError: 'Label' object has no attribute 'getitem' error
or it just doesn't remove the label that was created.
I think I'm not properly referring to the widget made but not sure how to...
Builder.load_string("""
<MenuScreen>:
FloatLayout:
#cols: 2
#rows: 2
size: 800,480
Button:
id: resetmash
text: "Reset"
font_size: 30
size_hint: None, None
background_color: 1,0.88,0.882,1
size: 100,100
pos: 1450,800
on_press: root.resetmash()
Button:
id: btn_0
text: "+"
size_hint: None, None
size: 100,100
pos: 550,700
on_press: root.listmashsteps()
""")
class MenuScreen(Screen):
def resetmash(self):
self.remove_widget(Label())
def listmashsteps(self, *largs):
self.add_widget(Label(text="Step"))
def __init__(self, **kwargs):
super(MenuScreen, self).__init__(**kwargs)
sm = ScreenManager()
menu_screen = MenuScreen(name='menu')
sm.add_widget(menu_screen)
class TestApp(App):
def build(self):
return sm
if __name__ == '__main__':
TestApp().run()

self.remove_widget(Label())
This doesn't make sense - you just instantiated that Label, so it isn't already added to the widget, so you can't remove it.
Instead store a reference to the Label when you add it, something like:
self.label = Label(text='Step')
self.add_widget(self.label)
Then later:
self.remove_widget(self.label)

Related

How to disable a widget when a button from a different class is pressed?

Im making a Button which disables a TopAppBar menu form my user interface, the problem is that the Button is on one class an the TopAppBar in another, because one belongs to the screen and the other is shared between multiple screens.
code:
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.core.window import Window
from kivymd.uix.screen import MDScreen
Window.size = (300, 500)
navigation_helper = """
<UserScreen>
name: 'user_screen'
MDBoxLayout:
orientation: "vertical"
pos_hint: {"top": 1}
adaptive_height: True
padding: (0, "80dp" , 0, 0)
FloatLayout:
MDRaisedButton:
id: save_btn_user
text: "Guardar"
md_bg_color: "white"
text_color: "black"
font_size: 16.2
pos_hint: {"left":1, "bottom": 1}
size_hint: (.2,.2)
disabled: True
on_press: root.save_btn_press()
MDRaisedButton:
id: edit_btn_user
text: "Editar"
md_bg_color: "white"
text_color: "black"
font_size: 16.2
pos_hint: {"right":1, "bottom": 1}
size_hint: (.2,.2)
disabled: False
on_press:
root.disable_top_bar()
<MainScreen>:
name: "main_screen"
BoxLayout:
orientation: 'vertical'
MDTopAppBar:
id: title_bar
title: 'Dietas'
md_bg_color: 0.08,0.07,0.45
specific_text_color: 1,1,1
left_action_items: [["menu", lambda x: nav_drawer.set_state('toggle')]]
Widget:
MDNavigationLayout:
ScreenManager:
id: scr
UserScreen:
MDNavigationDrawer:
id: nav_drawer
BoxLayout:
orientation: 'vertical'
spacing: '8dp'
ScrollView:
MDList:
OneLineIconListItem:
text: 'Usuario'
on_press:
scr.current= 'user_screen'
title_bar.title = "Usuario"
nav_drawer.set_state('close')
IconLeftWidgetWithoutTouch:
icon: 'account'
on_press:
scr.current= 'user_screen'
title_bar.title = "Usuario"
nav_drawer.set_state('close')
MainScreen:
"""
class UserScreen(MDScreen):
def disable_top_bar(self):
self.a=MainScreen()
self.a.disable_top_barr()
class MainScreen(MDScreen):
def disable_top_barr(self):
self.ids.title_bar.disabled = True
print("testmsg")
class DemoApp(MDApp):
def build(self):
self.theme_cls.theme_style = ("Dark")
screen = Builder.load_string(navigation_helper)
return screen
DemoApp().run()
As you may have seen, I tried creating two method, one in the TopAppBar class disabling it and printing a test message(that its shown) and the other in the Button class calling that function.
With the following approach
def disable_top_bar(self):
self.a=MainScreen()
self.a.disable_top_barr()
you will see no change in your GUI because each time you call that method, it will create a new instance of MainScreen (which is not related to your GUI) and call the asked method on it.
In order to access that specific instance you can use the method get_running_app of MDApp as follows,
def disable_top_bar(self):
# First access the running app instance.
app = MDApp.get_running_app()
# Access the target screen that you set as the root widget.
main_screen = app.root
# Call the method.
main_screen.disable_top_barr()
As a note avoid using predefined names like a, b, g etc. when working with KivyMD widgets.

Kivy >> On Press method triggered twice

For some reason the On Press event for the instance of Word_button is being triggered twice. The code below demonstrates this.
To repeat the problem.
run the code below
click on the "CREATE LIST OF WORD" button. This create a list of buttons. Each button has Boolean property if the word is correct or not.
then click on the word buttons. When clicking the button, the print statements print the correct boolean variable and the text of the button.
Problem:
The print commands are run twice.
from kivy.lang.builder import Builder
from kivy.uix.screenmanager import Screen
from kivy.properties import (NumericProperty, BooleanProperty)
from kivymd.app import MDApp
from kivymd.uix.button import MDRaisedButton
from kivymd.uix.boxlayout import MDBoxLayout
kv = '''
<Word_button#MDRaisedButton>:
pos_hint: {'center_x': .5}
size_hint: 1, 1
font_size: "16sp"
on_press: self.check_word()
<Words_Box#MDBoxLayout>:
pos_hint: {'center_x': .5}
size_hint: 1, 1
Screen:
id: spelling_screen
name: "spelling_screen"
MDBoxLayout:
orientation: 'vertical'
padding: dp(15)
spacing: dp(10)
MDLabel:
text: 'Words'
Words_Box:
id: words_box
orientation: 'vertical'
padding: dp(15)
spacing: dp(10)
MDRaisedButton:
text: 'CREATE LIST OF WORDS'
on_release: root.ids.words_box.add_word_buttons()
'''
class Word_button(MDRaisedButton):
correct = BooleanProperty()
def check_word(self):
print('Answer is ', self.correct)
print('Button Text is ', self.text)
class Words_Box(MDBoxLayout):
def add_word_buttons(self):
app = MDApp.get_running_app()
words = ['$WORD 1', 'WORD 2', 'WORD 3']
for word in words:
correct = False
if '$' in word:
correct = True
word = word[1:]
btn = Word_button(text=word, correct=correct)
self.add_widget(btn)
class RootScreen(Screen):
def __init__(self, **kwargs):
super(RootScreen, self).__init__(**kwargs)
class Main(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.screen = Builder.load_string(kv)
def build(self):
return self.screen
Main().run()
This was resolved by not naming the kv file main.kv.

Kivy how to create dropdownlist within a GridLayout

I have 2 files one of the py and the other kv, well as the title says, I do not know how to add a dropdown.
---> main.py
from kivy.app import App
from kivy.uix.dropdown import DropDown
from kivy.uix.gridlayout import GridLayout
class MyGridLayout(GridLayout):
pass
class LayoutsApp(App):
def build(self):
return MyGridLayout()
if __name__ == '__main__':
LayoutsApp().run()
---> layouts.kv
<MyGridLayout>:
rows: 2
id: main_win
Dropdown:
id: dropdown
Button:
id: btn1
text: 'option 1'
Button:
id: btn2
text: 'option 2'
BoxLayout:
BoxLayout:
when compiling, it generates error for that part. How is the correct way to make the call for dropdown-list?
Question 3
call the Button id: btn2, how would I do it? - I mean:class
MyGridLayout(GridLayout):
Solution 3
Python script
Add import statements: from kivy.uix.dropdown import DropDown; from kivy.properties import ObjectProperty
Implement class CustomDropDown(DropDown):
In class MyGridLayout, declare an ObjectProperty at class level (e.g. dropdown = ObjectProperty(None); implement a method dropdown_open(self):; instantiate the class CustomDropDown and assigned it to self.dropdown, etc. Please refer to snippets for detail.
kv file
Remove self.dismiss()
Replace dynamic class, <CustomDropdown#DropDown>: with class rule, <CustomDropDown>:
Replace rows: 2 with rows: 3
Replace Factory.CustomDropdown().open(self) with root.dropdown_open(self)
Example 3
main.py - Snippets
from kivy.uix.dropdown import DropDown
from kivy.properties import ObjectProperty
class CustomDropDown(DropDown):
pass
class MyGridLayout(GridLayout):
dropdown = ObjectProperty(None)
def dropdown_open(self, instance):
self.dropdown = CustomDropDown()
self.dropdown.open(instance)
print(self.dropdown.ids.btn2.text)
layouts.kv - Snippets
<CustomDropDown>:
id: dropdown
on_select:
app.root.ids.btn.text = '{}'.format(args[1])
...
<MyGridLayout>:
rows: 3
id: main_win
Button:
id: btn
text: 'Press'
size_hint_y: None
height: '48dp'
on_release:
root.dropdown_open(self)
Output 3
Question 2
when I added these commands, it does not work: BoxLayout: orientation:
'vertical' size_hint: (.9,.9) BoxLayout: orientation: 'vertical'
size_hint_y: 0.5
Note
You might need to increase the rows from 2 to 3.
Example #2
Added the config for the two BoxLayout
For illustration, added colour to the canvas for both BoxLayout
layouts.kv
#:kivy 1.10.1
#:import Factory kivy.factory.Factory
<CustomDropdown#DropDown>:
id: dropdown
on_select:
app.root.ids.btn.text = '{}'.format(args[1])
self.dismiss()
Button:
id: btn1
text: 'option 1'
size_hint_y: None
height: '48dp'
on_release:
dropdown.select(btn1.text)
Button:
id: btn2
text: 'option 2'
size_hint_y: None
height: '48dp'
on_release:
dropdown.select(btn2.text)
<MyGridLayout>:
rows: 2
id: main_win
Button:
id: btn
text: 'Press'
on_release: Factory.CustomDropdown().open(self)
size_hint_y: None
height: '48dp'
BoxLayout:
orientation: 'vertical'
size_hint: (.9,.9)
canvas.before:
Color:
rgba: 1, 0, 0, 1 # red
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
orientation: 'vertical'
size_hint_y: 0.5
canvas.before:
Color:
rgba: 0, 0, 1, 1 # blue
Rectangle:
pos: self.pos
size: self.size
Output #2
Solution #1
DropDown widget is like Popup widget i.e. they are special widget.
Popup / DropDown is a special widget. Don’t try to add it as a child to any other
widget. If you do, Popup / DropDown will be handled like an ordinary widget and
won’t be created hidden in the background.
Therefore, create a dynamic class with inheritance of DropDown widget, and use Factory to instantiate the class.
Example
main.py
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
class MyGridLayout(GridLayout):
pass
class LayoutsApp(App):
def build(self):
return MyGridLayout()
if __name__ == '__main__':
LayoutsApp().run()
layouts.kv
#:kivy 1.10.1
#:import Factory kivy.factory.Factory
<CustomDropdown#DropDown>:
id: dropdown
on_select:
app.root.ids.btn.text = '{}'.format(args[1])
self.dismiss()
Button:
id: btn1
text: 'option 1'
size_hint_y: None
height: '48dp'
on_release:
dropdown.select(btn1.text)
Button:
id: btn2
text: 'option 2'
size_hint_y: None
height: '48dp'
on_release:
dropdown.select(btn2.text)
<MyGridLayout>:
rows: 2
id: main_win
Button:
id: btn
text: 'Press'
on_release: Factory.CustomDropdown().open(self)
size_hint_y: None
height: '48dp'
BoxLayout:
BoxLayout:
Output

Kivy Layout Issue

I am trying to create a GUI using Kivy. However, I cannot resolve some formatting issues.
Here is a slimmed down version of my KV file:
BoxLayout:
MainCanvas:
size_hint: 1,1
size: (root.width,root.height*.9)
DoubleEllipseWidget:
ActionBar:
id: _action
size_hint: 1,0.1
size: (root.width,root.height*.1)
pos_hint: {'bottom':1}
ActionView:
use_separator: True
ActionPrevious:
title: 'Test App:'
with_previous: False
ActionOverflow:
disabled: True
ActionButton:
important: True
text: 'Button 1'
#on_release: some_function
ActionButton:
text: 'Button 2'
#on_release: some_function
ActionButton:
text: 'Button 3'
#on_release: some_function
<DoubleEllipseWidget>
size: [200, 200]
canvas:
Color:
rgba: 0, 0, 0, 1
Ellipse
size: [198, 198]
pos: [600-200-100, 800-200-100]
Color:
rgba: 1, 1, 1, 1
Ellipse
size: [200, 200]
pos: [600-200-100, 800-200-100]
TextInput:
on_parent:self.focus = True
text: 'center of circle'
background_color: (0,0,0,0)
foreground_color: (0,0,0,1)
What I am trying to get to is very easily explained.
Essentially, there should be a menu bar running horizontally along the screen window (10% of total height and 100% of width). I believe I have done this.
The remaining 95% height should be the main canvas - I believe I have also done this.
The final part is getting a particular widget to be placed into the center of the main canvas. This is where I am stuck and would appreciate some help.
The widget I need to center is made up of two circles (one centered on top of the other, with one being slightly smaller than the other). Then, on top of the top-most circle should be a TextInput.
After taking the advice from this community, I stripped all of the logic back until I was left with just the basics (class definitions and layout), and was still having issues.
However, I have now learnt that what I assumed was a layout issue was likely not. A Kivy bug report last commented on in Nov 2018 (TextInput focus problem #3863), suggests there are unexplained instances where textinputs can lose focus. Below is the suggested workaround which also worked for me. Thanks for those that tried to help me.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.uix.textinput import TextInput
from kivy.lang import Builder
Builder.load_string('''
<Base>:
Button:
text: 'Press for Popup'
on_press: root.edit_text()
<TextInputX>
id: texter
keyboard_mode: 'managed'
is_focusable: True
focus: True
keyboard_on_key_down:
texter.show_keyboard()
''')
class Base(BoxLayout):
def __init__(self, **kwargs):
super(Base, self).__init__(**kwargs)
def edit_text(self, *args):
self.clear_widgets()
content = TextInputX()
# content.focus = True
popup = Popup(content=content)
popup.open()
class TextInputX(TextInput):
def __init__(self, **kwargs):
super(TextInputX, self).__init__(**kwargs)
# self.focus = True
class SampleApp(App):
def build(self):
return Base()
if __name__ == '__main__':
SampleApp().run()

how to pass widget dimensions to __init__

Apologies if this question has an obvious answer but I have been unable to find a solution for some time now. A widget in my app has a 'graph' that is defined in terms of the widget's dimensions. I can dynamically update the 'graph' from kv because I have access to the widget's dimensions there. However I would like to define a default 'graph', also in terms of the widget's size, that appears at startup. I do not know how to pass the widget's dimensions to the __init__ function. Here is my boiled down example:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.properties import ListProperty
Builder.load_string('''
#:kivy 1.9.2
<MainWidget>:
BoxLayout:
size_hint_x: 20
orientation: 'vertical'
ToggleButton:
text: 'WF1'
state: 'down'
allow_no_selection: False
on_press:
root.line_points = [waveform.x, waveform.top, waveform.right, waveform.y]
root.event_handler()
group: 'lhs_buttons'
ToggleButton:
text: 'WF2'
allow_no_selection: False
on_press:
root.line_points = [waveform.x, waveform.y, waveform.right, waveform.top]
root.event_handler()
group: 'lhs_buttons'
BoxLayout:
size_hint_x: 80
Button:
id: waveform
canvas:
Line:
points: root.line_points
''')
class MainWidget(BoxLayout):
line_points = ListProperty()
def __init__(self, **kwargs):
super(MainWidget, self).__init__(**kwargs)
#self.line_points = [waveform.x, waveform.top, waveform.right, waveform.y]
def event_handler(self):
print "event"
class MyApp(App):
def build(self):
return MainWidget()
if __name__ == '__main__':
MyApp().run()
I suppose a partial solution would be to trigger the on_press event in __init__ for one of the buttons , but I have been unable to figure out how to do that. I am new to Python and to Kivy.
One approach will be to bind line_points like this:
<MainWidget>:
line_points: self.calc_line_points(waveform.x, waveform.y, waveform.right, waveform.top)
...
And calc_line_points will be defined such as :
def calc_line_points(self, x, y, right, top):
return [ x, top, right, y] #put more logic here ...

Resources