Can someone please show me how to correctly update the color of text in a label/button in Kivy with Python?
I want to be able to change the color, the user guide suggests markup language but I've tried and just can't get it to work. Ultimately I want to change the color of the text at different times of day - I can code that bit its just the changing of the text color outside of Kivy language?
I've extracted the following from my code as the relevant (I hope!) bits that i need the guidance on ..
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.properties import ObjectProperty, StringProperty
from kivy.clock import Clock
import time
from datetime import datetime, timedelta
Builder.load_string('''
<MainScreen>:
name: 'main'
the_time: _id_lbl_time
BoxLayout:
orientation: 'vertical'
Label:
id: _id_lbl_time
text: 'Time'
font_size: 120
''')
class MainScreen(Screen):
def update_time(self, sec):
MyTime = time.strftime("%H:%M:%S")
self.the_time.text = MyTime <--- UPDATE COLOR HERE?
class ScreenManagerApp(App):
def build(self):
self.main_screen = MainScreen()
return self.main_screen
def on_start(self):
Clock.schedule_interval(self.main_screen.update_time, 1)
#===========================================================
# run the App !
ScreenManagerApp().run()
I changed your code a bit, to make it change to a random color.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.properties import ObjectProperty, StringProperty, ListProperty
from kivy.clock import Clock
import time
from datetime import datetime, timedelta
from random import random
Builder.load_string('''
<MainScreen>:
name: 'main'
the_time: _id_lbl_time
BoxLayout:
orientation: 'vertical'
Label:
color: root.color
id: _id_lbl_time
text: 'Time'
font_size: 120
''')
class MainScreen(Screen):
color = ListProperty([1,1,1,1])
def __init__(self,**kwargs):
super(MainScreen,self).__init__(**kwargs)
Clock.schedule_interval(self.update_time, 1)
def update_time(self, sec):
MyTime = time.strftime("%H:%M:%S")
self.the_time.text = MyTime
self.color = [random(), random(), random(), 1]
class ScreenManagerApp(App):
def build(self):
self.main_screen = MainScreen()
return self.main_screen
ScreenManagerApp().run()
Related
So I want to change the color of an label in kivy if some variable is bigger than 22 I tried everything, this is how it should work.
like if ZZ is bigger than 22 the label color is red and if it is lower the label color is green, in the code example i clearfy it as an str with the number 40
Here my code hope you can help me.
import kivy
import requests
import json
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivy.properties import ListProperty
Builder.load_string("""
<MySec>:
rgba1: (1,.2,.2,.2)
GridLayout:
cols: 1
size: root.width,root.height
GridLayout:
Label:
id: kv_sec1
text: root.string1
font_size: 30
canvas.before:
Color:
rgba: root.rgba1
""")
class MySec(BoxLayout):
string1 = StringProperty('')
class MyApp(App):
def build(self):
Clock.schedule_interval(lambda dt: self.update_time(), 0)
return MySec()
def update_time(self):
global ZZ
ZZ = 40 #"minimal reproducible example"
self.root.string1 = str(ZZ)
def Temp(self, instance):
ZZ = int(self.ZZ)
if ZZ > 22:
self.rgba4 = [0.,1.,0.,1.] #gruen
else:
self.rgba4 = [1.,0.,0.,1.] #root
if __name__ == '__main__':
MyApp().run()
If I understood your problem properly then you want to change the text colour (color) depending on some value ( of ZZ perhaps).
To achieve that, first create a NumericProperty (assuming ZZ takes only number) for ZZ in MyApp. With that you will be able to use it everywhere in your code.
Now if the label's text is just this value of ZZ set it as text: str(app.ZZ). Now bind its color prop. depending on this value. One way of doing that would be simply like color: [0.,1.,0.,1.] if app.ZZ > 22 else [1.,0.,0.,1.].
With some more changes your code should now look like,
import kivy
import requests
import json
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import (
StringProperty,
NumericProperty,
ListProperty
)
Builder.load_string("""
<MySec>:
rgba1: (1,.2,.2,.2)
GridLayout:
cols: 1
size: root.width,root.height
GridLayout:
Label:
id: kv_sec1
text: str(app.ZZ)
font_size: 30
color: [0.,1.,0.,1.] if app.ZZ > 22 else [1.,0.,0.,1.]
""")
class MySec(BoxLayout):
string1 = StringProperty('')
class MyApp(App):
ZZ = NumericProperty(1)
def build(self):
Clock.schedule_interval(self.update_time, 0.5)
return MySec()
def update_time(self, *args):
self.ZZ *= -2
def Temp(self, instance):
ZZ = int(self.ZZ)
if ZZ > 22:
self.rgba4 = [0.,1.,0.,1.] #gruen
else:
self.rgba4 = [1.,0.,0.,1.] #root
if __name__ == '__main__':
MyApp().run()
Note:
Here, method Temp has not been used. There are definitely other ways to accomplish that though.
I am working on a mobile app and I want to create multiple of scoll items by using for loop but whenever I use for loop, gives me some error It has already parent widget. how can I make list of MDCard?
My App:
Click here
My Code:
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.screenmanager import Screen, ScreenManager
from kivymd.uix.boxlayout import BoxLayout
from kivy.uix.image import AsyncImage
from kivymd.uix.card import MDCard
from kivymd.uix.label import MDLabel
Window.size = (450, 740)
kv = '''
ScreenManager:
Main:
<main>:
name: 'main'
video_list: video_list
BoxLayout:
orientation: 'vertical'
MDToolbar:
title: 'Video downloader'
ScrollView:
Screen:
id: video_list
'''
class Main(Screen):
pass
sm = ScreenManager()
sm.add_widget(Main(name='main'))
class Ytube(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.theme_cls.colors = 'Red'
self.theme_cls.primary_palette = "Red"
self.root = Builder.load_string(kv)
image = AsyncImage(
source='https://static.hub.91mobiles.com/wp-content/uploads/2020/05/How-to-download-youtube-videos.jpg', size_hint=(1, .7), )
screen_id = self.root.get_screen('main').ids.video_list
for i in range(1):
card = MDCard(orientation='vertical', pos_hint={
'center_x': .5, 'center_y': .7}, size_hint=(.9, .4))
card.add_widget(image)
card.add_widget(MDLabel(
text='Phishing attacks are SCARY easy to do!! (let me show you!)', size_hint=(.6, .2), ))
screen_id.add_widget(card)
def build(self):
return self.root
if __name__ == "__main__":
Ytube().run()
Is there any way to make it
look like this.
The problem is that you are trying to use the same image widget for every MDCard. Any widget can only have one parent. You can only use that image widget once. You can fix that by moving the creation of the image widget inside the loop.
Also, a BoxLayout is a better choice for the child of a ScrollView, since it has a minimum_height property that you can use. Here is a modified version of your code that applies both those suggestions:
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.image import AsyncImage
from kivymd.uix.card import MDCard
from kivymd.uix.label import MDLabel
Window.size = (450, 740)
kv = '''
ScreenManager:
Main:
<main>:
name: 'main'
video_list: video_list
BoxLayout:
orientation: 'vertical'
MDToolbar:
title: 'Video downloader'
ScrollView:
do_scroll_x: False
BoxLayout:
id: video_list
orientation: 'vertical'
size_hint_y: None
height: self.minimum_height
'''
class Main(Screen):
pass
sm = ScreenManager()
sm.add_widget(Main(name='main'))
class Ytube(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.theme_cls.colors = 'Red'
self.theme_cls.primary_palette = "Red"
self.root = Builder.load_string(kv)
screen_id = self.root.get_screen('main').ids.video_list
for i in range(5):
# Make a new image widget for each MDCard
image = AsyncImage(
source='https://static.hub.91mobiles.com/wp-content/uploads/2020/05/How-to-download-youtube-videos.jpg', size_hint=(1, .7), )
card = MDCard(orientation='vertical', pos_hint={
'center_x': .5, 'center_y': .7}, size_hint=(.9, None), height=200)
card.add_widget(image)
card.add_widget(MDLabel(
text='Phishing attacks are SCARY easy to do!! (let me show you!)', size_hint=(.6, .2), ))
screen_id.add_widget(card)
def build(self):
return self.root
if __name__ == "__main__":
Ytube().run()
The following code permanently crashes with "Segmentation fault (core dumped)".
It seems that setting the text in the TextInput makes the application crash.
Can anyone confirm the error?
What does the error message mean?
I use threading, because I wanted the user to perform some actions on the screen and the application should wait for the result before going on.
Edit:
If there was something like a "goto" statement (o.k. sorry for that ;-)) I would like to jump to the line with comment # further commands and proceed there with the main flow.
import sys
import threading
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import StringProperty, ObjectProperty, BooleanProperty
Builder.load_string("""
<MyWordRecorderScreen>:
id: TheWordRecorderScreen
my_text_input: TheTextInput
my_record_button: TheRecordButton
BoxLayout:
orientation: 'vertical'
spacing: 10
MyTextInput:
id: TheTextInput
my_screen: TheWordRecorderScreen
# removing the following statement makes the app work but useless
text: "Example"
RecordButton:
id: TheRecordButton
text: "Record"
my_screen: TheWordRecorderScreen
size_hint: (0.2, 0.15)
""")
class MyTextInput(TextInput):
pass
class RecordButton(Button):
my_screen = ObjectProperty(None)
class MyWordRecorderScreen(Screen):
my_record_button = ObjectProperty(None)
my_text_input = ObjectProperty(None)
class MyScreenSequence():
def __init__(self,my_screenmanager):
self.sm=my_screenmanager
def do_job(self):
self.CurrentScreen = MyWordRecorderScreen()
self.sm.switch_to(self.CurrentScreen)
def ApplicationFlow(MyScreenManager, *largs):
sm = MyScreenManager
screen_terminated=threading.Condition()
screen_terminated.acquire()
# subroutine will use screen_terminated.notify_all() once result is available
MSS=MyScreenSequence(my_screenmanager=sm)
t2 = threading.Thread(target=MSS.do_job)
t2.daemon = False
t2.start()
screen_terminated.wait()
screen_terminated.release()
# get result from subroutine
# futher commands
return
class TestApp(App):
def build(self):
self.sm = ScreenManager()
return self.sm
def on_start(self):
t1 = threading.Thread(target=ApplicationFlow, args=[self.sm])
t1.daemon = False
t1.start()
def main(args):
TestApp().run()
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))
I find it simpler to use Thread.Event(). Here is my version of your code that uses Kivy.clock:
import sys
import threading
import time
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import StringProperty, ObjectProperty, BooleanProperty
from functools import partial
Builder.load_string("""
<MyWordRecorderScreen>:
id: TheWordRecorderScreen
my_text_input: TheTextInput
my_record_button: TheRecordButton
BoxLayout:
orientation: 'vertical'
spacing: 10
MyTextInput:
id: TheTextInput
my_screen: TheWordRecorderScreen
# removing the following statement makes the app work but useless
text: "Example"
RecordButton:
id: TheRecordButton
text: "Record"
my_screen: TheWordRecorderScreen
size_hint: (0.2, 0.15)
""")
class MyTextInput(TextInput):
pass
class RecordButton(Button):
my_screen = ObjectProperty(None)
class MyWordRecorderScreen(Screen):
my_record_button = ObjectProperty(None)
my_text_input = ObjectProperty(None)
class MyScreenSequence():
def __init__(self,my_screenmanager):
self.sm=my_screenmanager
def do_job(self, theEvent, dt):
self.CurrentScreen = MyWordRecorderScreen()
self.sm.switch_to(self.CurrentScreen)
theEvent.set()
def ApplicationFlow(MyScreenManager, *largs):
sm = MyScreenManager
screen_terminated=threading.Event()
# subroutine will use screen_terminated.set() once result is available
MSS=MyScreenSequence(my_screenmanager=sm)
Clock.schedule_once(partial(MSS.do_job, screen_terminated), 0)
screen_terminated.wait()
# get result from subroutine
# futher commands
return
class TestApp(App):
def build(self):
self.sm = ScreenManager()
return self.sm
def on_start(self):
t1 = threading.Thread(target=ApplicationFlow, args=[self.sm])
t1.daemon = False
t1.start()
def main(args):
TestApp().run()
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))
I'm having difficulty figuring out how to change the text of a label within a kivy widget. For simplicity, I have a label set to 0 and I would like to change the text to read 30 in this example. However, I get the following error.
AttributeError: 'super' object has no attribute 'getattr'
I understand that I'm probably not properly targeting that widget and I am hoping someone can please explain how to specifically reference the text of this label (self.ids.mainel1temp.stuff_r.text = '30') to update (with more detail than fixing the code)
#!/usr/bin/kivy
import kivy
from random import random
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.switch import Switch
from kivy.uix.label import Label
from kivy.config import Config
Config.set('graphics', 'width', '800')
Config.set('graphics', 'height', '480')
Builder.load_string("""
<Menuscreen>:
#Handling the gesture event.
ScreenManager:
id: manager
Screen:
id: main_screen
name:'main_screen'
stuff_r: mainel1temp
FloatLayout:
Label:
id: mainel1temp
size: self.texture_size
text:'0'
size_hint: None, None
text_size: 75,75
pos: 295,308
font_size:'20sp'
halign: 'center'
""")
class Thermostuff(Screen):
stuff_r = ObjectProperty(None)
def starttherm(self):
Clock.schedule_interval((self.read_temp), 1)
def read_temp(self, dt):
self.ids.mainel1temp.stuff_r.text = '30'
Thermrun = Thermostuff()
Thermrun.starttherm()
class MenuScreen(Screen):
pass
sm = ScreenManager()
menu_screen = MenuScreen(name='menu')
sm.add_widget(menu_screen)
class TestApp(App):
def build(self):
return sm
if __name__ == '__main__':
TestApp().run()
You do a couple of things wrong here.
You dont want to put a ScreenManager inside a Screen
Only one ScreenManager is needed.
Then you can start the Clock in the __init__ of the Thermostuff(Screen)
Or if you want it to initiate on_enter you need to overrite that. In that case you might want to check somehow, if its allready started, so you wont have multiple clocks running.
Then when you create an ObjectProperty you dont need self.ids, because you allready created that property. So self.stuff_r is now the label.
I rewrote your example a bit, to demonstrate this.
Try this:
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 = """
#Handling the gesture event.
ScreenManager:
id: manager
MenuScreen:
Button:
text: "Go to Thermostuff"
on_release:
root.current = "main_screen"
Thermostuff:
name:'main_screen'
stuff_r: mainel1temp
FloatLayout:
Label:
id: mainel1temp
size: self.texture_size
text:'0'
size_hint: None, None
text_size: 75,75
pos: 295,308
font_size:'20sp'
halign: 'center'
"""
class Thermostuff(Screen):
stuff_r = ObjectProperty(None)
test_temp = 0
def __init__(self,**kwargs):
super(Thermostuff,self).__init__(**kwargs)
Clock.schedule_interval((self.read_temp), 1)
def read_temp(self, dt):
self.test_temp += 1
self.stuff_r.text = str(self.test_temp)
class MenuScreen(Screen):
pass
class TestApp(App):
def build(self):
return Builder.load_string(sm)
if __name__ == '__main__':
TestApp().run()
I am trying to code up a program using the TabbedPanel class. I am going to use ScreenManager and navbuttons to navigate different screens within each tab. Problem is nothing is showing up. At first at least the NavLayout was showing up but now all that appears are the tabs with their respective names. How do I get both Navigation buttons and Screen to show up in the tab.
ex1.kv
<MainPanel>:
id: mp
usb_tab_wid: usb_tab
remote_tab_wid: remote_tab
sd_tab_wid: sd_tab
size_hint: 1,1
tab_width: 65
do_default_tab: False
tab_pos: 'top_right'
TabbedPanelItem:
id: usb_tab
text: 'Usb'
BoxLayout:
orientation: 'vertical'
UsbScreenManager:
NavLayout:
TabbedPanelItem:
id: remote_tab
text: 'Remote'
TabbedPanelItem:
id: sd_tab
text: 'SD'
<NavLayout>:
id: grid1
spacing: 5,5
rows: 1
size_hint: 1, .2
LeftButton:
id: left_arrow_button
Image:
source: 'left_arrow_iconb.png'
center: left_arrow_button.center
size: grid1.width*.8, grid1.height*.8
BackButton:
id: back_button
Image:
source: 'back_iconb.png'
center: back_button.center
size: grid1.width*.8, grid1.height*.8
RightButton:
id: right_arrow_button
Image:
source: 'right_arrow_iconb.png'
center: right_arrow_button.center
size: grid1.width*.8, grid1.height*.8
usb.kv
<UsbScreenManager>:
id: usb_screen_manager
H2HScreen:
name: 'h2h'
V9validationScreen:
name: 'v9'
<H2HScreen>:
name: 'h2h'
BoxLayout:
TestIcon:
source: 'h2h.png'
<V9validationScreen>:
name: 'v9'
BoxLayout:
Label:
text: 'V9 tests'
ex1.py
import kivy
kivy.require('1.9.0')
import subprocess
import csv
from datetime import datetime
import usb.core
import usb.util
import threading
import time
from functools import partial
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.properties import ObjectProperty, StringProperty, NumericProperty
from kivy.clock import Clock
from kivy.uix.image import Image
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.uix.boxlayout import BoxLayout
class MainPanel(TabbedPanel):
usb_tab_wid = ObjectProperty()
remote_tab_wid = ObjectProperty()
sd_tab_wid = ObjectProperty()
usbman = ObjectProperty(None)
def switch_to(self, header):
pass
def print_wd(self):
for widget in self.walk():
print("[]->{}".format(widget, widget.id))
def on_current_tab(self, b, c):
print(self.current_tab)
#return super(MainPanel, self).on_current_tab()
#print(usb_tab_wid)
pass
class TestIcon(ButtonBehavior, Image):
pass
class UsbScreenManager(ScreenManager):
pass
class H2HScreen(Screen):
pass
class V9validationScreen(Screen):
pass
class RemoteScreenManager(ScreenManager):
pass
class SDcardScreenManager(ScreenManager):
pass
class NavLayout(GridLayout):
pass
class LeftButton(Button):
sm = ObjectProperty()
mp = MainPanel()
ct= mp.current_tab
#print (ct)
pass
class RightButton(Button):
sm = ObjectProperty()
panel = ObjectProperty()
pass
class BackButton(Button):
sm = ObjectProperty()
panel = ObjectProperty()
def active_panel():
#if current tab is __ then load corresponding screen manager
pass
pass
class Sam1App(App):
def build(self):
return MainPanel()
Builder.load_file("usb.kv")
if __name__ == '__main__':
Sam1App().run()
I found I overrode the switch_to method for TabbedPanel but did not define anything. So the TabbedPanel did not know what to switch to so it displayed nothing.