So what I am trying to achieve is:
class FirstWindow(Screen):
layout=self.ids['second']
lab1=Label(text="one",size_hint_x=.35)
lab2=Label(text="two",size_hint_x=.35)
layout.add_widget(lab1)
layout.add_widget(lab2)
<FirstWindow>:
BoxLayout:
orientation: "vertical"
size: root.width, root.height
Image:
source: "gui-background.jpg"
size_hint: None, None
size: root.size
allow_stretch: True
keep_ratio: False
RelativeLayout:
id: second
cols:2
size_hint:(0.5, 0.4)
pos_hint:{'x':0, 'y': 0.6}
I want to add both Labels to the RelativeLayout with id: second. The issue that code shows is:
How do I fix it?
I resolved my problem with adding this entire stuff into a simple function
class FirstWindow(Screen):
def abc(self):
layout=self.ids['second']
lab1=Label(text="one",size_hint_x=.35)
lab2=Label(text="two",size_hint_x=.35)
layout.add_widget(lab1)
layout.add_widget(lab2)
Then it's just calling it with a button.
Button:
text: "click"
on_press: root.abc()
I read that it's problematic because self is an argument only available at function call time.
Related
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
I am trying to use kivy with Python to develop a quick app with sliders where one has to use the sliders first to determine a setting and then click a 'Submit' which then loads the desired image into the app window.
I currently have examples in my .kv file to insert a button and an image indiviudally, but I'm not sure how to connect them:
BoxLayout:
orientation:'vertical'
size: root.width,root.height
font_size: 20
padding: 100
spacing: 10
Button:
text: 'press me'
on_press: print("ouch! More gently please")
on_release: print("ahhh")
on_state:
#print("my current state is {}".format(self.state))
size_hint: (0.3,0.3)
Image:
source: 'images\IMG_6980.jpg'
#allow_stretch: True
#keep_ratio: True
pos_hint: {'center_x':0.7}
I feel like I need to do something with the on_press statement but I'm not quite sure what. Any help is appreciated.
I have given you an example below of how to generate an image from a button using the on_press method as you have described. By using the factory module, you can generate templates created in your *.kv files. So to complete your program, you would create more of these templates, and then generate the appropriate image template in your on_press method using conditionals. You could alternatively try to create dynamic templates within Python, but I believe my example to be more simple.
test.py:
import kivy
kivy.require('2.0.0')
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
class TestBoxLayout(BoxLayout):
pass
class TestApp(App):
def build(self):
return TestBoxLayout()
if __name__ == '__main__':
TestApp().run()
test.kv:
#: import Factory kivy.factory.Factory
<TestImage#Image>:
source: 'test.jpg'
#allow_stretch: True
#keep_ratio: True
pos_hint: {'centre_X':0.7}
<TestBoxLayout>:
orientation:'vertical'
size: root.width,root.height
font_size: 20
padding: 100
spacing: 10
Button:
text: 'press me'
on_press: root.add_widget(Factory.TestImage())
on_release: print("ahhh")
on_state:
#print("my current state is {}".format(self.state))
size_hint: (0.3,0.3)
To extend this solution to your comment, I have given a further example below. I want to reiterate that my example is designed to be simple to understand and can certainly be done more efficiently, but hopefully this acts as a clear solution to your problem. Please mark this as the best answer if this fulfils your needs!
test.kv:
#: import Factory kivy.factory.Factory
<TestImage1#Image>:
source: 'test_1.jpg'
#allow_stretch: True
#keep_ratio: True
pos_hint: {'centre_X':0.7}
<TestImage2#Image>:
source: 'test_2.jpg'
#allow_stretch: True
#keep_ratio: True
pos_hint: {'centre_X':0.7}
<TestBoxLayout>:
orientation:'vertical'
size: root.width,root.height
font_size: 20
padding: 100
spacing: 10
Slider:
id: slider
min: 1
max: 2
step: 1
Button:
text: 'press me'
on_press:
if slider.value == 1: root.add_widget(Factory.TestImage1())
elif slider.value == 2: root.add_widget(Factory.TestImage2())
on_release: print("ahhh")
on_state:
#print("my current state is {}".format(self.state))
size_hint: (0.3,0.3)
I'm creating a simple Database-GUI and want to add two lines to a form. each line is a DataField-Object, and contains a label and a TextInput organised by a horizontal Boxlayout. up to here all fine.
The Form-Widget contains also a BoxLayout (Orientaton: "Vertical") organizing the two DataField-Objects by listing them up under each other, but it doesn't work...
My Question: WHY??? and how can I fix it?
dbgui3.py
from kivy import require
require("2.0.0")
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import StringProperty, ObjectProperty
class DataField(Widget):
title = StringProperty()
pass
class Form(Widget):
b = ObjectProperty()
def __init__(self, *rec, **kwargs):
super().__init__()
for df in rec:
self.b.add_widget(DataField(title=df))
class DbGui3(App):
def build(self):
return Form("hello", "world")
if __name__ == '__main__':
DbGui3().run()
dbgui3.kv
<DataField>:
text: txt.text
BoxLayout:
orientation: "horizontal"
size_hint: None, None
size: root.width, 30
canvas:
Color:
rgb: 0.2,0.2,0.2
Rectangle:
size: self.size
Label:
text: root.title
size_hint: None, 1
width: self.texture_size[0]
TextInput:
id:txt
multiline: False
on_text_validate: print("text validated:" + root.text)
<Form>:
b:b
BoxLayout:
id: b
orientation: "vertical"
size_hint: None, None
size: root.width, root.height
canvas:
Color:
rgb: 0.1,0.2,0.3
Rectangle:
size: self.size
The problem is that you are using Widget as a base class for your DataField and Form classes. The Widget class does not take any notice of things like size_hint, pos_hint, and others. It is not intended to be a Layout. I suggest you use BoxLayout as the base for your DataField and some other Layout (perhaps FloatLayout) as the base class for Form. Using this suggestion, your class definitions could be:
class DataField(BoxLayout):
title = StringProperty("example")
class Form(FloatLayout):
pass
Then modifying your kv:
<DataField>:
size_hint: 1, None
height: 30
orientation: "horizontal"
Label:
size_hint: None, None
size: self.texture_size[0]*1.3, root.height
text: root.title
TextInput:
multiline: False
on_text_validate: print(root.title)
<Form>:
BoxLayout:
orientation: "vertical"
size_hint: None, None
size: root.width/2, root.height/2
DataField:
title: "hello"
DataField:
title: "world"
I think this will do what you want.
I am doing an App in kivy using kivyMD but I Want to insert a MDcard, the thing is that I want the MDCard look like this:
But it looks like this:
I know it can be with border_radius but I dont know how to do it,
here is my code:
PY:
import kivy
from kivymd.app import MDApp
from kivymd.uix.card import MDCard
class Home(MDCard):
pass
class Manage(MDApp):
title = 'QUICKP'
def build(self):
return Home()
if __name__ == '__main__':
Manage().run()
KV:
<Home>
MDCard:
size_hint: None, None
size: "280dp", "180dp"
pos_hint: {"center_x": .5, "center_y": .5}
You do not need to modify the card.py file nor do you have to use canvas you just have to place the following in your kivy code
border_radius: 20
radius: [15]
In border_radius you put the border limit that you will be able to use and in radius you put the border level that your mdcard will have.
People advise to use canvas its OK, but it increase your coding and make it very complex. Just go to the Kivymd>uix>card.py line 631 change the default from 3dp to for example 20dp or whatever you want. then go back to your code and type radius: [] and insert your desire number from 20 to 0.
Done!!!
#BernardoOlisan You can use this directly
MDCard:
size_hint: None, None
size: "280dp", "180dp"
pos_hint: {"center_x": .5, "center_y": .5}
elevation: 15
radius: 10
MDLabel:
text: 'Your title'
halign: 'center'
pos_hint: {"center_x": .5, "center_y": .5}
It works for sure
I had the same problem, and there is a border_radius property for MDCards, but it does not work. But i have solved this with this code:
<MyCard>:
orientation: 'vertical'
size_hint: 0.1, 1
canvas.before:
Color:
rgba: app.theme_cls.primary_color
RoundedRectangle:
radius: [10]
size: self.size
pos: self.pos
FloatLayout:
size: self.size
pos: self.pos
pos_hint: {"x": -0.1, "y": -0.6}
And here is a part from .py file:
class Plan(RectangularElevationBehavior, RectangularRippleBehavior, FloatLayout):
ripple_scale = 1.4
text = StringProperty()
text_label = StringProperty()
So, it creates a MyCard(FloatLayout class) with Canvas in it, which inherits from FloatLayout, RippleBehaviour and ElevationBehaviour. And then you can add to that FloatLayout in it to manage content of it. But if it looks strange because of the position, try playing with pos_hint-s.
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.