The Goal
I am trying to create a simple script that demonstrates how to attach a function to a button that can view (and soon change) properties of another widget.
I know this is possible based off of the example script effectwidget.py.
My Methods
At the moment, I'm trying to reverse engineer effectwidget.py because it does a lot of things I'll probably want to do with kivy.
In effectwidget.py, the SpinnerRow class has a function called update_effectwidget() that can view/edit attributes of specific instances of ComparisonWidget (which isn't even a child of SpinnerRow in the widget tree). SpinnerRow has child widgets EffectSpinner which trigger update_effectwidget().
In my script, ButtonHolder plays the role of SpinnerRow and colorChange1() plays the role of update_effect().
My Code
This code was reduced to only show the reproducible error. So I don't intend on using this to change the color of the labels.
#!/usr/bin/env python3
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
from kivy.uix.label import Label
from kivy.lang import Builder
class ButtonHolder(BoxLayout):
def colorChange1(self, *args):
print("this function works")
Builder.load_string("""
<MyGrid>:
rows: 3
Label:
canvas.before:
Color:
rgba: 1,0,0,1
Rectangle:
pos: self.pos
size: self.size
id: toplabel
Label:
canvas.before:
Color:
rgba: 0,1,0,1
Rectangle:
pos: self.pos
size: self.size
id: bottomlabel
ButtonHolder:
Button:
effectwidget: toplabel
on_press: root.colorChange1()
""")
class MyGrid(GridLayout):
pass
class TheApp(App):
def build(self):
return MyGrid()
TheApp().run()
The Problem
I am receiving the following error:
AttributeError: 'MyGrid' object has no attribute 'colorChange1'
My questions to you
Why is my function colorChange1() inside my ButtonHolder not being found when it follows the same structure as effectwidget.py?
For the purposes of scope and management, it's not practical for me to give every class its own functions so they can be called by self.functionName(). And if root.functionName() only calls functions in the very root widget (rather than any parents widgets along the way), wouldn't that mean that the root widget of a large kivy program would have to contain dozens of functions?
Note:
The closest questions I could find to this were Kivy 'object has no attribute' Error
and AttributeError: 'Button' object has no attribute 'update_label'.But their cases were too complex and specific to find the answer to my general problem. But I have seen them.
Your main one is the ignorance of the concept of root. To observe it better I have indented better your code:
<MyGrid>:
rows: 3
Label:
id: toplabel
canvas.before:
Color:
rgba: 1,0,0,1
Rectangle:
pos: self.pos
size: self.size
Label:
id: bottomlabel
canvas.before:
Color:
rgba: 0,1,0,1
Rectangle:
pos: self.pos
size: self.size
ButtonHolder:
Button:
effectwidget: toplabel
on_press: root.colorChange1()
The root is the initial element of the structure, in this case it is MyGrid.
Does MyGrid have a colorChange1 method? No, that's why you get that error.
What class does the colorChange1 method belong to? belongs to the ButtonHolder class, then the root must be changed by a reference of an object through an id.
<MyGrid>:
rows: 3
Label:
id: toplabel
canvas.before:
Color:
rgba: 1,0,0,1
Rectangle:
pos: self.pos
size: self.size
Label:
id: bottomlabel
canvas.before:
Color:
rgba: 0,1,0,1
Rectangle:
pos: self.pos
size: self.size
ButtonHolder:
id: button_holder # <---
Button:
effectwidget: toplabel
on_press: button_holder.colorChange1() # <---
Related
I am beginner in kivy. Here I am trying to use a button in custom color and make it rounded. I obtained the desired button which is indeed rounded. But The obtained button doesn't change it's color when clicking. I want to make it change the color when clicking like in case of default rectangular button. But i don't have any idea how to do it. I tried different ways like using 'button_down' property of documentation and using on_press event and all but I couldnot solve the problem. Please Help me guide how to do so. thanks in advance
my.kv file
RoundedButton:
text: "Lets Go"
text_color_normal: (0,0,0,1)
pos_hint: {'center_x': 0.5, 'center_y': 0.4}
size_hint:(0.2,0.2)
<RoundedButton#Button>
background_color: (0, 0,0,0)
background_normal: ''
canvas.before:
Color:
rgba: (3/255, 252/255, 65/255,1)
RoundedRectangle:
size: self.size
pos: self.pos
radius: [28]
MainWindow.py program
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.app import App
from kivy.lang import Builder
kv= Builder.load_file("my.kv")
class Mainpage(FloatLayout):
pass
class MainApp(App):
def build(self):
return Mainpage()
if __name__ == "__main__":
MainApp().run()
You can fix that by defining the colors that you want for when the RoundedButton is up and another when it is down. Here is a portion of a modified version of your kv that does that:
<RoundedButton#Button>
background_color: (0, 0,0,0)
background_normal: ''
up_color: (3/255, 252/255, 65/255,1)
down_color: (1,0,0,1)
canvas.before:
Color:
rgba: self.up_color if self.state == 'normal' else self.down_color
RoundedRectangle:
size: self.size
pos: self.pos
radius: [28]
I trying to create a one screen app with one root rule set. by pressing a button i should see a scrollable text in the grid next to the button. In all the example solutions I see that scrollview is working in similar KV files that I have seen in other questions. Could someone please identify what I have missed in KV file.
my .py file:
import kivy
import string
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.properties import ObjectProperty
from kivy.properties import StringProperty
from kivy.uix.scrollview import ScrollView
from kivy.core.window import Window
class RootContainer(BoxLayout):
instance = ObjectProperty(None)
def __init__(self, **kwargs):
super(RootContainer, self).__init__(**kwargs)
def clickAction1(self, instance):
#identify the button pressed
buttonText = instance.text
self.lbl1.text = instance.text + " some text goes here ... "
myresult = " this is scrolling text.\n " * 30
self.lbl5.text = myresult
class MBApp(App):
def build(self):
return RootContainer()
if __name__ == '__main__':
MBApp().run()
my KV file:
#:kivy 1.0.9
<RootContainer>:
id: theRoot
lbl1: my_labelC
lbl5: my_labelCS
BoxLayout:
orientation: 'vertical'
spacing: 20
padding: 20
canvas:
Color:
rgb: 0, .33, 0
Rectangle:
pos: self.pos
size: self.size
Button:
text: "This is 1st button"
text_size: self.size
size_hint: (.5,1)
on_press: theRoot.clickAction1(self)
Button:
text: "This is 2nd button"
text_size: self.size
size_hint: (.5,1)
on_press: root.clickAction1(self)
GridLayout:
rows: 2
cols: 1
spacing: 10
padding: 10
canvas:
Color:
rgb: .7, .63, 0
Rectangle:
pos: self.pos
size: self.size
Label:
id: my_labelC
canvas.before:
Color:
rgb: 0,0,0
Rectangle:
pos: self.pos
size: self.size
text: "Header text for button clicked ......."
text_size: self.size
ScrollView:
GridLayout:
cols:1
rows:1
height: self.minimum_height
Label:
id: my_labelCS
text: "Scrolling text goes here ....."
I hope this is not a duplicate. Any other suggestions to code are also welcome. Thank you.
You don't set the size of your Label, so the default applies, as any widget, the defaults are
size: 100, 100
size_hint: 1, 1
since size_hintis 1, 1, and the parent is a layout, size itself is overriden by the parent's available space
since you set size: minimum_size in the parent layout, it'll give the minimum size its children require, but the Label doesn't ask for any space, it's size_hint of 1, 1 means that it's happy to take all the available space. Kivy solve this situation by not giving it any space, so the Label size ends up being 0, 0, and the GridLayout's size as well.
So you want to disable size_hint (at least for the height, of the Label, and instead set it to the texture heights
size_hint_y: None
height: self.texture_size[1]
Which is usually sided with setting the texture width to the available width, by setting text_size.
text_size: self.width, None
size_hint_x: 1 # this is the default, so this line is not required, but you want to be sure not to disable this default
Do you really need the GridLayout inside your scrollview? This works for me:
ScrollView:
Label:
id: my_labelCS
size_hint: 1, None
text_size: self.width, None
height: self.texture_size[1]
text: "Scrolling text goes here ....."
This is a follow up to a previous question I've asked on how to change the properties of a kivy widget (update a kivy label text in another class). I've been trying to figure out why the temperature reading on the Menuscreen updates but within the Mashscreen, the text doesn't update. It looks like the values are being passed to eh temperature1def method but the screen widget doesn't update.
Also, is it better to send the value using
Mashscreen().temperature1def(self.test_temp)
or is it better practice to use
self.stuff_p.text = str(self.test_temp) + u'\u00B0F'
within the MenuScreen to update the label within the Mashscreen? Thanks in advance.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.properties import ObjectProperty
from kivy.clock import Clock
sm = """
ScreenManager:
#Handling the gesture event.
id:manager
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 0.5
Rectangle:
pos: 0,0
size: 800, 480
MenuScreen:
id:MenuScreen
name:'MenuScreen'
manager: manager
Mashscreen:
id:Mashscreen
name: 'Mashscreen'
manager: manager
<MenuScreen>:
stuff_r: mainel1temp
Button:
text: "Go to mashscreen"
on_release:
root.manager.current = "Mashscreen"
Label:
id: mainel1temp
text:'0'
size_hint: None, None
size: 75,50
pos: 295,308
font_size:'22sp'
text_size: self.size
halign: 'left'
valign: 'middle'
<Mashscreen>:
stuff_p: temperature1
FloatLayout:
Label:
id: temperature1
text:'100'
size_hint: None, None
size: 75,50
pos: 50,275
font_size:'22sp'
text_size: self.size
halign: 'left'
valign: 'middle'
"""
class MenuScreen(Screen):
test_temp = 99
stuff_r = ObjectProperty(None)
def __init__(self,**kwargs):
super(MenuScreen,self).__init__(**kwargs)
Clock.schedule_interval((self.read_temp), 1)
#self.read_temp(1)
def read_temp(self, dt):
self.test_temp += 1
self.stuff_r.text = str(self.test_temp) + u'\u00B0F'
Mashscreen().temperature1def(self.test_temp)
#self.parent.ids.Mashscreen.stuff_p.text = str(self.test_temp) + u'\u00B0F'
class Mashscreen(Screen):
stuff_p = ObjectProperty(None)
def __init__(self, **kwargs):
super(Mashscreen, self).__init__(**kwargs)
def temperature1def(self, temp1):
print(temp1)
self.stuff_p.text = str(temp1)
class TestApp(App):
def build(self):
return Builder.load_string(sm)
if __name__ == '__main__':
TestApp().run()
First…
Mashscreen().temperature1def(self.test_temp)
This doesn't call the temperature1def method on your Mashscreen instance in the UI, instead, it creates a new Mashscreen instance, calls the method on it, and then let this object be garbage collected by python. If you want to update your UI, you need to get a reference to the widget you want to update.
You define your Mashscreen in the root rule of your application, so you can get it by its id in this object.
App.get_running_app() will return a reference to your currently running app, which has a root attribute, which is your root widget, any widget at the root of a rule can use its ids attribute to get a reference to any id defined in its scope, so.
App.get_running_app().root.ids.Mashscreen.temperature1def(self.test_temp)
will certainly be more like what you actually want to do.
Now, regarding the question about how to do it best in python kivy, i find that it's cleaner to do something like.
App.get_running_app().root.ids.Mashscreen.temperature = self.test_temp
and then to change your Mashscreen class to have a temperature NumericProperty, and to change your kv rule to use this value in the Label.
<Mashscreen>:
stuff_p: temperature1
FloatLayout:
Label:
id: temperature1
text: '%s' % root.temperature
size_hint: None, None
size: 75,50
pos: 50,275
font_size:'22sp'
text_size: self.size
halign: 'left'
valign: 'middle'
Here is a screenshot of my kivy app. I am trying to get the TextInput in the bottom left to be centered in the BoxLayout which it is in, and I don't want it to be the same size as the layout, I want it to be much smaller. This BoxLayout in question resides in the bottom half of the screen. I have tried setting the TextInputs propertycenter:self.parent.center but this doesn't work. As you can see, I have printed the center coords from the BoxLayout into the TextInput using that very line, self.parent.center, with the correct result. Yet, setting the TextInputs center or position to these coords is not centering it, it doesn't move... what am I doing wrong?
py file:
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
class TimeTabler(Widget):
pass
class TimerApp(App):
def build(self):
return TimeTabler()
if __name__ == "__main__":
TimerApp().run()
****kv file:****
#:kivy 1.0
BoxLayout:
orientation: 'vertical'
size: root.size
BoxLayout:
orientation: 'vertical'
Label:
text: 'TimeTabler'
BoxLayout:
TextInput:
text: '%s' % (self.parent.center) # why does this work here
size_hint: None, None
width: sp(200)
height: sp(30)
center: self.parent.center # but not here
You gave the TextInput size_hint: None, None, therefore the BoxLayout doesn't try to manually give it the right size, and it assumes the default size of 100, 100. Just delete the size_hint line to fix it.
Also, several widgets have lines like size: self.size. This is meaningless, self refers to the widget itself, and clearly the line does nothing since it just tries to set the size to what it already is.
Things would also be simpler if you made your TimeTabler inherit from BoxLayout instead of Widget. That way you wouldn't need to manually set it's child BoxLayout's size.
Edit: It looks like I misunderstood what you wanted, here's an example that uses an AnchorLayout to center the TextInput:
<TimeTabler>
BoxLayout:
orientation: 'vertical'
size: root.size
on_touch_down: print self.pos, self.size
canvas:
Color:
rgba: 0, 1, 1, .3
Rectangle:
size: self.size
pos: self.pos
BoxLayout:
orientation: 'vertical'
size: self.size
Label:
text: 'TimeTabler'
BoxLayout:
id: bl
on_touch_down: print 'center', self.center
canvas:
Color:
rgb: 1,1,1
Line:
rectangle: self.x, self.y, self.width, self.height
AnchorLayout:
TextInput:
size_hint: None, None
text: '%s, %s' % (self.get_center_x(), self.get_center_y())
I think your problem was the BoxLayout automatically sets the position of the TextInput even when it is setting its own size. A simple way around this is to just next the TextInput in another widget, in this case an AnchorLayout that takes care of the centering for you. You could also just use a Widget and your previous mechanism of setting the TextInput center.
I am trying to build a basic Kivy app. After adding the basic elements, and running the app, all of the elements are crammed into the bottom left corner. It shows up like this on android and Linux.
Main.py:
from kivy.app import App
from kivy.uix.widget import Widget
class SublimeLauncher(Widget):
pass
class SublimeLauncherApp(App):
def build(self):
return SublimeLauncher()
if __name__ == "__main__":
SublimeLauncherApp().run()
sublimelauncher.kv:
#:kivy 1.2.0
<SublimeLauncher>:
FloatLayout:
BoxLayout:
orientation: 'vertical'
spacing: 10
Label:
text: "Enter the path to the folder to open.\nPress OK if you would like to open without a directory"
TextInput:
id: folderpath
Button:
text: 'OK'
I first tried it with just the BoxLayout, but read somewhere the root widget is always as big as the app. How do I declare the size of the app? Or the layout? How would you go about doing something like a dialog box?
Maybe I am missing something very basic, but I can't seem to figure it out.
Edit: here is what I am seeing..
Your layout has a default size of 100x100 pixels. You can try to color it to see how much space does it take:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
kv = '''
<SublimeLauncher>:
BoxLayout:
canvas:
Color:
rgb: 1, 0, 0
Rectangle:
size: self.size
orientation: 'vertical'
spacing: 10
Label:
text: "Enter the path to the folder to open.\\nPress OK if you would like to open without a directory"
TextInput:
id: folderpath
Button:
text: 'OK'
'''
Builder.load_string(kv)
class SublimeLauncher(Widget):
pass
class SublimeLauncherApp(App):
def build(self):
return SublimeLauncher()
if __name__ == "__main__":
SublimeLauncherApp().run()
Setting non-default size:
kv = '''
<SublimeLauncher>:
BoxLayout:
size: 250, 250
canvas:
Color:
rgb: 1, 0, 0
Rectangle:
size: self.size
orientation: 'vertical'
spacing: 10
Label:
text: "Enter the path to the folder to open.\\nPress OK if you would like to open without a directory"
TextInput:
id: folderpath
Button:
text: 'OK'
'''
Builder.load_string(kv)
Taking full space:
kv = '''
<SublimeLauncher>:
BoxLayout:
size: root.size
canvas:
Color:
rgb: 1, 0, 0
Rectangle:
size: self.size
orientation: 'vertical'
spacing: 10
Label:
text: "Enter the path to the folder to open. \\nPress OK if you would like to open without a directory"
TextInput:
id: folderpath
Button:
text: 'OK'
'''
Builder.load_string(kv)
I recently wrote a post of a little trick I use when I am programming interfaces. The trick will let you see a border around all the widgets (layouts included) you add to the screen. This would be the result for your code:
It takes advantage of inheritance and the Kivy rules to overwrite the base class of all widgets. You just have to add:
<Widget>:
canvas.after:
Line:
rectangle: self.x+1,self.y+1,self.width-1,self.height-1
at the beginning of this file:
<SublimeLauncher>:
FloatLayout:
BoxLayout:
orientation: 'vertical'
spacing: 10
Label:
text: "Enter the path to the folder to open.\nPress OK if you would like to open without a directory"
TextInput:
id: folderpath
Button:
text: 'OK'
As your root widget is not a layout (you made SublimeLauncher inherit Widget), it doesn't set its children size/positions. So your FloatLayout have the defaults, since you don't override them manually either.
pos: 0, 0
size: 100, 100
And these defaults of course constraints the child, since FloatLayout by constraint their size based on their size_hint property.
You want to give them more space, as Nykakin pointed out.
Also, as your text is bigger than the Label (you didn't set halign and text_size either) its texture is centered on the center of the Label, and so some part of it is out of screen. You want to have a look at kivy/examples/widgets/textalign.py