Removing Row from Kivy GridLayout or Refreshing GridLayout - python

I have a kivy app that displays data from a pandas dataframe generated from the output of a windows command.
In the GirdLayout I have a button. I am trying to have the button remove a row from the GridLayout after it runs its command.
I have figured out how to remove the button from the GridLayout but can't seem to figure out the rest.
Alternatively refreshing the layout by rerunning the windows command and generating a new dataframe would also work.
I've looked at this answer but haven't been able to apply it to my code in a way that works.
Kivy Removing elements from a Stack- / GridLayout
def removeRow(self, instance):
#This removes the button
self.remove_widget(instance)
#I've tried all 3 of these to refresh the GridLayout
super(MakeTable, self).__init__()
super(MakeTable, self).do_layout()
MakeTable.do_layout(self)
My init for building the GridLayout
def __init__(self, **kwargs):
table = ["Stuff"]
df = pandas.DataFrame(table)
super(MakeTable, self).__init__(**kwargs)
self.cols = 2
for index, row in df.iterrows():
btnremove = Button(text="Remove")
btnremove.bind(on_press=self.removeRow)
self.add_widget(btnremove)
lblUser = Label(text=row['USERNAME'])
self.add_widget(lblUser)

I changed the container layout to a BoxLayout and changed my init to add the items to a GridLayout and then add it to the BoxLayout.
This allowed me to use remove_widget on the parent of the clicked button.
def removerow(self, instance):
self.remove_widget(instance.parent)
def __init__(self, **kwargs):
grid = GridLayout()
grid.cols = 2
lblUser = Label(text=row['USERNAME'])
grid.add_widget(lblUser)
lblServer = Label(text=row['SERVER'])
grid.add_widget(lblServer)
self.add_widget(grid)

Related

Kivy Buttons on_press method doesn't work

So I have written this basic kivy code. I want to read sentences from a file, and dynamically create buttons for each one of them. Then, I want these buttons to disappear when they are clicked. In a for loop, I create my buttons, and put my buttons with the index i in a list. Then with the on_press method, it should delete itself.
Button_List[i].bind(on_press= lambda x: self.remove_widget(Button_List[i]))
So there is a Button in Button_List[i] , and when it is clicked, it should run:
self.remove_widget(Button_List[i])
so it should delete itself
I have 5 buttons for example, the problem is, that it whichever button I click, it deletes the button with the highest index. And the other buttons dont get deleted. I feel like kivy is only executing the last index, but I am not sure.
Here is my code:
new.py:
import kivy.uix.button as kb
from kivy.app import App
from kivy.uix.widget import Widget
from Nils_Programm_verkürzt import lektionstextlesen
from kivy.uix.gridlayout import GridLayout
sentences = ['example_sentence1','example_sentence2','example_sentence3','example_sentence4','example_sentence5',]
class Button_Widget(Widget):
def __init__(self, **kwargs):
super(Button_Widget, self).__init__(**kwargs)
global Button_List
Button_List = []
for i in range(len(sentences)):
print(i)
Button_List.append(kb.Button(text=sentences[i],pos=(self.width * i, self.height * i)))
Button_List[i].size = 50, 50
print('binding'+ str(i))
Button_List[i].bind(on_press= lambda x: self.remove_widget(Button_List[i]))
print(Button_List[i])
self.add_widget(Button_List[i])
class ButtonApp(App):
def build(self):
return Button_Widget()
if __name__ == "__main__":
ButtonApp().run()
Your help is very much appreciated :).
That's a common problem when using lambda in a loop. They all get the last value of the loop. A fix is to create a new variable that holds the current loop variable value. So, try replacing:
Button_List[i].bind(on_press= lambda x: self.remove_widget(Button_List[i]))
with:
Button_List[i].bind(on_press= lambda x, j=i: self.remove_widget(Button_List[j]))

Kivy Python Scrollview event misfiring when in a class instantiated multiple times

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)

Kivy, StackLayout, object order

