Appending and removing code elements within Kivy language on button click - python

I have written a Kivy GUI that consists of various buttons and nested layouts. I want to be able to, on a click of a button, append a section of code n number of times within one of these layouts, specifically the scroll layout. Since I have am very new to Kivy and since I cannot seem to find a tutorial on this matter, I am presenting it here.
Here is the Python code:
import kivy
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.widget import Widget
class Interface(Widget):
pass
class GUI(App):
def build(self):
return Interface()
if __name__ == "__main__":
GUI().run()
And the total Kivy code:
<Interface>:
GridLayout:
padding:0
size: root.width, root.height
cols:1
#Top buttons
GridLayout:
size_hint: 1, 0.07
padding:0
cols:1
Button:
text:"Add Phase"
#Chart areas
ScrollView:
do_scroll_y:False
BoxLayout:
orientation: "vertical"
size_hint_x: None
width: self.minimum_width
GridLayout:
size_hint_x: None
width: self.minimum_width
cols:20
#Phase template
GridLayout:
width: 200
size_hint_x: None
cols:1
TextInput:
size_hint: 1, 0.06
halign: 'center'
hint_text:"Phase"
TextInput:
size_hint: 1, 0.06
halign: 'center'
hint_text:"Step"
Button:
size_hint:1, 0.07
text:"Add Step"
GridLayout:
cols:20
#Step template
GridLayout:
width: 100
size_hint_x: None
cols:1
TextInput:
size_hint: 1, 0.06
halign: 'center'
hint_text:"Var1"
TextInput:
size_hint: 1, 0.06
halign: 'center'
hint_text:"Var2"
Button:
background_normal: ''
background_color: 0.28,0.59,0.72,1
text:"Test"
Button:
size_hint:1, 0.07
text:"Delete"
Button:
background_color: 0.8,0,0,1
size_hint:1, 0.07
text:"Delete"
You will see in the Kivy code that these is a commented secion called #Phase template. Basically on pressing the button Add Phase, this entire section and its children elements should be appended in the immediate parent GridLayout.
Here you can press the Add Phase button:
Which will result in this:
And then finally, pressing the Delete button should remove that specific appended section of code.
Again, no idea how to approach this from the Kivy language, which seems a bit rigid to work with. But I am sure what I want to do can be accomplished.

One way to accomplish that is to create a Phase class, and add a kv rule for building instances of Phase. Then, in kv, you can use Factory.Phase() to create new instances.
Modify your kv as:
#:import Factory kivy.factory.Factory
<Interface>:
GridLayout:
padding:0
size: root.width, root.height
cols:1
#Top buttons
GridLayout:
size_hint: 1, 0.07
padding:0
cols:1
Button:
text:"Add Phase"
on_release: grid.add_widget(Factory.Phase()) # this adds another Phase
#Chart areas
ScrollView:
do_scroll_y:False
BoxLayout:
orientation: "vertical"
size_hint_x: None
width: self.minimum_width
GridLayout:
id: grid # added id to identify where to add new Phase instances
size_hint_x: None
width: self.minimum_width
cols:20
# initial Phase instance
Phase:
#Phase template
<Phase#GridLayout>:
width: 200
size_hint_x: None
cols:1
TextInput:
size_hint: 1, 0.06
halign: 'center'
hint_text:"Phase"
TextInput:
size_hint: 1, 0.06
halign: 'center'
hint_text:"Step"
Button:
size_hint:1, 0.07
text:"Add Step"
GridLayout:
cols:20
#Step template
GridLayout:
width: 100
size_hint_x: None
cols:1
TextInput:
size_hint: 1, 0.06
halign: 'center'
hint_text:"Var1"
TextInput:
size_hint: 1, 0.06
halign: 'center'
hint_text:"Var2"
Button:
background_normal: ''
background_color: 0.28,0.59,0.72,1
text:"Test"
Button:
size_hint:1, 0.07
text:"Delete"
Button:
background_color: 0.8,0,0,1
size_hint:1, 0.07
text:"Delete"
on_release: root.parent.remove_widget(root) # delete this Phase
Key points are the <Phase#GridLayout> rule, the new grid id, and the use of Factory in the Add Phase Button.

