I use a ListAdapter to dispaly data in a ListView.
How do I select an item in the ListView from code?
You can get your list items from ListAdapter using get_view() method. If list item is a ListItemButton then you can simulate pressing using trigger_action() method of ButtonBehavior mixin (ButtonBehavior is parent of Button and Button is parent of ListItemButton). This will also trigger on_selection_change event, so you might need a variable to distinguish this from normal selection. An example:
from kivy.uix.listview import ListView, ListItemButton
from kivy.uix.boxlayout import BoxLayout
from kivy.adapters.dictadapter import ListAdapter
from kivy.uix.button import Button
from random import randint
class MainView(BoxLayout):
def __init__(self, **kwargs):
kwargs['cols'] = 2
super(MainView, self).__init__(**kwargs)
self.orientation = 'vertical'
self.list_adapter = ListAdapter(data=["Item #{0}".format(i) for i in range(10)], cls=ListItemButton, sorted_keys=[])
self.list_adapter.bind(on_selection_change=self.selection_change)
list_view = ListView(adapter=self.list_adapter)
self.add_widget(list_view)
self.add_widget(Button(text="select random item", on_press=self.callback))
def callback(self, instance):
index = randint(0, 9)
self.change_from_code = True
if not self.list_adapter.get_view(index).is_selected:
self.list_adapter.get_view(index).trigger_action(duration=0)
self.change_from_code = False
def selection_change(self, adapter, *args):
if self.change_from_code:
print "selection change from code"
else:
print "selection changed by click"
if __name__ == '__main__':
from kivy.base import runTouchApp
runTouchApp(MainView(width=800))
The ListAdapter function handle_selection already handles this. It's not in the documentation, but it's in the code. All you need to know is the list item that you're looking for.
item = list_adapter.get_data_item(0)
list_adapter.handle_selection(item)
If you don't want to fire a on_selection_change event add True. This tells handle_selection not to fire the dispatch.
item = list_adapter.get_data_item(0)
list_adapter.handle_selection(item, True)
Related
In a Kivy/Python program, I have a class which contains a scrollview. I instantiate this class multiple times. The scrollview is bound to the on_scroll_stop event. My desired outcome is that the on_scroll_stop event will only fire within the instantiated class to which it belongs, but it seems that the event fires across all of the instantiated classes. Am I doing something wrong or is this expected behavior? Working sample below. In this example the "Left" section shows the issue most often, however the error is seen in the right section once you scroll up at the top or scroll down once reaching the bottom. To recreate scroll only one side, preferably the "Left" side. In my actual code which is far more complex the issue is much more prevalent.
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
class DisplaySection(Widget):
def CreateSection(self):
self.display_panel_main_layout = BoxLayout(orientation='vertical')
self.elements_layout = FloatLayout(size_hint_y=None)
self.sv = ScrollView()
self.sv.bind(on_scroll_stop=self.on_scrolling_stop)
self.sv.add_widget(self.elements_layout)
self.display_panel_main_layout.add_widget(self.sv)
return self.display_panel_main_layout
def LoadElements(self,section_name):
self.section_name = section_name
number_of_elemements = 100
element_hieght = 40
layout_height = number_of_elemements * element_hieght
self.elements_layout.height = layout_height
xcord = 0
for x in range(number_of_elemements):
ycord = self.elements_layout.height - element_hieght*x
name = Label(text='Name' + str(x),size_hint_y=None, height=40,pos=(xcord,ycord))
self.elements_layout.add_widget(name)
def on_scrolling_stop(self, sv, value):
#print(sv,value)
print('I am',self.section_name)
class testscrollapp(App):
def build(self):
main_layout = BoxLayout()
section_names = ['Left','Right']
for x, section_name in enumerate(section_names):
section_layout = BoxLayout(orientation='vertical')
btn = Button(text=section_name,size_hint=(1,None))
section_layout.add_widget(btn)
# instantiate the class containing the scroll view
scroll_layout = DisplaySection()
scr = scroll_layout.CreateSection()
section_layout.add_widget(scr)
scroll_layout.LoadElements(section_name)
main_layout.add_widget(section_layout)
return main_layout
testscrollapp().run()
The on_scroll_stop event is dispatched just like touch events, so any ScrollView instance that subscribes to on_scroll_stop will get all the on_scroll_stop events. And just like a touch event, the subscribing ScrollView instance must determine which of the events it is interested in, and ignore the rest. Since the value argument to your on_scrolling_stop is the MouseMotionEvent, you can do a collide_point() test to determine if the scroll event is within the ScrollView:
def on_scrolling_stop(self, sv, value):
if sv.collide_point(*value.pos):
print('\tI am',self.section_name)
I run into an error while trying to access two buttons from a group of buttons created in for loop and binding the button to on_press. Please what is the right way to do this (without .kv). How can I access individual button and bind them to different on_press event
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
class TestApp(App):
def build(self):
layout=GridLayout(cols=1)
for i in range(6):
btn = Button(text= str(i))
layout.add_widget(btn)
# error occurred here
btn[0].bind(on_press=first)
btn[1].bind(on_press=second)
def first(self):
pass
def second(self):
pass
return layout
if __name__ == '__main__':
TestApp().run()
Code involving btn[0] implies a btn list, but there is no such list in your code. Here is a modified version of your code that does what you want:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
class TestApp(App):
def build(self):
layout=GridLayout(cols=1)
for i in range(6):
btn = Button(text= str(i))
layout.add_widget(btn)
if i == 0:
btn.bind(on_press=self.first)
elif i == 1:
btn.bind(on_press=self.second)
return layout
def first(self, button_instance):
print('first')
def second(self, button_instance):
print('second')
if __name__ == '__main__':
TestApp().run()
I still haven't fully solved my problem, however, the following code returned the object created on the button and the value assigned to it. So I believe that this solution can help you.
from functools import partial
for x in test:
btn = Button(text=x["CustomerName"], background_color=[0.9, 0.9, 0.9, 0.9], size_hint_y=None, height=40)
btn.bind(on_press=partial(self.mostra,x["CustomerName"]))
self.ids.grd1.add_widget(btn)
def mostra(self,*args):
print(args)
Looking at the code I posted I used bind after creating the button (btn.bind) and in it I passed the function that shows on the screen the value saved in the x of my loop. In the function (mostra) the use of self and * args is mandatory.
At the beginning there is the line (from functools import partial) that line is necessary to execute the function show inside the bind.
Im writing an app in Kivy which automaticaly adds Buttons and gives them a unique id using a for loop. This id is then used as a key in the dictionary for a link. So the dictionary works fine and after printing it, it outputs {'button0': 'somewebsite', 'button1': 'other website', 'button2': 'andanotherwebsite'} which is exactly what I want but the button callback function always prints out button2 instead of its own id. Am I assigning the ids wrong? The example below demonstrates my problem.
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivymd.utils import asynckivy
from kivy.clock import Clock
class TestButton(Button):
def callback(self):
print(self.id)
class RootWidget(BoxLayout):
def __init__(self):
super().__init__()
self.links = ["somewebsite", "other website", "andanotherwebsite"]
self.dic_btn_to_lnk = {}
self.size_hint = (None, None)
self.size = ("600dp", "50dp")
Clock.schedule_once(self.add_widgets, 0)
def add_widgets(self, *args):
async def update():
number = 0
for link in self.links:
button = TestButton()
button.text = link
button.size = ("200dp", "50dp")
button.pos_hint = {"center_x": .5}
btn_id = "button" + str(number)
button.id = btn_id
button.bind(on_release=lambda x: button.callback())
number += 1
self.dic_btn_to_lnk[btn_id] = link
self.add_widget(button)
print(self.dic_btn_to_lnk)
asynckivy.start(update())
class TestApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
TestApp().run()
The problem is that your on_release binding is calling button.callback(), and button will be the last Button added by the time the on_release is triggered. The solution is to use partial, which freezes its arguments to their values when partial is executed, so the on_release calls the correct button.callback. Like this:
button.bind(on_release=partial(button.callback))
And to simplify the above, the definition of callback is changed to:
class TestButton(Button):
def callback(self, instance):
print(self.id)
I'm something of a newbie to Python and Kivy. After making some progress, I've hit a brick wall and no amount of internet searching can find an answer.
I have a python/kivy script which starts with a GridLayout selection menu. Then I would like to "Click Next" and replace the GridLayout with a BoxLayout to display the output. The Python script is:
import sys
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.checkbox import CheckBox
from kivy.uix.gridlayout import GridLayout
from kivy.uix.pagelayout import PageLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.properties import StringProperty, ListProperty, DictProperty
from kivy.uix.widget import Widget
from brp_stats import *
from dice_roller import *
#from display import *
race = ''
statblock = ''
class Test(GridLayout):
def printcharacter(self,my_sb,my_cr,my_scm,my_scb):
printable_stats = print_stats(my_sb)
printable_rolls = print_rolls(my_cr)
printable_scm = print_scm(my_scm)
printable_scb = print_scb(my_scb)
self.clear_widgets()
self.add_widget(Label(text_size=(300, None),
text='Stats\n' + str(printable_stats)))
self.add_widget(Label(text_size=(300, None),
text='Rolls\n' + str(printable_rolls)))
self.add_widget(Label(text_size=(300, None),
text='SCM\n' + str(printable_scm)))
self.add_widget(Label(text_size=(300, None),
text='SCB\n' + str(printable_scb)))
wayout = Button(text='Way Out')
self.add_widget(wayout)
wayout.bind(on_press=self.canvas.clear)
# def bar():
# print ("BAR")
def human(self,a,b):
if b==True:
self.Status="human"
race=self.Status
statblock = human()
characteristic_rolls = rolls(statblock)
skill_category_modifiers = scm(statblock)
skill_category_bonuses = scb(statblock)
# TestApp.foo()
# Test.bar()
Test.printcharacter( \ self,statblock,characteristic_rolls,skill_category_modifiers,skill_category_bonuses)
class TestApp(App):
# def foo():
# print ("FOO")
def build(self):
self.title="BRP Character Generator"
return Test()
#### MAIN CODE ####
if __name__ == '__main__':
TestApp().run()
And the KV script is
<Test>:
cols: 2
canvas:
Color:
rgb: .2,.2,.2
Rectangle:
pos: self.pos
size: self.size
Label:
text: 'Human'
CheckBox:
group: 'race_group'
on_active: root.human(*args)
Button:
text: 'Next'
on_press: printcharacter()
What's happening is I select an option (Human in this example). It should wait until I click Next before displaying the results. However, as soon as I select Human, it immediately prints the result, in a GridLayout. Two questions spring to mind, which I'm hoping the experts here can help with:
1) Why does the Select screen not wait until I have clicked Next before displaying the results?
2) How do I swap the layout from Grid to Box when I display the second screen?
Any pointers or suggestions would be gratefully received.
Regards,
Colin
Your Checkbox calls Test.human(*args) when it is active (pressed/selected), and the last line of Test.human() calls Test.printcharacter() which explains why it is printing before you hit the 'Next' button.
Since your Test class inherits from GridLayout the widgets you add to a root Test layout will be added as if to a GridLayout. There are several ways to handle switching the layout to a BoxLayout, one of which would be to not inherit directly from a particular Layout class and instead add/remove different Layout widgets to Test as needed.
For example, Test could start off with a GridLayout child widget that is the parent of the Label, CheckBox, and Button widgets. Then when you want to switch layouts for printing your results, you clear those widgets and start by adding a BoxLayout to Test.
I want to generate a dropdown-list in my second screen managed by Kivys ScreenManager. If I do so, I get this traceback:
...
File "C:/Users/ORANG/PycharmProjects/waldi/playground/cw.py", line 76, in on_text
instance.drop_down.open(instance)
File "C:\Kivy-1.9.0-py2.7-win32-x64\kivy27\kivy\uix\dropdown.py", line 215, in open
'Cannot open a dropdown list on a hidden widget')
kivy.uix.dropdown.DropDownException: Cannot open a dropdown list on a hidden widget
This is the code, which is basically the same as in this
example, just embedded in a screenmanager scenario simple as can be:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.textinput import TextInput
from kivy.properties import ListProperty, StringProperty
import re
from kivy.lang import Builder
Builder.load_string('''
<Lieferant>:
ComboLayout:
Label:
text: 'Label'
ComboEdit:
size_hint: .5, .3
pos_hint: {'center':(.5, .5)}
# `args` is the keyword for arguments passed to `on_text` in kv language
on_text: self.parent.on_text(self, args[1])
''')
class ComboEdit(TextInput):
"""
This class defines a Editable Combo-Box in the traditional sense
that shows it's options
"""
options = ListProperty(('',))
'''
:data:`options` defines the list of options that will be displayed when
touch is released from this widget.
'''
def __init__(self, **kw):
ddn = self.drop_down = DropDown()
ddn.bind(on_select=self.on_select)
super(ComboEdit, self).__init__(**kw)
def on_options(self, instance, value):
ddn = self.drop_down
# clear old options
ddn.clear_widgets()
for option in value:
# create a button for each option
but = Button(text=option,
size_hint_y=None,
height='36sp',
# and make sure the press of the button calls select
# will results in calling `self.on_select`
on_release=lambda btn: ddn.select(btn.text))
ddn.add_widget(but)
def on_select(self, instance, value):
# on selection of Drop down Item... do what you want here
# update text of selection to the edit box
self.text = value
class ComboLayout(BoxLayout):
rtsstr = StringProperty("".join(("Substrate1,,,Substrate1,,,Substrate1,,,",
"Substrate1,,,Substrate1,,,Substrate_coating",
",,,silicon,,,silicon_Substrate,,,substrate_",
"silicon,,,")))
def on_text(self, instance, value):
if value == '':
instance.options = []
else:
match = re.findall("(?<=,{3})(?:(?!,{3}).)*?%s.*?(?=,{3})" % value, \
self.rtsstr, re.IGNORECASE)
# using a set to remove duplicates, if any.
instance.options = list(set(match))
instance.drop_down.open(instance)
class Intro(Screen):
pass
class Lieferant(Screen):
pass
class CWApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(Intro(name='Intro'))
sm.add_widget(Lieferant(name='Lieferant'))
return sm
if __name__ == '__main__':
CWApp().run()
Is it possible to combine them? How would you do this?
This code is running if I just comment this line out, which adds a screen before the screen with the dropdown:
sm.add_widget(Intro(name='Intro'))
Ok - for this question I got the "Tumbleweed Badget" - LOL
I admit it's a very special one and makes obvious, how much I am a beginner here. So I asked another question based on the same problem and spend some effort to make it easier to understand the code and to reproduce it. This paid off! Look here for the solution if you tumble once into that kind of problem: Is it a bug in Kivy? DropDown + ScreenManager not working as expected