I wanted to make a small program with an widget containing an image, which changes the image when you click on it, but when I ran the program it did not displayed the widget.
Instead it displayed an empty, screen, but with the correct backgroundcolor.
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.clock import Clock
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import StringProperty
kv_string = Builder.load_string('''
<Root>:
Feld:
pos_hint: {"right": 1, 'top': 1}
id: a1
<Feld>:
Image:
pos: root.pos
id: my_image
source: root.weg
''')
class Root(FloatLayout):
kr = StringProperty('kreuz.png')
ks = StringProperty('kreis.png')
def __init__(self, *args, **kwargs):
super(Root, self).__init__(*args, **kwargs)
Clock.schedule_interval(self.Update, 1/60.)
def Update(self, *args):
pass
class Feld(Widget):
weg = StringProperty('hinterg.png')
def __init__(self, *args, **kwargs):
super(Feld, self).__init__(*args, **kwargs)
Clock.schedule_interval(self.Update, 1/60.)
def Update(self, *args):
pass
def on_touch_down(self, touch):
if self.ids.my_image.collide_point(*touch.pos):
self.weg = self.parent.kr
class TTT(App):
def build(self):
Window.clearcolor = (0, 1, 1, 1)
return kv_string
if __name__ == "__main__":
TTT().run()
Your root widget is kv_string, which is None since the variable is the result of Builder.load_string on a string that doesn't contain a root widget.
Related
i made a gui with python kivy with a starting screen that has a button in it which generates new screens when pressed, and it also generates new buttons so its easier to pick the screen i need to get in focus but i can't get it working properly cause i can't generate buttons on the newly made screens
when they aren't in focus, the script only generates buttons for the screen in focus it seems
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
class ScreenManagement(ScreenManager):
def __init__(self, **kwargs):
super(ScreenManagement, self).__init__(**kwargs)
sm = ScreenManagement(transition=FadeTransition())
class newtab(Screen):
def __init__(self, **kwargs):
super(newtab, self).__init__(**kwargs)
self.bt1=(Button(text="New tab", size_hint =(.1, .1) ,pos_hint ={'center_x':.1, 'center_y':.94}))
self.add_widget(self.bt1)
self.bt1.bind(on_release=self.new_tab)
self.txt1 = TextInput(text='',size_hint =(.1, .1), pos_hint ={'center_x':.2, 'center_y':.75}, multiline=True)
self.add_widget(self.txt1)
def transition(self, instance):
self.manager.current = (instance.text)
for item in sm.screen_names:
self.bt2=(Button(text=item, size_hint =(.1, .1) ,pos_hint ={'center_x':(.1*sm.screen_names.index(item)+.1), 'center_y':.84}))
self.add_widget(self.bt2)
self.bt2.bind(on_release=self.transition)
def new_tab(self, *args):
n = len(self.manager.screen_names) ##number of screens+1
screen = newtab(name="screen {}".format(n)) #make new screen and give it number
self.manager.add_widget(screen)
self.manager.current = "screen 0"
for item in sm.screen_names:
self.bt2=(Button(text=item, size_hint =(.1, .1) ,pos_hint ={'center_x':(.1*sm.screen_names.index(item)+.1), 'center_y':.84}))
self.add_widget(self.bt2)
self.bt2.bind(on_release=self.transition)
sm.add_widget(newtab(name='screen 0'))
class Application(App):
def build(self):
return sm
if __name__ == "__main__":
Application().run()
I suppose that below is what you expected to get. But I agree with #ApuCoder that you may look for TabbedPanel functionality.
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
class ScreenManagement(ScreenManager):
def __init__(self, **kwargs):
super(ScreenManagement, self).__init__(**kwargs)
sm = ScreenManagement(transition=FadeTransition())
class newtab(Screen):
def __init__(self, **kwargs):
super(newtab, self).__init__(**kwargs)
self.bt1 = (Button(text="New tab", size_hint=(.1, .1), pos_hint={'center_x': .1, 'center_y': .94}))
self.add_widget(self.bt1)
self.bt1.bind(on_release=self.new_tab)
self.txt1 = TextInput(text='', size_hint=(.1, .1), pos_hint={'center_x': .2, 'center_y': .75}, multiline=True)
self.add_widget(self.txt1)
# add all present screen buttons to newly created screen
for i, screen in enumerate(sm.screens):
self.bt2 = (Button(text=screen.name, size_hint=(.1, .1), pos_hint={'center_x': (.1 * i + .1), 'center_y': .84}))
self.add_widget(self.bt2)
self.bt2.bind(on_release=self.transition)
# add this newly created screen button to all screens
for screen in sm.screens + [self]:
screen.bt2 = (Button(text=self.name, size_hint=(.1, .1), pos_hint={'center_x': (.1 * len(sm.screens) + .1), 'center_y': .84}))
screen.add_widget(screen.bt2)
screen.bt2.bind(on_release=screen.transition)
def transition(self, instance):
self.manager.current = instance.text
def new_tab(self, *args):
n = len(self.manager.screen_names) ##number of screens+1
screen = newtab(name="screen {}".format(n)) # make new screen and give it number
self.manager.add_widget(screen)
self.manager.current = "screen 0"
sm.add_widget(newtab(name='screen 0'))
class Application(App):
def build(self):
return sm
if __name__ == "__main__":
Application().run()
I am building an app using Kivy and would like to draw a circle to the middle of a Widget as soon as the app starts up. I found how to run code on start in this question (How do I run a function once the form is loaded Kivy). However, as a comment points out, widths and heights are not initialized yet when calling on_start(). Does anyone know how I could do this?
I have the following code. Using this, the circle is drawn at position 50, 50, while I would like it in the middle of the Field widget.
main.py:
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle, Color, Ellipse
from kivy.lang import Builder
from kivy.properties import ObjectProperty
class Field(Widget):
def __init__(self, **kwargs):
super(Field, self).__init__(**kwargs)
def init_circle(self):
with self.canvas:
Color(1,1,1,1, mode='rgba')
r = 20
self.circle = Ellipse(pos=(self.width//2 - r, self.height//2 - r), size=(2*r, 2*r))
print(self.circle)
def move_circle(self):
cx, cy = self.circle.pos
self.circle.pos = (cx + 10, cy)
class RootClass(Widget):
field = ObjectProperty(None)
def __init__(self, **kwargs):
super(RootClass, self).__init__(**kwargs)
class MyMainApp(App):
def build(self):
self.r = RootClass()
return self.r
def on_start(self, **kwargs):
self.r.field.init_circle()
if __name__ == '__main__':
Builder.load_file("my.kv")
MyMainApp().run()
my.kv:
<RootClass>
field: field_id
BoxLayout:
size: root.size
orientation: "vertical"
Field:
id: field_id
size_hint: 1, 0.9
Button:
size_hint: 1, 0.1
text: "Move"
on_press:
root.field.move_circle()
Method 1:
Using bind by binding to a callback method,
class Field(Widget):
def __init__(self, **kwargs):
super(Field, self).__init__(**kwargs)
# Bind a callback method, say here 'init_circle' to the prop. 'size' and 'pos'
# so that whenever those prop. change the method will be called.
# To prevent redrawing you may use method 'clear' or some other strategy.
self.bind(size = self.init_circle, pos = self.init_circle)
def init_circle(self, *args):
with self.canvas:
...
Method 2:
Using Clock by scheduling the process,
def on_start(self, **kwargs):
Clock.schedule_once(self.r.field.init_circle)
I want to create a list of coloured labels. The thing is that I could do it with the kv file, but I need to do it through the build() method. So I tried replicate what I have done, but it does not work. And I can't understand why.
This is what I've coded
from kivy.app import App
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.graphics import *
class RL(RelativeLayout): # Creates the background colour for each label
def __init__(self, **kwargs):
super().__init__(**kwargs)
with self.canvas:
Color(.7, 0, .5, 1)
Rectangle(size_hint=self.size)
class MainMenu(BoxLayout):
N_LBLS = 8
labels_text = []
RL_list = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
button = Button(text='do something')
button.bind(on_release=self.change_text)
box = BoxLayout(orientation='vertical', padding= 10, spacing = 15)
for i in range(0, self.N_LBLS):
self.RL_list.append(RL())
self.labels_text.append(Label(text=f'{i}º label', size_hint=self.size))
self.RL_list[i].add_widget(self.labels_text[i])
box.add_widget(self.RL_list[i])
self.add_widget(button)
self.add_widget(box)
def change_text(self, instance):
for lbl in self.labels_text:
if lbl.text[0] == '5':
lbl.text = 'Text changed'
class MainApp(App):
def build(self):
return MainMenu()
if __name__ == '__main__':
MainApp().run()
It's supposed to make a button to the left, and a list of 8 coloured labels to the right.
The problem is that you are setting size_hint=self.size in each Label. The self.size is the size of the MainMenu, which is [100,100] when that code is executed. Note that size_hint is a multiplier that is applied to the parents size to calculate the widgets size. So a size_hint of [100,100] makes each Label 100 times bigger than the MainMenu. So your code is working, but the Labels are so large that the text is off the screen. Start by just removing size_hint=self.size.
And, to set a background color on a Label, you can just use the canvas of that Label, rather than some container. Here is a version of your code that does that:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
class ColorLabel(Label):
pass
Builder.load_string('''
<ColorLabel>:
bg_color: [.7, 0, .5, 1]
canvas.before:
Color:
rgba: self.bg_color
Rectangle:
pos: self.pos
size: self.size
''')
class MainMenu(BoxLayout):
N_LBLS = 8
labels_text = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
button = Button(text='do something')
button.bind(on_release=self.change_text)
box = BoxLayout(orientation='vertical', padding=10, spacing=15)
for i in range(0, self.N_LBLS):
self.labels_text.append(ColorLabel(text=f'{i}º label'))
box.add_widget(self.labels_text[i])
self.add_widget(button)
self.add_widget(box)
def change_text(self, instance):
for lbl in self.labels_text:
if lbl.text[0] == '5':
lbl.text = 'Text changed'
lbl.bg_color = [0, 1, 0, 1]
class MainApp(App):
def build(self):
return MainMenu()
if __name__ == '__main__':
MainApp().run()
While I run this script rippleexample2.py with rippleexample2.kv, the buttons should have ripple effects upon pressing, but it doesn't work.
I know the RippleButton class is working fine in ctmbtn.py, when a button is pressed here, there is ripple effect. I don't know what is wrong here. Perhaps binding function?
rippleexample2.py
from kivy.app import App
from kivy.uix.touchripple import TouchRippleBehavior
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
from kivy.properties import (StringProperty, NumericProperty, ObjectProperty, ListProperty, DictProperty, BooleanProperty)
class RippleButton(TouchRippleBehavior, Button):
isRippled = BooleanProperty(False)
def __init__(self, **kwargs):
super(RippleButton, self).__init__(**kwargs)
def on_touch_down(self, touch):
collide_point = self.collide_point(touch.x, touch.y)
if collide_point and not self.isRippled:
self.isRippled = True
self.ripple_show(touch)
return super(RippleButton, self).on_touch_down(touch)
def on_touch_up(self, touch):
collide_point = self.collide_point(touch.x, touch.y)
if collide_point and self.isRippled:
self.isRippled = False
self.ripple_fade()
return super(RippleButton, self).on_touch_up(touch)
class Login(Screen):
pass
class MainScreen(Screen):
pass
class ScreenManager(ScreenManager):
pass
Login = Builder.load_file("rippleexample2.kv")
class SimpleKivy4(App):
def build(self):
return Login
if __name__ == "__main__":
SimpleKivy4().run()
rippleexample2.kv
ScreenManager:
Login:
MainScreen:
<Login>:
name:"login"
RippleButton:
text:'Login'
font_size: 24
size_hint: (.4,.25)
on_release: app.root.current = "main"
<MainScreen>:
name: "main"
Button:
text: 'back'
on_release: app.root.current = "login"
ctmbtn.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import (StringProperty, NumericProperty, ObjectProperty,
ListProperty, DictProperty, BooleanProperty)
from kivy.uix.touchripple import TouchRippleBehavior
class RippleButton(TouchRippleBehavior, Button):
isRippled = BooleanProperty(False)
def __init__(self, **kwargs):
super(RippleButton, self).__init__(**kwargs)
def on_touch_down(self, touch):
collide_point = self.collide_point(touch.x, touch.y)
if collide_point and not self.isRippled:
self.isRippled = True
self.ripple_show(touch)
return super(RippleButton, self).on_touch_down(touch)
def on_touch_up(self, touch):
collide_point = self.collide_point(touch.x, touch.y)
if collide_point and self.isRippled:
self.isRippled = False
self.ripple_fade()
return super(RippleButton, self).on_touch_up(touch)
class RootWidget(BoxLayout):
def __init__(self, **kwargs):
super(RootWidget, self).__init__(**kwargs)
self.add_widget(RippleButton(text='btn 1'))
cb = CustomBtn()
cb.bind(pressed=self.btn_pressed)
self.add_widget(cb)
self.add_widget(RippleButton(text='btn 2'))
def btn_pressed(self, instance, pos):
print ('pos: printed from root widget: {pos}'.format(pos=pos))
class CustomBtn(Widget):
pressed = ListProperty([0, 0])
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
self.pressed = touch.pos
# we consumed the touch. return False here to propagate
# the touch further to the children.
return True
return super(CustomBtn, self).on_touch_down(touch)
def on_pressed(self, instance, pos):
print ('pressed at {pos}'.format(pos=pos))
class TestApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
TestApp().run()
The Touch Ripple code is still experimental, and it is not visible when using it in kv language for Button widget.
Actually, the Button touch ripple animation on interaction works when using kv language. It is just not visible because the background_color (r, g, b, a) of Button widget defaults to [1, 1, 1, 1] or grey color with no transparency. Whereby 'a' (alpha compositing or transparency) is 1 i.e. not transparent.
The ripple_color defaults to [1., 1., 1., .5] or grey color with half transparency.
Solution
The temporary work around is to change the alpha compositing or transparency of Button widget to 0.5.
Please refer to my example for Kivy RippleButton
I want to place image on button. Button's size depends on size of screen. I want to select ideal image's size according to button's size. Buttons are defined in class ButtonTools. With my knowledges I cann obtain size only in fuction of class ButtonTools. How do I obtain size of screen?
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.image import Image
class Paint(Widget):
pass
class ButtonTools(BoxLayout):
def __init__(self, **kwargs):
super(ButtonTools, self).__init__(**kwargs)
self.size_hint = (1, None)
self.height = 50
but = Button(on_press = self.DrawAbscissa)
but.background_normal = 'abscissa.png'
self.add_widget(but)
but = Button( on_press = self.DrawCurve)
but.background_normal ='curve.png'
self.add_widget(but)
def DrawAbscissa(self, obj):
size = self.parent.size
p=1
pass
def DrawCurve(self, obj):
pass
class WorkShop(BoxLayout):
def __init__(self, **kwargs):
super(WorkShop, self).__init__(**kwargs)
self.orientation = "vertical"
self.paint = Paint()
self.tools = ButtonTools()
self.add_widget(self.paint)
self.add_widget(self.tools)
class MyPaintApp(App):
def build(self):
return WorkShop()
if __name__ == '__main__':
MyPaintApp().run()
You can obtain the screen size using the following:
from kivy.core.window import Window
...
print("Window.size={}".format(Window.size))
print("Window.height={}".format(Window.height))
print("Window.width={}".format(Window.width))