Related

I need to put the source of TextInput inside a variable in my Main.py in kivy

I'm trying to use this id "self.ids.input_tornillo.text" and put it inside a variable in my main.py but when running this code I got this Error: "NameError: name 'self' is not define". I think the word self just work inside a function. But I don't know if it really necessary create a funcion for this or I can do it in another way. Pls someone can give me a hand? -
main.py
class FirstWindow(Screen):
#Variables Shopping Cart
var1= StringProperty("images/shoppingcart.png")
var2= StringProperty("images/shoppingcart2.png")
#Variables Items
variable= self.ids.input_tornillo.text
#Here is the problem with self, How can I put it in a variable?
main.kv
<FirstWindow>:
name: "Catalogue"
BoxLayout:
orientation:'vertical'
pos_hint: {'top': 1}
size: root.width, root.height
size_hint_y: .55
GridLayout:
size:(root.width, root.height)
size_hint_x: None
size_hint_y: None
cols:4
height: self.minimum_height
Label:
text: "Items"
#font_size: 25
Label:
text: "Number"
Label:
text: "Price"
Label:
text: "Add to Cart"
ScrollView:
size_hint_y: .80
pos_hint: {'x':0, 'y': .11}
do_scroll_x: True
do_scroll_y: True
GridLayout:
size:root.width, root.height
size_hint_x: None
size_hint_y: None
cols:4
#height: self.minimum_height
#Label:
# text: "Items"
#font_size: 25
#Label:
# text: "Number"
#Label:
# text: "Price"
#Label:
# text: "Add to Cart"
Label:
text: "Tornillos"
text_size: self.size
halign: 'center'
valign: 'bottom'
Image:
id: image_tornillos
allow_stretch: True
keep_ratio: True
size_hint: 0.2,0.2
width: 60
height: 80
#pos_hint: {'center_x':1, 'center_y':1}
source: "images/tornillo.png"
center_x: self.parent.center_x
center_y: self.parent.center_y+10
TextInput:
id: input_tornillo #I need to put this id in a variable in my main.py
text: ""
halign: "right"
font_size: 18
multiline: True
#size_hint: (1, .15)
I was thinking create a function too and make it like this
class FirstWindow(Screen):
#Variables Shopping Cart
var1= StringProperty("images/shoppingcart.png")
var2= StringProperty("images/shoppingcart2.png")
#Variables Items
def list_items(self):
tornillo= self.ids.input_tornillo.text
tornillo1= float(tornillo) * 0.10
tornillo2= str(tornillo1)
but I don't how to call my variable "tornillo2" in my main.kv either
TextInput:
id: input_tornillo
text: ""
halign: "right"
font_size: 18
multiline: True
#size_hint: (1, .15)
Label:
id: label_tornillo
#text: root.ids.input_tornillo.text*2
text: root.tornillo2
root.tornillo2 is giving me this error...
AttributeError: 'FirstWindow' object has no attribute 'tornillo2'
In the python code, define a property for the variable "tornillo2"
tornillo2 = StringProperty("")
Now you can use it in the kv file as you did it already
(text: root.tornillo2)

Trying to call a layout class within a screen layout - kivy

