Kivy - Label not updating on UI - python

When I click on the buttons in the left part, the label "Top 10 Plays of 2015" should be changed with the text of the button that I've clicked. I can see the variable changing text but the label is not changing text.
this is my work: when I click the buttons on left side the title in the middle should change:
python
class MainApp(Screen, EventDispatcher):
vid = StringProperty("Videos/Top 10 Plays of 2015.mp4")
title = StringProperty("Top 10 Plays of 2015")
def __init__(self,*args,**kwargs):
super(MainApp,self).__init__(*args, **kwargs)
pass
class OtherVideos(BoxLayout, EventDispatcher):
root = MainApp
def __init__(self, *args, **kwargs):
super(OtherVideos,self).__init__(*args, **kwargs)
self.loadVideos()
def loadVideos(self):
con = MongoClient()
db = con.nba
vids = db.videos.find()
vidnum = 1
for filename in vids:
myid = "vid" + str(vidnum)
getfilename = filename['filename']
button = Button(id=myid,
text=getfilename,
color=[0,0.7,1],
bold=1)
button.bind(on_release=lambda x:(self.change_Title(getfilename), self.change_Vid(getfilename)))
self.add_widget(button)
vidnum += 1
def change_Title(self, title):
self.root.title = title
def change_Vid(self, myfilename):
con = MongoClient()
db = con.nba
vids = db.videos.find()
for filename in vids:
file = os.path.basename(filename['video_path'])
if myfilename == filename['filename']:
self.root.vid = filename['video_path']
break
pass
kivy
BoxLayout:
orientation: 'vertical'
Label:
id: lblTitle
text: root.title
size_hint_y: None
height: 40
font_size: 25
bold: 1
canvas.before:
Color:
rgba: 1, 0, 0, 0.7
Rectangle:
pos: self.pos
size: self.size
VideoPlayer:
id: activeVid
source: root.vid
state: 'play'
when I'm printing root.title it's changing its text but the label is not changing which it should because it's getting its text from that variable.
But when I'm putting the change_title() method in __init__ of OtherVideos like this, the label is changing:
class OtherVideos(BoxLayout, EventDispatcher):
root = MainApp
def __init__(self, *args, **kwargs):
super(OtherVideos,self).__init__(*args, **kwargs)
self.change_Title("my title")
This is already taking me days of work, anyone can help?

There are several problems with your code:
Every widget inherits from EventDispatcher, so inheriting from it the second time is pointless.
This root = MainApp is not a reference to the MainApp (object), but to its class.
You don't need to specify a StringProperty to achieve your goal.
The easiest way to do this is to add a reference of the label you want to change in the function change_Title:
def change_Title(self, title):
# self.root.title = title
self.ids.my_label_to_change.text = title

Related

How to make changes to global object attribute with Kivy TextInput and have it save for later use?