I have a screen with a StackLayout. The first row of the stack includes a textinput and a "+" button which is meant to add another identical row below the actual one in a loop (i.e. another textinput with another "add" button). Then there is a "Save" button, which is supposed to be always at the end of the stack.
The dictionary is supposed to later grab the input from the text field, when pressing the save button, but this should not be relevant to my problem.
There are two problems with my code:
First, when I press the "+" button in a row, the button that gets highlighted is not this button itself, but the one in the row below (e.g. if there are three rows and I press the button in the second row, the button in the third row is highlighted)
Second and most importantly, starting from the second row onwards, each press of the "+" button adds a new row above the actual one, rather than below it.
I am aware that kivy assigns reverse order to the widgets (i.e. the one added last will be drawn first) and that is why I am manually assigning indexes to the new rows. However, I cannot achieve the desired behavior.
Here is a minimal working version:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.stacklayout import StackLayout
from kivy.uix.screenmanager import ScreenManager, Screen
class AddWindow(Screen):
def __init__(self, **kwargs):
super(AddWindow, self).__init__(**kwargs)
self.grid = StackLayout()
self.grid.pos_hint = {"x":0.05,"top":0.8}
self.grid.size_hint = (0.9,None)
self.add_widget(self.grid)
self.i = 1
self.n = 1
self.inputs = {}
self.ing1 = TextInput(size_hint=(0.9,'0.3sp'))
self.grid.add_widget(self.ing1)
self.inputs['0'] = 'ing1'
self.addIng = Button(text="+", size_hint=(0.1,'0.3sp'))
self.addIng.bind(on_press=self.addIngredient)
self.grid.add_widget(self.addIng)
self.doneButton = Button(text="Save")
self.grid.add_widget(self.doneButton, index=0)
def addIngredient (self, instance):
self.ing = TextInput(size_hint=(0.9,'0.3sp'))
self.inputs[self.i] = 'ing{}'.format(self.i+1)
self.grid.add_widget(self.ing, index=self.n+1)
self.addNext = Button(text="+", size_hint=(0.1,'0.3sp'))
self.addNext.bind(on_press=self.addIngredient)
self.grid.add_widget(self.addNext, index=self.n+2)
self.i += 1
self.n += 2
print(self.inputs)
WMan = ScreenManager()
WMan.add_widget(AddWindow(name='add'))
class RecipApp(App):
def build(self):
return WMan
if __name__ == "__main__":
RecipApp().run()
What am I missing? Here is a screenshot for better clarity:
Screenshot
Here is a brute force method to do what you want by rebuilding the StackLayout each time a + `Button is pressed:
def addIngredient(self, instance):
tmp_children_list = self.grid.children[:]
self.grid.clear_widgets()
for index in range(len(tmp_children_list)-1, -1, -1):
child = tmp_children_list[index]
self.grid.add_widget(child)
if child == instance:
# this is the pressed Button, so add new row after it
self.n += 1
self.ing = TextInput(size_hint=(0.9,'0.3sp'))
self.ing.text = str(self.n) # add text just for identification
self.grid.add_widget(self.ing)
self.addNext = Button(text="+", size_hint=(0.1,'0.3sp'))
self.addNext.bind(on_press=self.addIngredient)
self.grid.add_widget(self.addNext)
Here is a quick and dirty approach , where your problems should be fixed due to a remove and add of the save button. Between those to actions you add a new row before you finally add the save button again. Hope it helps you to adjust your code. The key part is the custom on_press method of the AddButton.
from kivy.app import App
from kivy.uix.stacklayout import StackLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.core.window import Window
class AddButton(Button):
def __init__(self, **kwargs):
super(AddButton, self).__init__(**kwargs)
def on_press(self):
self.parent.remove_widget(self.parent.save_btn)
self.parent.add_widget(TextInput(size_hint=(None, None), size=(Window.width * 0.8 - self.parent.padding[1], Window.height * 0.1)))
self.parent.add_widget(AddButton(text="+", size_hint=(None, None), size=(Window.width * 0.2, Window.height * 0.1)))
self.parent.add_widget(self.parent.save_btn)
class MyApp(App):
def build(self):
Window.size = (400, 600)
layout = StackLayout(orientation="lr-tb", padding=(20, 40))
layout.save_btn = Button(text="Save", size_hint=(None, None),
size=(Window.width - layout.padding[1], Window.height * 0.1))
layout.add_widget(TextInput(size_hint=(None, None), size=(Window.width * 0.8 - layout.padding[1], Window.height * 0.1)))
layout.add_widget(AddButton(text="+", size_hint=(None, None), size=(Window.width * 0.2, Window.height * 0.1)))
layout.add_widget(layout.save_btn)
return layout
MyApp().run()

Is ScreenManager compatible with DropDown in Kivy?

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

Kivy - Create new widget and set its position and size

got a little problem. So Iam trying to create my own widget, and i have succeed, only except for setting its size and position right(to be same as its parrent).
class Story(App):
def build(self):
return MyWidgets().init()
The app has GridLayout as a holder, into which i want to pass the StoryWidget
class MyWidgets(object):
def init(self):
root = GridLayout(cols=2,rows=1)
root.add_widget(StoryWidget())
root.add_widget(Button())
return root
Story Widget goes as this:
class StoryWidget(Widget):
def __init__(self,**kwargs):
super(StoryWidget, self).__init__(**kwargs)
topLayout=BoxLayout(orientation = "vertical")
topLayout.add_widget(Button(text="first"))
topLayout.add_widget(Button(text="second"))
self.add_widget(topLayout)
If I try to get the background color to it, it works fine:
with self.canvas.before:
Color(.9,.9,1)
self.Backgroud = Rectangle(pos=self.pos,size=self.size)
self.bind(pos=self.repaint,size=self.repaint)
self.bind(pos=self.resize,size=self.resize)
def repaint(self,*args):
self.Backgroud.pos = self.pos
self.Backgroud.size = self.size
The whole firs column of root(Gridlayout) gets correctly repainted in white,
but the Widget stands on defaut pos(0,0) and default size(100,100).
From what i know, its because Widget cant handle these things. Layout should do it automatically somehow. As can be seen, the root widget of StoryWidget is layout. I do not know why it`s not working. I tried to inherit from the Layout instead of Widget, but still nothing. Any advice? Thanks!
Alright, i have figured it out, turns out i forgot to set appropriate attributes. So Iam now using Gridlayout instead of BoxLayout, in which case it needs cols and rows so it should now look like this:
class StoryWidget(GridLayout):
def __init__(self,**kwargs):
self.cols=1
self.rows=1
super(StoryWidget, self).__init__(**kwargs)
topLayout=BoxLayout(orientation = "vertical")
topLayout.add_widget(Button(text="first"))
topLayout.add_widget(Button(text="second"))
self.add_widget(topLayout)
with self.canvas.before:
Color(.9,.9,1)
self.Backgroud = Rectangle(pos=self.pos,size=self.size)
self.bind(pos=self.repaint,size=self.repaint)
self.bind(pos=self.resize,size=self.resize)

Categories

Resources