Essentially, I have multiple screens, being controlled by the screen manager (primarily for logins), however on the main screen, I have it broken up with a header, nav bar, and a section for the body. In the body section is where I want to be able to call different layouts that I have classes for, and add and remove these as buttons on the nav bar are pressed.
Im using kivy to control all aspects of the gui, but am rather new to it. At the moment I have:
Builder.load_string('''
<MainMenu>:
FloatLayout:
canvas.before:
Color:
rgba: 0.235, 0.271, 0.302, 1
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
orientation: 'vertical'
rows: 4
Label:
size_hint: 1,0.12
text: "ProductName"
text_size: self.size
halign: 'left'
valign: 'middle'
font_size: 32
color: [0.114,0.18,0.224,1]
StackLayout:
size_hint: 1,0.10
orientation: 'lr-tb'
#Purchase Order System
Button:
id: butpos
text: "Purchase Order"
size_hint: 0.166,1
on_press: spaceholder.add_widget(pos)
#Asset Assignment System
Button:
id: butaas
text: "Asset Assignment"
size_hint: 0.166,1
#Review and Revise System
Button:
id: butrrs
text: "Review"
size_hint: 0.166,1
#Administrative System
Button:
id: butadm
text: "Administration"
size_hint: 0.166,1
#Analytics and Reporting System
Button:
id: butars
text: "Analytics"
size_hint: 0.166,1
#Placeholder, possibly documentation
Button:
id: butbut
text: "Placeholder"
size_hint: 0.166,1
Label:
canvas.before:
Color:
rgba: 0.259, 0.643, 0.937, 1
Rectangle:
pos: self.pos
size: self.size
size_hint: 1,0.005
Widget:
id: spaceholder
<PurchaseOrder>
id: pos
orientation: 'lr-tb'
#Asset Details
Label:
BoxLayout:
orientation: 'vertical'
Label:
text: "Asset Requirements"
Label:
text: "Asset1: "
TextInput:
id: as1count
Label:
text: "Asset2: "
TextInput:
id: as2count
''')
class MainMenu(Screen):
pass
class PurchaseOrder(StackLayout):
pass
class MainApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(MainMenu(name='mainmenu'))
return sm
if __name__ == '__main__':
MainApp().run()
PurchaseOrder is a class that Id like to be added or removed, much like classes I intend to set up for the other navigation options. Ive tried a number of methods, but I think im missing something. Im either being thrown with 'pos' is not defined, or that 'spaceholder' has wrong attributes when ive tried different combinations.
You can make that work by modifying the Purchase Order Button:
on_press: spaceholder.add_widget(Factory.PurchaseOrder())
That requires an addition to your kv to import Factory:
#:import Factory kivy.factory.Factory
I would recommend using a Layout for your spaceholder to take advantage of their sizing and positioning capabilities. For example, using an AnchorLayout automatically centers its child (but is limited to a single child). Like this:
AnchorLayout:
id: spaceholder

Kivy - Referencing IDs of Subclasses (Nested IDs)

I'm creating a kivy app right now. I'm quite new to Kivy and kv lang and there doesn't seam to be much rumour about it although i find it great to seperate code logic and layout, especially after some pygame development.
So to my actuall problem: I have a wiki-style screen for screenmanager, consisting out of a BoxLayout:
Title as Label
Scrollable Label for the Text (later , there shall be displayed a nested kv file)
Buttons for Navigation (scroll up and go back to main screen)
Now I'm recreating the Navigation buttons to be floating type as known from many webpages and apps. Problem is, I suddendly cant reference my ScrollView anymore. Any help or suggestions?
<Wiki>:
name: "wiki"
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: "picture.jpg"
BoxLayout:
id: box
orientation: "vertical"
padding: 10
spacing: 10
Label:
font_size: 20
size_hint_y: .1
text: root.country_text
ScrollView:
id: scrlvw
BackgroundLabel:
background_color: 220,220,220, 0.5
size_hint_y: None
text_size: self.width, None
height: self.texture_size[1]
halign: "left"
valign: "top"
text: root.wiki_text
FloatLayout:
size_hint_y: .1
Button:
size_hint_x: .2
pos_hint: {"center_x": .25, "center_y": .5}
text: "Back"
on_release:
app.root.current = "main"
root.manager.transition.direction = "right"
FloatButton:
size_hint_x: .2
pos_hint: {"center_x": .75, "center_y": .5}
text: "Up"
on_release:
widget = BoxLayout()
widget.ids.scrlvw.scroll_y = 0
Before, when it worked, it was:
BoxLayout:
FloatLayout:
size_hint_y: .1
Button:
size_hint_x: .2
pos_hint: {"center_x": .25, "center_y": .5}
text: "Back"
on_release:
app.root.current = "main"
root.manager.transition.direction = "right"
Button:
size_hint_x: .2
pos_hint: {"center_x": .75, "center_y": .5}
text: "Up"
on_release:
scrlvw.scroll_y = 0
Well as its just a design question, I guess i temporary have to dismiss the floating design. But I would be so pleased if you could help me understand this language better.
As long as the 'kv' code described as "when it worked" is still in the same <Wiki> rule, it should still work. The newer kv code will never work as you are trying to create a new BoxLayout and reference an id in it. That has two problems:
The newly created BoxLayout is not the BoxLayout instance that appears in your GUI, so any changes to it will have no effect on what appears in the display.
Unless you have a <BoxLayout> rule in your kv, the newly created BoxLayout will not have a scrlvw id.
The ids defined within a kv rule are available for use only within that rule. See the documntation.

