How to ensure proper functionality when adding widgets to Kivy - python

I am attempting to create a Kivy/Python application which gets the user to push a button on one screen, and in turn adds several widgets to the next screen. However, when I run the script, the button appears and can be clicked on the first page, and the widgets are visible on the second page, but they can not be interacted with.
Here is an example of my code, any help is appreciated:
py:
import kivy
kivy.require('1.11.1')
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.properties import ListProperty, StringProperty, ObjectProperty
from kivy.core.window import Window
from kivy.uix.label import Label
from kivy.uix.scrollview import ScrollView
from kivy.uix.button import Button
from kivy.uix.spinner import Spinner
sm = None
class RcGUITestApp(App):
def build(self):
global sm
Window.clearcolor = (0, 0, 0.35, 1)
sm = ScreenManager()
sm.add_widget(NewJobPage(name="new_job_page"))
sm.add_widget(IPMConfirmPage(name = "ipm_confirm_page"))
return sm
class NewJobPage(Screen):
def retrieve(self):
global rc
global sm
sm.get_screen("ipm_confirm_page").displayIPM()
class IPMConfirmPage(Screen):
grid = ObjectProperty(None)
def displayIPM(self):
for ipm in range(50):
self.grid.add_widget(Label(text="A"))
self.grid.add_widget(Spinner(text="B"))
self.grid.add_widget(Spinner(text="C"))
def main():
rc_gui = RcGUITestApp()
rc_gui.run()
if __name__ == "__main__":
main()
kv:
<NewJobPage>:
BoxLayout:
orientation: "horizontal"
size_hint: (1, .35)
Button:
text: "RETRIEVE"
pos_hint: {"center_x": .5, "center_y": .5}
size_hint: (.33, .33)
color: (1,1,1,1)
on_press:
root.retrieve()
root.manager.transition.direction = "left"
root.manager.transition.duration = .8
root.manager.current = "ipm_confirm_page"
<IPMConfirmPage>:
grid: Grid
GridLayout:
cols: 1
size_hint_x: 1
padding: 10
spacing: 10
BoxLayout:
size_hint: (1, 0.1)
Label:
text: "IPM"
Label:
text: "CD#"
Label:
text: "CONDUCTOR"
ScrollView:
GridLayout:
id: Grid
cols: 3
height: self.minimum_height
size_hint: 1, None
spacing: 50
padding: 30
First Page output
Second Page output

You need to provide the height of each widget added to grid. There are two solutions to the problem.
Method 1 - kv file
This method involves changing only the kv file by adding row_force_default: True and row_default_height: 40.
Snippets - kv file
ScrollView:
GridLayout:
row_force_default: True
row_default_height: 40
...
Method 2 - py file
This method involves changing only the py file by adding size_hint_y=None and height=40.
Snippets
def displayIPM(self):
for ipm in range(50):
self.grid.add_widget(Label(text="A", size_hint_y=None, height=40))
self.grid.add_widget(Spinner(text="B", size_hint_y=None, height=40))
self.grid.add_widget(Spinner(text="C", size_hint_y=None, height=40))

Related

Kivy: How to check the location of a drag behavior item