My app currently has 10 "players" with a name, offensive rating, and defensive rating. I want to give the user the opportunity to change the names of the players before running my game simulation code, but it appears I can only temporarily change the name inside of the current screen. Here's my current python code for when the user inputs the name from the gui:
class SecondWindow(Screen):
def pg1_name_val(self, widget):
pg1.name = widget.text
If I code in a print(pg1.name) right below that, it will print the correct name, but when the user hits "play" to shift to a results screen, the names go back to the default names.
How do I make pg1.name from my SecondWindow(Screen) become the global pg1.name? Is there a simple line of code I can use to return pg1.name as the global variable?
EDIT ----
So I've came a long way with my app this week, but now that I want to have a separate "results" screen, I've realized that I lost all of my data from the previous screen. It looks like all of the changes I make to my "players" are going back to defaults once the screen changes. I'll paste some code below for reference. Basically, my question now is this: Is there a way to keep the objects from one screen and return them back as the global instance of the object. Not just the local instance of the object.
.py -
class Player():
def __init__(self,name,off,deff):
self.name = name
self.off = off
self.deff = deff
self.stats = 0
class Team():
def init(self,pg, sg, sf, pf, c):
self.pg = pg
self.sg = sg
self.sf = sf
self.pf = pf
self.c = c
self.score = 0
self.results = None
self.to = 0
On the screen where I'm creating my team, I have a button to run the simulation. This button runs the sim correctly, records all of the game results in a string, prints all of the results to the console, attributes, and score correctly. The button also changes the screen to where I want to display a scroll view of the results string, but the results are empty.
I'd really appreciate any help on this, as I'm at a roadblock. Thanks!
It would kinda look the same. Depending on how exactly you wanna keep the 10 different players.
class Player():
def __init__(self, name, off, deff):
self.name = name
self.off = off
self.deff = deff
self.stats = 0
player = [Player('', 0, 0)] * 10
class SecondWindow(Screen):
def __init__(self, **kwargs):
super(SecondWindow, self).__init__(**kwargs)
self.player = player
def name_change(self):
self.player[0].name = self.ids.name.text
player[0].name = self.player[0].name
def next_screen(self):
self.manager.current = 'result'
class ResultWindow(Screen):
def __init__(self, **kwargs):
super(ResultWindow, self).__init__(**kwargs)
def on_enter(self, *args):
print(player[0].name)
class Manager(ScreenManager):
pass
class Player(App):
def build(self):
sm = ScreenManager()
sm.add_widget(SecondWindow(name='second'))
sm.add_widget(ResultWindow(name='result'))
return sm
if __name__ == '__main__':
Player().run()
You would have to work with OOP, here I created a list of you player object for all ten players it is an easy way to keep the code short.
You might like to keep them as 10 different objects the good part of a list it is easy to iterate through in a for loop and then get all the data for a stats board on a screen and also easy to expand later if you need 20 players or more.
I didn't change anything in the .kv file.
.kv
<SecondWindow>:
TextInput:
id: name
hint_text: 'Player name'
size: 260, 32
pos: (root.width / 2) - (self.width / 2), 248
multiline: False
on_text_validate: root.name_change()
Button:
id: create_btn
text: 'Next Screen'
size: 260, 32
pos: (root.width / 2) - (self.width / 2), 206
color: utils.get_color_from_hex('#ffffff')
background_color: utils.get_color_from_hex('#016bf8')
on_press: root.next_screen()
<ResultWindow>:
Label:
id: name
The on_text_validate only works with multiline false and when enter is pressed if you don't want to press enter user on_text instead.
--Edit--
I did some research this morning and thought maybe to update this a bit with my findings.
You could instead of doing it the import to all screens do it in the screenmanager and just call it from the screen like this:
pyhon.py
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
class Player:
def __init__(self, name, off, deff):
self.name = name
self.off = off
self.deff = deff
self.stats = 0
class SecondWindow(Screen):
def __init__(self, **kwargs):
super(SecondWindow, self).__init__(**kwargs)
def name_change(self):
self.manager.player.name = self.ids.name.text
def next_screen(self):
self.manager.current = 'result'
class ResultWindow(Screen):
def __init__(self, **kwargs):
super(ResultWindow, self).__init__(**kwargs)
def on_enter(self):
self.ids.name.text = self.manager.player.name
class Manager(ScreenManager):
player = Player('', 0, 0)
def __init__(self, **kwargs):
super(Manager, self).__init__(**kwargs)
class PlayerApp(App):
def build(self):
return Builder.load_file('test.kv')
if __name__ == '__main__':
PlayerApp().run()
the kv file would look something like this:
.kv
#:import utils kivy.utils
Manager:
SecondWindow:
name: 'second'
ResultWindow:
name: 'result'
<SecondWindow>:
TextInput:
id: name
hint_text: 'Player name'
size: 260, 32
size_hint: None, None
pos: (root.width / 2) - (self.width / 2), 248
multiline: False
on_text_validate: root.name_change()
Button:
id: create_btn
text: 'Next Screen'
size_hint: None, None
size: 260, 32
pos: 0, 0
color: utils.get_color_from_hex('#ffffff')
background_color: utils.get_color_from_hex('#016bf8')
on_press: root.next_screen()
<ResultWindow>:
Label:
id: name
hope it helps

Why won't my Kivy program update the font size when I tell it to?