kivy scrollview consisting of maplotlib plots not scrolling

I'm currently working on building a digital planner app, where I want to include some statistics-related features. Obviously, matplotlib is an optimal means of doing that, but I have two problems with it when I try to add more than one plot in a a Kivy ScrollView:
Each plot decreases in size so much that you cannot see what's
actually being displayed;
Kivy ScrollView is not scrolling - unfortunately, very common.
I've tried setting ScrollView's height equal to ScrollView.minimum_height and yet I get no result.
Here's a bit of my Python code:
class StatsWindow(FloatLayout, MDTabsBase):
dates_from, dates_to, plot_scroll = [str(datetime.today()).split()[0]], [str(datetime.today()).split()[0]], ObjectProperty(None)
def add_sp_plot(self, date_from, date_to):
# There were too many lines of data handling and plot creating, so I've only left the KivyPart:
# ------- KIVY part ------- KIVY part ------- KIVY part ------- KIVY part ------- #
self.plot_scroll.plot_layout.clear_widgets()
self.plot_scroll.plot_layout.add_widget(FigureCanvasKivyAgg(plt.gcf(), size_hint_y=None))
self.plot_scroll.plot_layout.add_widget(FigureCanvasKivyAgg(plt.gcf(), size_hint_y=None))
self.plot_scroll.plot_layout.add_widget(FigureCanvasKivyAgg(plt.gcf(), size_hint_y=None))
Here's a bit of my Kivy code:
<StatsWindow>:
name: "stats"
text: "STATS"
icon: "thumbs-up-down"
plot_scroll: plot_scroll
choose_date_to: choose_date_to
choose_date_from: choose_date_from
FloatLayout:
MDLabel:
halign: "center"
size_hint: 1, .1
text: "Choose the dates to view your stats"
font_size: self.width / 17
color: app.theme_cls.primary_color
pos_hint: {"top": .98, "center_x": .5}
BoxLayout:
MDFlatButton:
id: choose_date_from
pos_hint: {"center_x": .25, "top": .88}
text: "from"
size_hint: .4, .1
font_size: self.width / 11
on_release: root.open_date_picker(root.dates_from, self)
MDFlatButton:
text: "|"
size_hint: .1, .1
pos_hint: {"center_x": .5, "top": .88}
MDFlatButton:
id: choose_date_to
font_size: self.width / 11
pos_hint: {"center_x": .75, "top": .88}
text: "to"
size_hint: .4, .1
font_size: self.width / 11
on_release:
root.open_date_picker(root.dates_from, self)
BoxLayout:
MDFlatButton:
size_hint: 1, .1
text: "Load the statistics"
pos_hint: {"center_x": .5, "top": .78}
on_release: root.add_sp_plot(choose_date_from.text, choose_date_to.text)
ScrollView:
id: plot_scroll
do_scroll_y: True
do_scroll_x: False
pos_hint: {"top": .68}
plot_layout: plot_layout
size: root.width, root.height
GridLayout:
id: plot_layout
cols: 1
height: self.minimum_height
Here's the picture of what I get when I run the program:
The ScrollView will only scroll if its child (the GridLayout) is larger than the ScrollView. Also, the line:
height: self.minimum_height
will have no effect unless you add the line:
size_hint_y: None
You can increase the size of the plots by specifying a row_default_height for the GridLayout and eliminating the size_hint_y=None for the FigureCanvasKivyAgg. So, I would suggest specifying your ScrollView as:
ScrollView:
id: plot_scroll
do_scroll_y: True
do_scroll_x: False
pos_hint: {"top": .68}
plot_layout: plot_layout
GridLayout:
id: plot_layout
cols: 1
size_hint_y: None
row_default_height: 500 # any default row height that you desire
height: self.minimum_height
Then, add the plots using:
def add_sp_plot(self, date_from, date_to):
# There were too many lines of data handling and plot creating, so I've only left the KivyPart:
# ------- KIVY part ------- KIVY part ------- KIVY part ------- KIVY part ------- #
self.plot_scroll.plot_layout.clear_widgets()
self.plot_scroll.plot_layout.add_widget(FigureCanvasKivyAgg(plt.gcf()))
self.plot_scroll.plot_layout.add_widget(FigureCanvasKivyAgg(plt.gcf()))
self.plot_scroll.plot_layout.add_widget(FigureCanvasKivyAgg(plt.gcf()))
Removing the size_hint_y=None leaves the default value of size_hint_y as 1, so that the plot will take up all the space that the GridLayout allocates to it (the row_default_height).