Not much code yet, but I am trying to make the buttons stay if they are on the right side, but if they are on the left, to disappear. I am not sure if this is possible, but if you are able to help, I would love it!main.py:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.uix.behaviors import DragBehavior
from kivy.uix.button import Button
Builder.load_file('main.kv')
class DragButton(DragBehavior, Button):
pass
class MainScreen(Widget):
pass
class WorkingApp(App):
def build(self):
return MainScreen()
if __name__ == '__main__':
WorkingApp().run()
​
main.kv
<DragButton>
drag_rectangle: self.x, self.y, self.width, self.height
drag_timeout: 10000
drag_distance: 0
text: 'Hi'
size_hint: (1, .5)
<MainScreen>
BoxLayout:
size: root.width, root.height
orientation: 'horizontal'
BoxLayout:
orientation: 'vertical'
Label:
text: 'Widgets'
size_hint: (1, .25)
DragButton:
DragButton:
DragButton:
Splitter:
sizable_from: 'left'
Label:
text: 'Check If they are here'
One way to do this is to bind a method to the center_x property of the DragButtons. That method can then check the position of the DragButton, and remove it, if it is to the right of the Splitter:
from kivy.app import App
from kivy.properties import ObjectProperty
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.uix.behaviors import DragBehavior
from kivy.uix.button import Button
Builder.load_file('main.kv')
class DragButton(DragBehavior, Button):
pass
class MainScreen(Widget):
splitter = ObjectProperty(None)
boxlayout = ObjectProperty(None)
def dragged(self, dragButton):
if dragButton.center_x > self.splitter.x:
self.boxlayout.remove_widget(dragButton)
class WorkingApp(App):
def build(self):
return MainScreen()
if __name__ == '__main__':
WorkingApp().run()
with some small modifications to the 'kv':
<DragButton>
drag_rectangle: self.x, self.y, self.width, self.height
drag_timeout: 10000
drag_distance: 0
text: 'Hi'
size_hint: (1, .5)
<MainScreen>
splitter: split
boxlayout: box
BoxLayout:
size: root.width, root.height
orientation: 'horizontal'
BoxLayout:
id: box
orientation: 'vertical'
Label:
text: 'Widgets'
size_hint: (1, .25)
DragButton:
on_center_x: root.dragged(self)
DragButton:
on_center_x: root.dragged(self)
DragButton:
on_center_x: root.dragged(self)
Splitter:
id: split
sizable_from: 'left'
Label:
text: 'Check If they are here'
Key modifications are the addition of ids in the kv for the Splitter and the BoxLayout and methods bound to the center_x property of each DragButton. In the python, ObjectProperties for those new ids and the dragged() method are added to the MainScreen class. The dragged() method just checks if the center of the DragButton is roght of the Splitter and removes that DragButton if it is.

How can I add an amount of widgets in the kv file based on user input?

I'm trying to make a program that puts x amount of TextInputs based on the user's input. I can do that in the py file, but how do I do this in the kv file? I also need to be able to reference these TextInputs later on.
This is what I have so far:
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivy.uix.textinput import TextInput
class GetCount(Screen):
count_input = ObjectProperty(None)
def next(self):
# Setup next screen
text_inputs = [TextInput(multiline=False) for i in range(int(self.count_input.text))]
for text_input in text_inputs:
self.manager.ids.get_input.ids.grid.add_widget(text_input)
# Switch to next screen
self.manager.current = "get_input"
class GetInput(Screen):
pass
kv_file = Builder.load_string("""
ScreenManager:
GetCount:
name: "get_count"
id: get_count
GetInput:
name: "get_input"
id: get_input
<GetCount>:
count_input: count_input
FloatLayout:
Label:
text: "count"
size_hint: 1, 0.05
pos_hint: {"top":0.9}
TextInput:
id: count_input
size_hint: 0.8, 0.05
pos_hint: {"top":0.7, "x":0.1}
multiline: False
Button:
text: "Next"
on_release: root.next()
size_hint: 0.8, 0.05
pos_hint: {"top":0.5, "x":0.1}
<GetInput>:
FloatLayout:
GridLayout:
id: grid
cols: 1
""")
class MainApp(App):
def build(self):
return kv_file
if __name__ == "__main__":
app = MainApp()
app.run()
The problem is, the TextInputs are being added and created by the py code, how can I create the widget in kv and then add it in py?

KIVY: Why wont the accordion properly load it with the title text visable?