I'm making a choose your own adventure game, but sometimes I need to change the font size and Kivy isn't giving me the results I'm expecting. This is the full code so feel free to run it and see what I mean.
Here is the python file:
# A Choose your own adventure game
import kivy
kivy.require('1.11.1')
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.lang import Builder
global root
root = BoxLayout() #If I don't define root immediately the program won't work
#root is given a proper definition in class Main()
#Easily add new pages to the program
def add_page(pagenum):
root.clear_widgets()
root.add_widget(pagenum)
#The main window that encapsulates all other widgets
class RootBoxLayout(BoxLayout):
def __init__(self, **kwargs):
super(RootBoxLayout, self).__init__(**kwargs)
# The Menu that drives the game
class Menu(BoxLayout):
def __init__(self, **kwargs):
super(Menu, self).__init__(**kwargs)
# The Main Menu
class StartMenu(Menu):
def __init__(self, **kwargs):
super(Menu, self).__init__(**kwargs)
#Text Box
self.ids.textbox.text = "Opening Screen"
# Button 1
self.ids.button1.text = "Play"
self.ids.button1.bind(on_press = self.nextpage1)
def nextpage1(self, *args):
add_page(HappyBee())
class HappyBee(Menu):
def __init__(self, **kwargs):
super(Menu, self).__init__(**kwargs)
#############################################
### This is where the problem seems to be ###
#############################################
self.ids.textbox.font_size = self.ids.textbox.height/10 #Kivy says nah I don't feel like doing this
self.ids.textbox.text = "This is a very large block of text that I would like " \
"to decrease the font size of. Pressing the button below changes it but I don't " \
"want users to have to press a button just to get the game to function " \
"how it should function from the start."
# Button 1
self.ids.button1.text = "y tho"
self.ids.button1.bind(on_press = self.nextpage1)
# What to do when each button is pressed
def nextpage1(self, *args):
self.ids.textbox.font_size = self.ids.textbox.height/10 # Kivy says ok I can change it now lol
# An App class that will be used to umbrella everything else in the application
class Main(App):
def build(self):
Builder.load_file("cyoa.kv")
global root # Other classes and functions need to easily access root
root = RootBoxLayout()
first_screen = StartMenu()
add_page(first_screen) # Add the Main Menu to the root window
return root
if __name__ == '__main__':
Main().run()
and here is the corresponding kv file, which I have saved as cyoa.kv
<RootBoxLayout>:
orientation: 'vertical'
# Create the background color of the root layout
canvas.before:
Color:
rgba: 0,0,0,1 # black
Rectangle:
pos: self.pos
size: self.size
# This custom button allows to font size to change dynamically with the window
<MyButton#Button>:
font_size: self.height/3
halign: 'center'
valign: 'center'
text_size: self.size
size_hint_y: 0.14
<Menu>:
BoxLayout:
orientation: 'vertical'
Label:
id: textbox
font_size: self.height/6
text_size: self.size # Allows text to wrap
halign: 'center'
valign: 'center'
size_hint_y: 0.6
MyButton:
id: button1
text: 'Play'
I can change font_size in __init__ only if I remove font_size from .kv. It seems it gets value from .kv after running __init__ and this makes problem. There is also other problem: height (and width) in __init__ is 100 instead of expected size. Probably it calculates it after running __init__.
Searching in internet I found on Reddit: How can I use init for screen ids?
It uses Clock to run some function after all updates and in this function change values.
def __init__(self, **kwargs):
#super(...)
Clock.schedule_once(self._do_setup)
def _do_setup(self, *l):
self.ids.something = '....'
In your code it would be
from kivy.clock import Clock # <---
class HappyBee(Menu):
def __init__(self, **kwargs):
super(Menu, self).__init__(**kwargs)
self.ids.textbox.text = "This is a very large block of text that I would like " \
"to decrease the font size of. Pressing the button below changes it but I don't " \
"want users to have to press a button just to get the game to function " \
"how it should function from the start."
self.ids.button1.text = "y tho"
self.ids.button1.bind(on_press = self.nextpage1)
Clock.schedule_once(self.on_init_complete) # <---
def on_init_complete(self, *args, **kwargs):
self.ids.textbox.font_size = self.ids.textbox.height/10 # <---
It works but has one small problem - it display text in original size for few milliseconds. But if you don't know this then you may not notice this.
EDIT: Similar problem: How to Load Kivy IDs Before Class Method is Initialized (Python with Kivy)

How to add new widget in GridLayout without resizing older widgets?

