Related
I'm making a stopwatch using Kivy that includes "Minutes, seconds, milliseconds".
But the problem is that the Clock method doesn't seem to support the shorter time below 0.02. So no matter what numbers you give, it still can not catch up with the milliseconds' change and counting time slower than the real-time (You can see it clearly in my code below).
So is there any better way to update my Kivy's GUI by milliseconds, so I can display the numbers on my app's screen?
Here's the code I extracted from my main source:
from kivy.uix.relativelayout import RelativeLayout
from kivy.lang import Builder
from kivy.clock import Clock
from functools import partial
Builder.load_string("""
<ModifyTime>:
id: name
BoxLayout:
id: time
spacing: 1
size_hint_y: None
height: minute.height
TextInput:
id: minute
hint_text: "Minutes"
size_hint: None,None
width: time_scale.texture_size[0] + dp(14)
height: time_scale.texture_size[1] + dp(12)
multiline: False
TextInput:
id: second
hint_text: "Seconds"
size_hint: None,None
width: time_scale.texture_size[0] + dp(14)
height: time_scale.texture_size[1] + dp(12)
multiline: False
TextInput:
id:milisecond
hint_text: "Miliseconds"
size_hint: None,None
width: time_scale.texture_size[0] + dp(14)
height: time_scale.texture_size[1] + dp(12)
multiline: False
Button:
text: "Activate stopwatch"
on_release:
root.activate_stopwatch(root)
Label:
id: time_scale
opacity: 0
text: "00:00:00"
size_hint: None,None
size: 0,0
""")
class ModifyTime(RelativeLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
def activate_stopwatch(self,watch):
watch.ids.minute.text="0"
watch.ids.second.text="0"
watch.ids.milisecond.text="0"
self.stopwatch=Clock.schedule_interval(partial(self._time_update,watch),0.001)
def _time_update(self,watch,dt):
watch.ids.milisecond.text=str(int(watch.ids.milisecond.text)+1)
self._time_update_calculate(watch)
def _time_update_calculate(self,watch):
if watch.ids.milisecond.text == "1000":
watch.ids.milisecond.text = "0"
watch.ids.second.text=str(int(watch.ids.second.text)+1)
if watch.ids.second.text == "60":
watch.ids.second.text == "0"
watch.ids.minute.text=str(int(watch.ids.minute.text)+1)
if __name__ == "__main__":
from kivy.app import App
class TestApp(App):
def build(self):
return ModifyTime()
TestApp().run()
I modified your .py code to the following which should fairly count upto milliseconds (should work without any changes in config).
class ModifyTime(RelativeLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.milisec = 0
def activate_stopwatch(self,watch):
watch.ids.minute.text="0"
watch.ids.second.text="0"
watch.ids.milisecond.text="0"
self.stopwatch=Clock.schedule_interval(partial(self._time_update,watch),0.001)
def _time_update(self,watch,dt):
self.milisec += 1000*dt
sec, mili = divmod(self.milisec, 1000)
mint, sec = divmod(sec, 60)
watch.ids.milisecond.text = str(int(mili))
watch.ids.second.text = str(int(sec))
watch.ids.minute.text = str(int(mint))
if __name__ == "__main__":
from kivy.app import App
class TestApp(App):
def build(self):
return ModifyTime()
TestApp().run()
So, I wanted to create an app that created tasks/habits in kivy. Basically, there would be a screen that shows you your tasks/habits, and you could click on a button to take you to another screen to create a new task/habit. You would input information about the task/habit in that screen, and click the submit button. The submit button would add the task/habit to the first screen. However, when I run my code, it works the first time. I can go to the second screen, add my habit/code and then go back to the first screen. However, when I click the button on the first screen, it gives me an error.
The error being: kivy.uix.screenmanager.ScreenManagerException: No Screen with name "second".
Here is my code:
main.py:
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.image import Image
from PIL import Image
import numpy as np
class FirstWindow(Screen):
def add_label(self, name, time, date):
label = Label(text= f' {name}, {time}, {date}')
self.ids.boxlayout.add_widget(label)
class SecondWindow(Screen):
a_task = False
a_habit = False
name = ""
time = ""
date = ""
def task_button(self):
self.a_task = True
self.a_habit = False
def habit_button(self):
self.a_habit = True
self.a_task = False
def submit_button(self):
self.name = self.ids.the_name.text
self.time = f'{self.ids.time_spinner_1.text}:{self.ids.time_spinner_2.text}'
self.date = f'{self.ids.date_spinner_1.text}/{self.ids.date_spinner_2.text}'
def get_name(self):
return self.name
def get_time(self):
return self.time
def get_date(self):
return self.date
kv = Builder.load_file('Button.kv')
class ButtonApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(FirstWindow())
sm.add_widget(SecondWindow())
return sm
if __name__ == '__main__':
ButtonApp().run()
Button.kv:
<FirstWindow>:
name: "first"
BoxLayout:
id: boxlayout
orientation: "vertical"
canvas.before:
Color:
rgba: (0.3,0.33,0.3,1)
Rectangle:
pos: self.pos
size: self.size
FloatLayout:
Label:
text: "To Do List"
font_size: 32
pos_hint: { 'right': 0.65, 'top': 1.4 }
Button:
text: "add a task"
on_release: root.manager.current = "second"
<SecondWindow>:
name: "second"
BoxLayout:
orientation: "vertical"
spacing: "10dp"
canvas.before:
Color:
rgba: (0.3,0.33,0.3,1)
Rectangle:
pos: self.pos
size: self.size
FloatLayout:
Label:
text: "Add A Task"
font_size: 32
pos_hint: { 'right': 0.65, 'top': 1.2 }
BoxLayout:
orientation: "horizontal"
Button:
text: "task"
on_press: root.task_button()
Button:
text: "habit"
on_press: root.habit_button()
TextInput:
id: the_name
text: "Name"
Label:
text: "alert"
BoxLayout:
orientation: "horizontal"
Spinner:
id: time_spinner_1
text: "00"
values: ['1','2','3','4','5','6','7','8','9','10','11', '12']
Spinner:
id: time_spinner_2
text: "00"
values: ['01','02','03','04','05','06','07','08','09','10','11', '12', '13','14','15','16','17','18','19','20','21','22','23','24','25','26','27','28','29','30','31','32','33','34','35','36','37','38','39','40','41','42','43','44','45','46','47','48','49','50','51','52','53','54','55','56','57','58','59']
BoxLayout:
orientation: "horizontal"
Spinner:
id: date_spinner_1
text: "00"
values: ['1','2','3','4','5','6','7','8','9','10','11', '12']
Spinner:
id: date_spinner_2
text: "00"
values: ['01','02','03','04','05','06','07','08','09','10','11', '12', '13','14','15','16','17','18','19','20','21','22','23','24','25','26','27','28','29','30','31']
Button:
text: "submit"
on_press: root.submit_button()
on_release:
root.manager.current = "first"
root.manager.get_screen("first").add_label(root.get_name(), root.get_time(), root.get_date())
Your code:
def submit_button(self):
self.name = self.ids.the_name.text
self.time = f'{self.ids.time_spinner_1.text}:{self.ids.time_spinner_2.text}'
self.date = f'{self.ids.date_spinner_1.text}/{self.ids.date_spinner_2.text}'
changes the name property of the SecondWindow Screen, so trying to change Screen to the second will no longer work. It looks like you are trying to use name for some other purpose. I suggest you use a variable with a different name.
I have started building a simple app using Kivy (and am in the process of moving over to KivyMD for aesthetic purposes) and have come across an issue where all of the elements I'm rendering to screen are rendering twice: The first time they are static and uninteractable and the second time are the interactable ones over the top. Im working with some ScrollViews with Buttons in them and when I scroll the buttons underneath are visible. There is also Labels with text that I update, these still show the default text underneath them.
screenshot of double rendering elements
main.py
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.scrollview import ScrollView
from kivy.uix.togglebutton import ToggleButton
from kivy.properties import NumericProperty, ObjectProperty
class MagnateCalcApp(App):
def build(self):
return Builder.load_file("magnatecalc.kv")
class CalcWindow(Screen):
current_value = NumericProperty(100)
current_price = NumericProperty(1)
pricelabel = ObjectProperty(None)
valuelabel = ObjectProperty(None)
money = ObjectProperty(None)
def update(self):
if self.current_price != 1:
val = float(self.current_price)/1000
self.pricelabel.text = "Price: " + str(val) + "M"
if self.current_value != 100:
self.valuelabel.text = "Multiplier: " + str(self.current_value)
if self.current_price != 1 and self.current_value != 100:
val = (float(self.current_value) * float(self.current_price))/1000
print(val)
self.money.text = "Value: " + str(val) + "M"
class RentWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
if __name__ == "__main__":
MagnateCalcApp().run()
magnatecalc.kv (usually has quite a lot of buttons in the ScrollView but the majority of these have been removed for readability)
WindowManager:
CalcWindow:
RentWindow:
<Button>:
font_size:35
size_hint:0.5,0.5
<CalcWindow>:
name: "Calc"
current_value: 100
current_price: 1
pricelabel: pricelabel
valuelabel: valuelabel
money: money
GridLayout:
cols:2
size: root.width, root.height
Label:
text:""
pos_hint:{"x":0, "top":1}
size_hint_y:0.15
size_hint_x:0.05
ScrollView:
do_scroll_y:False
do_scroll_x:True
pos_hint:{"x":1, "top":1}
size_hint_y:0.15
GridLayout: # here i want a scrollview
id: price
rows: 1
size_hint_x: None
width: self.minimum_width
ToggleButton:
text:"300K"
size_hint_x: None
on_press:
root.current_price = 300
root.update()
group:"price"
ToggleButton:
text:"400K"
size_hint_x: None
on_press:
root.current_price = 400
root.update()
group:"price"
ToggleButton:
text:"500K"
size_hint_x: None
on_press:
root.current_price = 500
root.update()
group:"price"
ToggleButton:
text:"600K"
size_hint_x: None
on_press:
root.current_price = 600
root.update()
group:"price"
ToggleButton:
text:"700K"
size_hint_x: None
on_press:
root.current_price = 700
root.update()
group:"price"
ToggleButton:
text:"800K"
size_hint_x: None
on_press:
root.current_price = 800
root.update()
group:"price"
ScrollView:
do_scroll_y:True
do_scroll_x:False
pos_hint:{"x":0, "top":0}
size_hint_x:0.075
GridLayout: # here i want a scrollview
id: multiplier
cols: 1
size_hint_y: None
height: self.minimum_height
ToggleButton:
text:"2"
size_hint_y: None
on_press:
root.current_value = 2
root.update()
group:"multiplier"
ToggleButton:
text:"3"
size_hint_y: None
on_press:
root.current_value = 3
root.update()
group:"multiplier"
ToggleButton:
text:"4"
size_hint_y: None
on_press:
root.current_value = 4
root.update()
group:"multiplier"
ToggleButton:
text:"5"
size_hint_y: None
on_press:
root.current_value = 5
root.update()
group:"multiplier"
ToggleButton:
text:"6"
size_hint_y: None
on_press:
root.current_value = 6
root.update()
group:"multiplier"
ToggleButton:
text:"7"
size_hint_y: None
on_press:
root.current_value = 7
root.update()
group:"multiplier"
GridLayout:
rows:2
pos_hint:{"x":1, "top":0}
GridLayout:
cols:2
Label:
id:pricelabel
text:"Price: "
font_size:50
Label:
id:valuelabel
text:"Multiplier: "
font_size:50
GridLayout:
cols:2
Label:
id:money
text:"Value: "
font_size:90
size_hint_x:1
pos_hint:{"x":0, "top":0}
Button:
text:"<- Rent"
pos_hint:{"x":1, "top":0}
size_hint: 0.25,0.25
on_release:
app.root.current = "Rent"
root.manager.transition.direction="right"
<RentWindow>:
name: "Rent"
Button:
text:"Go Back ->"
on_release:
app.root.current="Calc"
root.manager.transition.direction="left"
Is there anything obvious that I am missing in my .py that would cause this? This issue appeared when I started switching over to KivyMD but I have since reverted those changes to try and get back to the original functionality and I can't so have clearly broken something along the way.
Your kv file is being loaded twice, once explicitly by you with Builder.load_file and once automatically because it has the same name as your app class.
The simplest solution is to skip the manual load and just delete your build method.
I had the same problem as you, and I figured out how to build correctly the app to stop duplicate the items.
In kivymd this is the correct way to build the app:
class MagnateCalcApp(MDApp):
def build(self):
return super().build()
I don't know how but it recognises the correct kivy file.
I had this same thing occur after I was using pyinstaller to create a distribution. The build and dist directories created by the pyinstaller process contained copies of my .kv files. This was causing all the visual elements to appear multiple times depending on how many copies of the files were present in other directories.
Moving or deleting these directories solved the issue for me.
Textfile should look like this :
e.g
Walking 61.0/2018-09-04 79.0/2018-10-04
Running 24.0/2018-09-04 33.0/2018-10-04
The point of this function is to append the textfile with the new value of the slider or to change one.
There are some names for the slider and each of them can have many values which consist of a slider.value and the current date. If today is not the date of the last value - then we append the file.
If today is the same date as the date of the last value - then it is supposed to change it(I did not do it yet, but this is not a problem, i will do it myself after I solve this problem).
Here is the full Python file and Kivy files, nothing but def save_values matters. Everything else is just to make the program working for you.
Python
from kivy.app import App
import time
import datetime
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.lang import Builder
from kivy.uix.slider import Slider
from kivy.uix.widget import Widget
from kivy.uix.textinput import TextInput
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.config import Config
screen_width = 450
screen_height = 800
Config.set("graphics", "resizable","1")
Config.set("graphics", "width", screen_width)
Config.set("graphics", "height", screen_height)
languages = ["Reading","Writing","Running","Climbing"]
spawned_List = ["False"]
class MenuScreen(Screen):
pass
class JobChoiceScreen(Screen):
def changing_screen(self, instance, *args):
self.manager.current= "sliderScreen"
screenSlider = self.manager.get_screen('sliderScreen')
text = str(instance.text)
screenSlider.changing_label(text)
def on_pre_enter(self, *args):
if spawned_List[0] == "False":
for x in range(0,len(languages)):
word = languages[x]
word = str(word)
btn = Button(text = word, size_hint=(None, None), size = (140,70),
font_size = 18, bold = True, color = [0,1,1,1], background_color = (.156, .172, .24, 0.7),
on_release = self.changing_screen)
self.ids.container.add_widget(btn)
spawned_List[0] = "True"
self.ids.boxLayBottom.add_widget(Widget(size_hint=(1, .4)))
self.ids.datesContainer.add_widget(Button(text = "Day back", size_hint=(.28, 1), font_size = 18, bold = True, color = [0,1,1,1], background_color = (.176, .192, .44, 0.7)))
self.ids.datesContainer.add_widget(Widget(size_hint=(.44, 1)))
self.ids.datesContainer.add_widget(Button(text = "Day forward", size_hint=(.28, 1), font_size = 18, bold = True, color = [0,1,1,1], background_color = (.176, .192, .44, 0.7)))
class SliderScreen(Screen):
def save_values(self, *args, **kwargs):
date = (datetime.datetime.now().strftime("%y-%m-%d"))
written = (str(self.ids.my_slider.value)+ "/" + date + " ")
print("started save_values")
with open('values.txt', 'r') as fileValues:
lines = fileValues.readlines()
print("opened the file")
with open('values.txt', 'a') as fileValues:
for i, line in enumerate(lines):
if line.startswith(self.ids.my_label.text) and line.find(date) == -1:
line = line + ((self.ids.my_label.text) + " " + written)
print(line)
if not line.startswith(self.ids.my_label.text) and line.find(date) == -1:
line = (" " + written)
print(line)
print("hello bill")
def changing_label(self, text):
self.ids.my_label.text = text
class ScreenManagement(ScreenManager):
pass
presentation = Builder.load_file("manager.kv")
class MainApp(App):
def build(self):
return presentation
if __name__ == "__main__":
MainApp().run()
Kivy
ScreenManagement:
#transition: FadeTransition()
MenuScreen:
JobChoiceScreen:
SliderScreen:
<MenuScreen>:
canvas:
Rectangle:
source:"background.jpg"
pos: self.pos
size: self.size
name: "menu"
BoxLayout:
padding: [10,10,10,10]
orientation: "vertical"
Widget:
size_hint: [1,0.2]
BoxLayout:
Button:
bold: True
color: [0,1,1,1]
on_release: app.root.current = "list_of_jobs"
text: "Change"
size_hint: [0.28,1]
font_size: 20
background_color: (.156, .172, .24, 0.7)
Widget:
size_hint: [0.44,1]
Button:
bold: True
color: [.5,0, .8, .7]
text: "View \n Progress"
size_hint: [0.28,1]
font_size: 20
halign: "center"
valign: "center"
background_color: (.156, .172, .24, 0.7)
on_release: app.root.current = "sliderScreen"
Widget:
size_hint: [1,0.2]
<JobChoiceScreen>:
canvas:
Rectangle:
source:"background.jpg"
pos: self.pos
size: self.size
name: "list_of_jobs"
BoxLayout:
orientation: "vertical"
padding: [10,10,10,10]
BoxLayout:
orientation: "vertical"
id: boxLayBottom
size_hint: (1,.1)
BoxLayout:
id: datesContainer
orientation: "horizontal"
size_hint: (1,.6)
AnchorLayout:
anchor_x: "center"
acnhor_y: "top"
size_hint: (1,.8)
GridLayout:
id: container
cols: 3
spacing: 5
BoxLayout:
orientation: "vertical"
id: boxContainer
size_hint: (1,.1)
Button:
text: "Back to Menu"
on_release: app.root.current = "menu"
bold: True
color: [0,1,1,1]
background_color: (.176, .192, .44, 0.7)
<SliderScreen>:
canvas:
Rectangle:
source:"background.jpg"
pos: self.pos
size: self.size
name: "sliderScreen"
BoxLayout:
padding: [10,10,10,10]
orientation: "vertical"
id: my_label_container
Slider:
id: my_slider
min: 0
max: 100
value: 0
orientation: "vertical"
size_hint: [1, 0.7]
step: 1
Label:
id: my_label
size_hint: [1, 0.2]
bold: True
font_size: 40
text: ""
Button:
size_hint: [1, 0.1]
bold: True
on_release: app.root.current = "menu"
text : "Back Home"
font_size: 20
halign: "center"
valign: "center"
background_color: (.156, .172, .24, 0.7)
on_press: root.save_values()
def save_values
def save_values(self, *args, **kwargs):
date = (datetime.datetime.now().strftime("%y-%m-%d"))
written = (str(self.ids.my_slider.value)+ "/" + date + " ")
print("started save_values")
with open('values.txt', 'r') as fileValues:
lines = fileValues.readlines()
print("opened the file")
with open('values.txt', 'a') as fileValues:
for i, line in enumerate(lines):
if line.startswith(self.ids.my_label.text) and line.find(date) == -1:
line = line + ((self.ids.my_label.text) + " " + written)
print(line)
if not line.startswith(self.ids.my_label.text) and line.find(date) == -1:
line = (" " + written)
print(line)
print("hello bill")
I see two problems with your code:
In your save_values() method, You are opening the values.txt file twice at the same time with the same name, but with different modes. I think you need to unindent the second with open block, remove the second with open statement, and modify the mode in the first with open statement to r+. So your code should look something like:
with open('values.txt', 'r+') as fileValues:
lines = fileValues.readlines()
print("opened the file")
for i, line in enumerate(lines):
You never call any write routine, so nothing gets written. When you want to append line/lines to values.txt, you need to call fileValues.write() or fileValues.writelines().
I have not looked at your code logic, so I will not comment on that.
I am trying to display the value of a slider on a different screen. i have tried this(below code) but for some reason, the value doesn't seem to show up. the code runs fine but no value is returned. Thanks for your help :) Cheers.
temperature screen
here is a snippet of the python code:
class Thermostat(Screen):
label = StringProperty()
def display(self):
tempVal = self.label
return str(tempVal)
and the kv files:
<Thermostat>:
name: "thermostat"
BoxLayout:
orientation: 'horizontal'
cols: 2
Label:
id: label
font_size: "11sp"
text: "INSIDE: " + root.display()
Label:
text: "More Info"
font_size: "11sp"
kv file 2:This screen holds the true value from the slider, i am trying to pass that value to the Thermostat screen.
<Temperature>:
BoxLayout:
size_hint_y: None
height: '48dp'
cols: 3
Label:
text: 'THERMOSTAT'
Slider:
id: temp
min: 40
max: 100
value: 1
step: 1
on_value: app.root.get_screen('thermostat').label = str('{}'.format(temp.value))
Label:
id: slide_val
text: '{}'.format(temp.value)
root.display is only called once, at the beginning of the program. For it to work well, every time you change the value of the slider root.display should be called.
However, it is very simple to do this using propertis in kv languaje:
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.lang.builder import Builder
Builder.load_string('''
<Manager>:
id: manager
Thermostat:
id: thermostat
name: 'thermostat'
manager: 'screen_manager'
temp: temperature.temp #<<<<<<<<<<<<
Temperature:
id: temperature
name: 'temperature'
manager: 'screen_manager'
<Thermostat>:
temp: 0 #<<<<<<<<<<<<
BoxLayout:
orientation: 'horizontal'
cols: 3
Label:
id: label
font_size: "11sp"
text: "INSIDE: {}".format(root.temp) #<<<<<<<<<<<<
Label:
text: "More Info"
font_size: "11sp"
Button:
text: ">"
on_release: app.root.current= "temperature"
size_hint_x: None
width: 30
<Temperature>:
temp: temp_slider.value #<<<<<<<<<<<<
BoxLayout:
cols: 4
Button:
text: "<"
on_press: app.root.current = "thermostat"
size_hint_x: None
width: 30
Label:
text: 'THERMOSTAT'
Slider:
id: temp_slider
min: 40
max: 100
value: 40
step: 1
Label:
id: slide_val
text: str(root.temp)
''')
class Thermostat(Screen):
pass
class Temperature(Screen):
pass
class Manager(ScreenManager):
pass
class ExampleApp(App):
def build(self):
return Manager()
if __name__ == "__main__":
ExampleApp().run()
If you want to use the slider's value in your class Temperature, simply declare the property in the class:
from kivy.properties import NumericProperty
class Temperature(Screen):
temp = NumericProperty()
def __init__(self, **kwargs):
super(Temperature, self).__init__(**kwargs)