For some reason when I run the code my accordion titles are black and the title text is not visible, when comparing it to example code where the accordion are a dark grey with white text. The rest of the accordion works fine when I click on it, it expands and closes. The only problem is the accordion titles not properly working.
*.py
#imported from kivy framework
from kivy.app import App
from kivymd.app import MDApp
from kivy.app import App
from kivy.uix.label import Label
from datetime import datetime
from datetime import timedelta
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.image import Image
import dictionaryData
from kivy.lang import Builder
from kivy.base import runTouchApp
import os
from kivy.uix.button import Button
from kivy.uix.accordion import Accordion, AccordionItem
class Main_Screen(Screen):
pass
class Dictionary_Screen(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
layout = BoxLayout(orientation='vertical') # instantiate BoxLayout
self.add_widget(layout) # add BoxLayout to screen
btn2 = Button(
text='change screen',
size_hint=(.5, .05),
)
btn2.bind(on_press=self.changer)
layout.add_widget(btn2) # add Button to BoxLayout
title = ["Title 1", "Title 2", "Title 3", "Title 4", "Title 5"]
accordion = Accordion(orientation='vertical') # instantiate Accordion
layout.add_widget(accordion) # add Accordion to BoxLayout
for x in range(2):
item = AccordionItem(title="Cheese",background_normal= "LogoFF.png")
item.add_widget(Label(text='Very big content\n' * 2))
accordion.add_widget(item) # add AccordionItem to Accordion
def changer(self,*args):
print("ww")
#class for all screens
class ScreenManagement(ScreenManager):
pass
class MainApp(MDApp):
def build(self):
# declaring time from python, and making it refresh every second
self.now = datetime.now()
Clock.schedule_interval(self.update_clock, 1)
def update_clock(self, *args):
self.now = self.now + timedelta(seconds=1)
self.root.get_screen("Main_Screen").ids["CurrentTime"].text = self.now.strftime("%H:%M:%S")
MainApp().run()
*.kv
#:kivy 1.0
#:import hex kivy.utils.get_color_from_hex
#styles that will apply to all intences for each tag
<MDRaisedButton>:
font_size:18
<Label>:
color: 0,0,0,1
#declaring screen managers and printing them in this order
ScreenManagement:
Main_Screen:
name: "Main_Screen"
Dictionary_Screen:
name: "Dictionary_Screen"
<Main_Screen>:
FloatLayout:
spacing: 10
canvas.before:
Color:
rgba: hex('#eff3fa')
Rectangle:
size: self.size
pos: self.pos
#Navbar
MDToolbar:
id: fb
pos_hint: {'center_x': 0.5, 'top':1.0}
size_hint_y:None
height: 50
title: "Virtual Assistant"
md_bg_color: hex('#132843')
Label:
id: CurrentTime
font_size:18
size_hint_x: .1
color: (1,1,1,1)
BoxLayout:
orientation: 'vertical'
spacing: 10
padding: 50
canvas.before:
Color:
rgba: hex('#000')
Rectangle:
size: self.size
pos: self.pos
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
size_hint: 0.5, 0.5
Button:
text: "Dictionary"
on_release:
app.root.current = "Dictionary_Screen"
<Dictionary_Screen>:
Label:
id: box
color: (1,0,1,1)
Is there a way I can fix this?
The AccordionItemTitle is a Label, and your <Label>: rule sets all text in all Labels to black. You can fix the problem by removing the lines:
<Label>:
color: 0,0,0,1
from your kv file. It is generally a bad idea to make a kv rule for a commonly used Widget. A better approach would be to create a custom class like:
<BlackLabel#Label>:
color: 0,0,0,1
and use BlackLabel where you want such a Label.

Kivy adding and removing widgets from GridLayout gives weak reference issue

I am attempting to dynamically add and remove widgets from a GridLayout with Python-Kivy, and I am running into an issue with weak references. When the Screen containing the GridLayout is initialized, I am placing a Label inside of the GridLayout that contains text notifying the user that the container has no items (technically, it has the Label in there so it is not necessarily empty). Then I have a Button that allows the user to add individual GridLayout widgets to the GridLayout that contain a Label, a TextInput and a CheckBox, as well as a Button that allows the user to remove those individual GridLayout widgets that they created. If all of these widgets (dynamically added by the user) are removed, then the original Label is added back to the GridLayout.
When I attempt to construct this logic in Python to pair with Kivy, I run into an issue where the original Label never seems to be fully removed from the stack.
I was under the impression that self.ids.widget_list.remove_widget(self.ids.empty) would remove the Label widget with id empty, however this is not the case. It is clear that the widget still exists, when I call print(self.ids):
{'widget_list': <WeakProxy to <kivy.uix.gridlayout.GridLayout object at 0x0451F030>>, 'empty': <WeakProxy to <kivy.uix.gridlayout.GridLayout object at 0x0451F9D0>>}
Any help is much appreciated.
EDIT
When checking for i in self.layouts: print(i.children) every time the remove() method is called, shows that the references to the added widgets are never being completely removed. This may be where my issues resides, but unsure how to resolve it.
Python
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.scrollview import ScrollView
from kivy.uix.label import Label
from kivy.uix.checkbox import CheckBox
from kivy.uix.popup import Popup
from kivy.uix.screenmanager import ScreenManager, Screen
#Load kv file
Builder.load_file('test.kv')
#First Screen
class Screen1(Screen):
layouts = []
def remove(self):
for i in self.layouts:
if i.children[0].active:
self.ids.widget_list.remove_widget(i)
if len(self.layouts)==0:
layout = GridLayout(rows=1, id=empty)
layout.add_widget(Label(text='Nothing Here'))
self.ids.widget_list.add_widget(layout)
else:
self.update_hints()
def add(self):
if len(self.ids.widget_list.children)<5:
print(self.ids)
self.ids.widget_list.remove_widget(self.ids.empty)
print(self.ids)
layout = GridLayout(cols=3)
layout.add_widget(Label(text='Test ' + str(len(self.ids.widget_list.children)+1)))
layout.add_widget(TextInput())
layout.add_widget(CheckBox())
self.ids.widget_list.add_widget(layout)
self.layouts.append(layout)
self.update_hints()
else:
layout = GridLayout(cols=1)
layout.add_widget(Label(text='Only five allowed at once.\nRemove at least one to add another.'))
button = Button(text='Acknowledge'); layout.add_widget(button)
popup = Popup(content=layout, title='Limit Reached', size_hint=(.5,.5), auto_dismiss=False)
button.bind(on_release=popup.dismiss)
popup.open()
def update_hints(self):
for i in self.layouts:
i.children[1].hint_text = 'Ex. ' + str(round(100/len(self.ids.widget_list.children),2)) + '%'
#Initialize Screens and Start App
class MyScreenManager(ScreenManager):
pass
#Main application
class SampleApp(App):
def build(self):
self.sm = MyScreenManager()
return self.sm
if __name__ == '__main__':
SampleApp().run()
kv
<MyScreenManager>:
Screen1:
name: 'screen1'
<Screen1>:
BoxLayout:
orientation: 'vertical'
GridLayout:
cols: 3
padding: 20
spacing: 20
size_hint: 1, .1
Label:
text: 'List of Widgets'
underline: True
Label:
text: 'Percentage'
underline: True
Label:
text: 'Remove (Y/N)'
underline: True
ScrollView:
size_hint: 1, .5
do_scroll_x: False
padding: 20
spacing: 20
GridLayout:
id: widget_list
cols: 1
spacing: 5
GridLayout:
id: empty
rows: 1
Label:
text: 'Nothing Here'
GridLayout:
cols: 3
padding: 20
spacing: 20
size_hint: 1, .2
Button:
text: 'Add Widget'
on_release: root.add()
Label:
text: ''
Button:
text: 'Remove Widget'
on_release: root.remove()
Ok, I seem to have found a simpler means of implementation that avoids specifying any id for the initial Label (the same one that shows if self.layouts==[]):
Python
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.scrollview import ScrollView
from kivy.uix.label import Label
from kivy.uix.checkbox import CheckBox
from kivy.uix.popup import Popup
from kivy.uix.screenmanager import ScreenManager, Screen
#Load kv file
Builder.load_file('test.kv')
#First Screen
class Screen1(Screen):
count = 0
layouts = []
def remove(self):
for i in self.layouts:
if i.children[0].active:
self.ids.widget_list.remove_widget(i)
self.layouts = [i for i in self.layouts if not i.children[0].active]
if self.layouts!=[]:
self.update_hints()
else:
layout = GridLayout(rows=1)
layout.add_widget(Label(text='Nothing Here'))
self.ids.widget_list.add_widget(layout)
def add(self):
if self.layouts==[]:
self.ids.widget_list.clear_widgets()
if len(self.ids.widget_list.children)<5:
self.count+=1
layout = GridLayout(cols=3)
layout.add_widget(Label(text='Test ' + str(self.count)))
layout.add_widget(TextInput())
layout.add_widget(CheckBox())
self.ids.widget_list.add_widget(layout)
self.layouts.append(layout)
self.update_hints()
else:
layout = GridLayout(cols=1)
layout.add_widget(Label(text='Only five allowed at once.\nRemove at least one to add another.'))
button = Button(text='Acknowledge'); layout.add_widget(button)
popup = Popup(content=layout, title='Limit Reached', size_hint=(.5,.5), auto_dismiss=False)
button.bind(on_release=popup.dismiss)
popup.open()
def update_hints(self):
for i in self.layouts:
i.children[1].hint_text = 'Ex. ' + str(round(100/len(self.ids.widget_list.children),2)) + '%'
#Initialize Screens and Start App
class MyScreenManager(ScreenManager):
pass
#Main application
class SampleApp(App):
def build(self):
self.sm = MyScreenManager()
return self.sm
if __name__ == '__main__':
SampleApp().run()
kv
<MyScreenManager>:
Screen1:
name: 'screen1'
<Screen1>:
BoxLayout:
orientation: 'vertical'
GridLayout:
cols: 3
padding: 20
spacing: 20
size_hint: 1, .1
Label:
text: 'List of Widgets'
underline: True
Label:
text: 'Percentage'
underline: True
Label:
text: 'Remove (Y/N)'
underline: True
ScrollView:
size_hint: 1, .5
do_scroll_x: False
padding: 20
spacing: 20
GridLayout:
id: widget_list
cols: 1
spacing: 5
GridLayout:
rows: 1
Label:
text: 'Nothing Here'
GridLayout:
cols: 3
padding: 20
spacing: 20
size_hint: 1, .2
Button:
text: 'Add Widget'
on_release: root.add()
Label:
text: ''
Button:
text: 'Remove Widget'
on_release: root.remove()

Kivy Update Label Text with Variable

Question: How do I update Label text in this scenario?
Python code:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.textinput import TextInput
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.uix.scrollview import ScrollView
from kivy.properties import StringProperty, ObjectProperty, NumericProperty
class SearchForPart(Widget):
def searchforpart(self):
Barty = 'text update';
demogar = DemoGarage()
demogar.ids.scrollref.UpdateParam(Barty)
class ScrollableLabel1(ScrollView):
text_variable_1 = StringProperty()
def __init__(self, **kwargs):
super(ScrollableLabel1, self).__init__(**kwargs)
self.text_variable_1 = 'initial'
def UpdateParam(self, param):
self.param = param
self.text_variable_1 = param
print(param)
class AnotherScreen(Screen):
pass
class MainScreen(Screen):
pass
class DemoGarage(Screen):
pass
class ScreenManagement(ScreenManager):
pass
presentation = Builder.load_file("garagemainexample.kv")
class MainApp(App):
def build(self):
return presentation
if __name__ == "__main__":
MainApp().run()
Kv code:
#: import FadeTransition kivy.uix.screenmanager.FadeTransition
ScreenManagement:
transition: FadeTransition()
MainScreen:
DemoGarage:
<BigButton#Button>:
font_size: 40
size_hint: 0.5, 0.15
color: 0,1,0,1
<MainScreen>:
name: "main"
FloatLayout:
BigButton:
on_release: app.root.current = "demogarage"
text: "Demo Garage"
pos_hint: {"x":0.25, "top": 0.4}
<DemoGarage>:
name: "demogarage"
ScrollableLabel1:
id:scrollref
SearchForPart:
id:searchpart
<ScrollableLabel1>:
Label:
id: mylabel
text: root.text_variable_1
font_size: 20
text_size: self.width, None
size_hint_y: None
height: self.texture_size[1]
padding_y: 10
padding_x: 140
<SearchForPart>:
size_hint: 1,1
FloatLayout:
Label:
text: "Search for Part:"
font_size: 30
pos_hint: {"x": 0.95, "y": 0.85}
color: 0,1,0,1
size_hint: 0.3, 0.2
TextInput:
id: partname
multiline: False
font_size: 30
pos_hint: {"x": 0.85, "y": 0.15}
color: 0,1,0,1
on_text_validate: root.searchforpart()
size_hint: 1, 0.5
After initializing program, the Label text is successfully initialized and outputs 'initial text'.
Once the 'searchforpart' method runs (as a response to hitting enter in search box), even though 'print(param)' prints 'text update', the label text isn't updated.
Question: How do I update Label text in this scenario?
Am I misunderstanding a core concept of how changes to the Kivy event loop are triggered?
Use a Kivy property:
from kivy.properties import StringProperty
class (YourClass):
text_variable_1 = StringProperty()
When you reference this property in kv, and its value changes, the gui will automatically update. If you don't use a property this doesn't happen because the kv code has no way to know the value changed.
Also, you don't have to follow pep8, but the property name should start with a lower case letter - it probably doesn't matter for your example, but can matter in kv if you try to set the property there.

Categories

Resources