I have added GridLayout to ScrollView, and I'm adding widgets in GridLayout dynamically from python program.Instead of using more space of window it's resizing height of older widgets.What am i doing wrong here?
I tried putting BoxLayout inside GridLayout but it's not working.
I also tried to add widgets directly to ScrollView but i found out ScrollView only supports one widget.
My kv code:
<Downloading>:
my_grid: mygrid
GridLayout:
cols: 1
size_hint_y : None
hight: self.minimum_height
id: mygrid
My python code:
class Downloading(ScrollView):
set_text = ObjectProperty()
my_grid = ObjectProperty()
def __init__(self, select, link, path, username, password):
self.select = select
self.link = link
self.path = path
self.username = username
self.password = password
self.p_bar = []
self.stat = []
self.parent_conn, self.child_conn = Pipe()
p = Process(target=main, args=(self.child_conn, self.select,
self.link, self.path,
self.username, self.password))
p.start()
super().__init__()
self.event = Clock.schedule_interval(self.download_GUI, 0.1)
def newFile(self, title):
# self.newId = "stat" + str(len(self.p_bar) + 1)
self.stat.append(Label(text=''))
self.p_bar.append(ProgressBar())
self.my_grid.add_widget(Label(text=title))
self.my_grid.add_widget(self.stat[-1])
self.my_grid.add_widget(self.p_bar[-1])
def download_GUI(self, a):
temp = self.parent_conn.recv()
print(temp)
if temp == "new":
self.downloading = True
return
if self.downloading:
self.newFile(temp)
self.downloading = False
return
if type(temp) == type({}):
self.complete = temp['complete']
if not self.complete:
status = "{0}//{1} # {2} ETA: {3}".format(temp['dl_size'],
temp['total_size'],temp['speed'],temp['eta'])
self.stat[-1].text = status
self.p_bar[-1].value = temp['progress']
return
if temp == "end":
self.event.cancel()
Clock.schedule_once(exit, 3)
I believe you just need to set the height for each widget that you add to your GridLayout. For example:
self.my_grid.add_widget(Label(text=title, size_hint=(1, None), height=50))
You might need to do the same for the other widgets you are adding. The GridLayout may give the initially added widgets more space than that, but will not squeeze them any any tighter than your specified height.
After applying following changes to kv file
<Downloading>:
size: self.size
my_grid: mygrid
GridLayout:
cols: 1
size_hint_y : None
row_default_height: '25dp'
row_force_default: True
id: mygrid
And adding this line in the end of constructor (__init__), it is working as expected.
self.my_grid.bind(minimum_height=self.my_grid.setter('height'))

Updating Progress Bar displayed in Popup in Kivy