Adding variable number of controls to DropDown - weakly-referenced object no longer exists

I have a dropdown with a list of the months in it. When the Month is selected, I'm trying to dynamically populate buttons in a second dropdown with the correct number of days. When I do so, I get:
ReferenceError: weakly-referenced object no longer exists
Here are my files for reference:
main.py:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
globIDs = {}
class appScreen(BoxLayout):
def dayDropPop(self, num):
globIDs['dayDropDown'].populate(num)
class ExtDropDown(BoxLayout):
heldValue = ''
def setID(self, key):
globIDs[key] = self
def changeValue(self, sentText, parent):
self.heldValue = sentText
parent.text = sentText
class PriorityDropDown(ExtDropDown):
pass
class MonthDropDown(ExtDropDown):
def __init__(self, **kwargs):
super(MonthDropDown, self).__init__(**kwargs)
self.setID('monthDropDown')
def monthSelect(self, month):
monthDay = {'Jan': 31, 'Feb': 29, 'Mar': 31, 'Apr': 30, 'May': 31, 'Jun': 30, 'Jul': 31, 'Aug': 31, 'Sep': 30,
'Oct': 31, 'Nov': 30, 'Dec': 31}
numOfDays = monthDay[month]
appScreen().dayDropPop(numOfDays)
def testingFurther(self):
print()
class DayDropDown(ExtDropDown):
def __init__(self, **kwargs):
super(DayDropDown, self).__init__(**kwargs)
self.setID('dayDropDown')
def populate(self, num):
for i in range(0, num):
newButt = Button(text=str(num + 1))
self.ids.drop.add_widget(newButt)
class schedulerApp(App):
def build(self):
return appScreen()
if __name__ == '__main__':
schedulerApp().run()
scheduler.kv:
<PriorityDropDown>:
Button:
id: ddRoot
text: 'Priority'
on_release: drop.open(ddRoot)
size_hint_y: None
height: root.height
DropDown:
id: drop
on_parent: self.dismiss()
on_select: root.changeValue(args[1], ddRoot)
Button:
text: 'Top'
size_hint_y: None
height: root.height
on_release: drop.select(self.text)
Button:
text: 'High'
size_hint_y: None
height: root.height
on_release: drop.select(self.text)
Button:
text: 'Medium'
size_hint_y: None
height: root.height
on_release: drop.select(self.text)
Button:
text: 'Low'
size_hint_y: None
height: root.height
on_release: drop.select(self.text)
<MonthDropDown>:
Button:
id: ddRoot
text: 'Month'
on_release: drop.open(ddRoot)
size_hint_y: None
height: root.height
DropDown:
id: drop
on_parent: self.dismiss()
on_select: root.monthSelect(args[1])
Button:
text: 'Jan'
size_hint_y: None
height: root.height
on_release: drop.select(self.text)
Button:
text: 'Feb'
size_hint_y: None
height: root.height
on_release: drop.select(self.text)
Button:
text: 'Mar'
size_hint_y: None
height: root.height
on_release: drop.select(self.text)
Button:
text: 'Apr'
size_hint_y: None
height: root.height
on_release: drop.select(self.text)
Button:
text: 'May'
size_hint_y: None
height: root.height
on_release: drop.select(self.text)
Button:
text: 'Jun'
size_hint_y: None
height: root.height
on_release: drop.select(self.text)
Button:
text: 'Jul'
size_hint_y: None
height: root.height
on_release: drop.select(self.text)
Button:
text: 'Aug'
size_hint_y: None
height: root.height
on_release: drop.select(self.text)
Button:
text: 'Sep'
size_hint_y: None
height: root.height
on_release: drop.select(self.text)
Button:
text: 'Oct'
size_hint_y: None
height: root.height
on_release: drop.select(self.text)
Button:
text: 'Nov'
size_hint_y: None
height: root.height
on_release: drop.select(self.text)
Button:
text: 'Dec'
size_hint_y: None
height: root.height
on_release: drop.select(self.text)
<DayDropDown>:
height: root.height
Button:
id: ddRoot
text: 'Day'
on_release: drop.open(ddRoot)
size_hint_y: None
height: root.height
DropDown:
id: drop
on_parent: self.dismiss()
on_select: root.changeValue(args[1], ddRoot)
<appScreen>:
orientation: 'vertical'
Label:
size_hint_y: .1
text: 'Hello World'
GridLayout:
size_hint_y:.1
width: root.width
cols: 3
Button:
Button:
Button:
ScrollView:
canvas.before:
Color:
rgba: .3, .3, .3, 5
Rectangle:
pos: self.pos
size: self.size
GridLayout:
cols: 3
Label:
id: textReceiver
text: 'Words'
text_size: self.size
halign: 'left'
valign: 'top'
Label:
Label:
BoxLayout:
size_hint_y: .125
TextInput:
size_hint_x: .7
PriorityDropDown:
size_hint_x: .3
BoxLayout:
size_hint_y: .125
MonthDropDown:
size_hint_x: .35
DayDropDown:
id: 'dayDrop'
size_hint_y: 1
size_hint_x: .2
TextInput:
size_hint_x: .45
I think the issue stems from the controls in question being created in Kivy code, rather than in Python. What testing I've done leads me to believe that I'm referencing my DayDropDown widget incorrectly. However, I don't know how else I would do so. With that in mind, how would I go about referencing my DayDropDown using what I already have? If that isn't my issue, what else might be causing the ReferenceError to be thrown?
Edit:
Messed with my code a little bit. I created a new class "globAddable" with methods "getID" - a simple return self - and put setID in there instead. I then set my setID now assigns self.getID() to a variable, then uses that variable as the object to be added to the globObjects (formerly globIDs) dictionary.
I also created a new class for my DropDown object, called ExtDropArray, which lives in my DayDropDown. I moved the populate() method to this new class so that it can be called directly by the Dropdown, rather than its parent BoxLayout. I made the ExtDropArray and ExtDropDown inherit from globAddable to expose the setID (and implicitly getID) methods.
The net result of all this is exactly the same. I still don't see my day DropDown when clicking the button on DayDropDown, and after testing with different values on the MonthDropDown, again I get the 'ReferenceError: weakly-referenced object no longer exists' error. However, I am noticing that the offending line is actually the method that opens the dropdown (drop.open(ddRoot), which is called on line 114 of my .kv file). This still doesn't quite give me enough information to know which part of the process is causing the error, be it the adding of the buttons to the DropDown or simply the calling of the open method. Given this new information, is anyone able to deduce what needs to change?
Alright, I finally figured this one out.
My ReferenceError was not a result of my methods, but rather a fundamental misunderstanding on my part of the implementation of DropDown objects. The fix was simply
<DayDropDown>:
drop: drop.__self__
...
This took me one heck of a long time to find, but came in the form of this documentation. Seeing as no other posts mentioning these ReferenceErrors makes any mention to this document, I'm leaving it here for others to use in case they run into a similar issue.
An answer for the year 2020
The official documentation (Kv language Programming Guide) says to add 'strong' references such as id_name: id_name.__self__ in the KV code, but it is unclear where this is necessary. What's more, it did not solve the ReferenceError: weakly-referenced object no longer exists errors for me.
What did work is forcing Buildozer to use a specific version of hostpython3 by adding this to the requirements line of the buildozer.spec file:
python3==3.7.5, hostpython3==3.7.5
One more note: after adding the above to requirements, I went back and removed all my __self__ references and it still worked fine, so apparently these are no longer needed in Kivy KV language.
Credit for this goes to the beautiful answer from leo10011.

Categories

Resources