How do you update a progress bar that is displayed in Kivy. In the following example I get AttributeError: 'super' object has no attribute '__getattr__'. The issue is in the following line
self.ids.progress.value = value
I can see why since the progressBar widget is in <LoadingPopup> and not <MainScreen> but after trying several different things I can't reference the progressBar widget in <LoadingPopup> from the do_update method.
Thanks in advance
import threading
from functools import partial
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import StringProperty
from kivy.uix.spinner import Spinner
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.popup import Popup
Builder.load_string('''
<LoadingPopup>:
title: "Popup"
size_hint: None, None
size: 400, 400
auto_dismiss: False
BoxLayout:
orientation: "vertical"
ProgressBar:
id: progress
size_hint: (1.0, 0.06)
<MainScreen>:
BoxLayout:
orientation: 'vertical'
Spinner:
id: first
text: ' First Number'
values: ['1','2','3','4','5','6','7','8','9']
Spinner:
id: second
text: ' Second Number'
values: ['1','2','3','4','5','6','7','8','9']
Label:
id: result
text: ' Result'
color: 0,0,0,1
Button:
id: res
on_press: root.doit_in_thread(first.text,second.text)
text: 'Multiply'
''')
class LoadingPopup(Popup):
def __init__(self, obj, **kwargs):
super(LoadingPopup, self).__init__(**kwargs)
class MainScreen(FloatLayout):
changet = StringProperty()
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
def doit_in_thread(self, fir, sec):
popup = LoadingPopup(self)
popup.open()
threading.Thread(target=partial(self.onMul, fir, sec, popup)).start()
def do_update(self, value, text, *args):
self.ids.progress.value = value
self.ids.result.text = text
def onMul(self,fir,sec, popup):
a = (int(fir)*int(sec))
print(a)
b = 0
old_value = 0
endRange = 1000000
for i in range(endRange):
progress = int(((i+1)*100)/endRange)
if progress != old_value and progress % 5 == 0:
text = str(b*(int(fir)*int(sec)))
Clock.schedule_once(partial(self.do_update, progress, text))
old_value = progress
b+=1
popup.dismiss()
class TestApp(App):
def build(self):
return MainScreen()
if __name__ == "__main__":
TestApp().run()
One way to do it, is to keep a reference of the popup so you can address it later:
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
self.popup = LoadingPopup(self)
def doit_in_thread(self, fir, sec):
self.popup.open()
threading.Thread(target=partial(self.onMul, fir, sec, self.popup)).start()
def do_update(self, value, text, *args):
self.popup.ids.progress.value = value
self.ids.result.text = text
Python Code
Replace all references of popup with self.popup so that you can access it in the methods inside class MainScreen.
Replace self.ids.progress.value with self.popup.ids.progress.value
Since a = (int(fir)*int(sec), replace str(b*(int(fir)*int(sec))) with str(b * a)
Don't need to pass popup to onMul method, access it using self.popup
Snippet
class MainScreen(FloatLayout):
changet = StringProperty()
def doit_in_thread(self, fir, sec):
self.popup = LoadingPopup(self)
self.popup.open()
threading.Thread(target=partial(self.onMul, fir, sec)).start()
def do_update(self, value, text, *args):
self.popup.ids.progress.value = value
self.ids.result.text = text
def onMul(self, fir, sec):
a = (int(fir)*int(sec))
print(a)
b = 0
old_value = 0
endRange = 1000000
for i in range(endRange):
progress = int(((i+1)*100)/endRange)
if progress != old_value and progress % 5 == 0:
text = str(b*(int(fir)*int(sec)))
# text = str(b * a)
print("\ttext=", text)
Clock.schedule_once(partial(self.do_update, progress, text))
old_value = progress
b+=1
self.popup.dismiss()
kv file
The result is not visible because the default background colour of Label widget is black, and the text colour for result is also set to black, color: 0,0,0,1. Therefore, remove color: 0,0,0,1.
Snippet
Label:
id: result
text: ' Result'
Output

Kivy Popup or Eventloop Interaction?

The first screen of my app has a small menu (in a gridlayout) of three buttons. Two are supposed to open popups. One for Help and one for About.
The third one changes to another screen.
Only one popup works. The first one called (in the kivy file) works, the second doesn't open the popup. If I switch the order in cdd.kv, then the other one works.
Excerpt from cdd.kv:
CDDMainMenuLayout:
HelpButton:
size_hint: .5,.5
MetadataButton:
size_hint: .5,.5
on_release: app.root.current = 'metadata'
AboutButton:
size_hint: .5,.5
Excerpt from main.py:
class CDDMainMenuLayout(GridLayout):
"""
Provides the layout for the three buttons on the home screen.
"""
def __init__(self, *args, **kwargs):
super(CDDMainMenuLayout, self).__init__(*args, **kwargs)
self.rows = 1
self.cols = 3
self.size_hint = (.5,.5)
...
class CDDButton(Button):
def __init__(self, **kwargs):
super(CDDButton, self).__init__(**kwargs)
self.text = _('Button')
self.background_color = colors.grey2
class AboutButton(CDDButton):
def __init__(self, **kwargs):
super(AboutButton, self).__init__(**kwargs)
self.text = _("About the CDD")
self.background_color = colors.red1
a = Popup()
a.title = _("About Constraint Definition Designer, Version - " + __version__)
a.content = RstDocument(source='about.rst')
a.size_hint_x = .8
a.size_hint_y = .8
self.bind(on_release=a.open)
class HelpButton(CDDButton):
def __init__(self, **kwargs):
super(HelpButton, self).__init__(**kwargs)
self.text = _("Help")
self.background_color = colors.green1
h = Popup()
h.title = _("CDD Help")
h.content = RstDocument(source='help.rst')
h.size_hint_x = .8
h.size_hint_y = .8
self.bind(on_release=h.open)
Does anything change if you add extra lines self.popup = h and self.popup = a? One possibility is that your popups are simply being garbage collected since you don't store any references to them. I'm not sure if/how this would give your particular behaviour, but it's worth a try.

Categories

Resources