Kivy - Binding a Label's Text to a Variable in Another Class - python

I am trying to get the label’s text in “EventWindow” to update when the variable “current_text” in the Event class in “scripts/events.py”. I realize that the answer may lie in binding “current_text” to “ct”, but that just results in “AttributeError: class Event has no attribute ‘bind’”. If I am barking up the wrong tree with this solution, I would be very receptive of another method.
Below are the relevant code snippets, but the full project is available at: https://github.com/DinkWerks
main.py
from kivy.properties import ObjectProperty, StringProperty
from kivy.app import App
# Utility Imports
import yaml
# Game Imports
from scripts.events import Event
# File Loads
loc_file = file('data/location.yml')
loc_dat = yaml.load(loc_file)
# Bind Classes
event = Event()
class EventWindow(BoxLayout):
ct = StringProperty('')
def __init__(self, **kwargs):
super(EventWindow, self).__init__(**kwargs)
self.ct = event.current_text
# Error occurs below. Comment out too see semi-functional app.
Event.bind(current_text=self.setter('ct'))
scripts/events.py
from kivy.properties import StringProperty
...
from player import Player
from enemy import Enemy
class Event(Player):
open_events = file('data/event.yml', 'r')
event_file = yaml.load(open_events)
current_text = StringProperty('1234')
def __init__(self):
Player.__init__(self)
self.events = Event.event_file
self.selection = ''
self.current_text = '1234'
def event_name(self):
...
def event_selector(self, eid):
...
def parse(self):
driver = 1
variables = ('Name', 'Is_Person', 'Level', 'Gold')
poss_commands = ("[Next Slide]", "[Query]", "[Terminate]", "[Combat]")
while driver >= 0:
text = self.events[self.selection][driver]
lexer = shlex(text)
lexer.quotes = '/'
output = ''
command = ''
for token in lexer:
if token in variables:
output += str(eval('Player.' + token))
elif token.replace('/', '') in poss_commands:
command += token.replace('/', '')
else:
output += token.replace('/', '')
self.current_text = output
driver += self.controller(command)
self.modifier()
def modifier(self):
...
def controller(self, cmd):
...
text.kv
<EventWindow>:
BoxLayout:
pos: 100,100
size_hint: .4,.7
orientation: 'vertical'
Image:
source: 'maps/map.jpg'
pos: self.pos
size: self.size
ScrollView:
canvas.before:
Color:
rgba: [.2,.2,.2,.8]
Rectangle:
size: self.size
pos: self.pos
Label:
id: text_area
text: root.ct
padding: 15,10
text_size: self.width, None
size_hint_y: None
height: self.texture_size[1]
<Foo>:
id: bl
popup: popup.__self__
header: header
BoxLayout:
orientation: 'vertical'
BoxLayout:
...
FloatLayout:
id: mapspace
canvas:
...
EventWindow:
id: event
Popup:
...
Thanks!

In your code, Event is derived from Player, which itself is an old style class. Importantly, neither implement methods necessary for event management (e.g. fbind; for a stub class defining an interface see Observable). Most likely it is enough to make Event or Player a subclass of EventDispatcher to be able to create and bind to events kivy-style.

Related

How to schedule the kivy camera to read the texture continuously?

I have been trying to create a kivy camera scanner from a number of sources (I would use the zbarcam if I could, but the garden.xcamera module will not import, therefore I am trying to create something similar).
Problem
The problem is the camera does not read or update the texture continuously nor is there a way that I can find to capture frame-for-frame from the camera. This means I only get the texture on initialization.
Tried
Firstly, I have tried scheduling an event that will update the texture instance every 0.5 seconds. I could not get the texture instance of the camera because there is some delay in the camera to load, which caused an error.
Secondly, I created an on_texture event in my kv string, but it only reads the texture on initialization.
Thirdly, I tried binding the on_texture event a bit later in the python script, by creating a binding function and calling it as a scheduled event. It did not even get the instance.
Fourthly, I created_triggers and ask_update(callbacks) to the _on_texture() event, but again the script loads to fast before the camera can instantiate crashing the script.
Fifthly, I noticed there is a kivy.core.video module that contains a on_frame attribute. Did re-write my script to use it in conjunction with the kivy.uix.video module, but noticed that the video cannot run without first loading a video file.
Code
import kivy
import gi
kivy.require('1.11.1')
gi.require_version('Gst', '1.0')
from collections import namedtuple
from PIL import Image
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ListProperty, ObjectProperty
from kivy.uix.camera import Camera
import time
from gi.repository import Gst
import pyzbar.pyzbar
from kivy.uix.modalview import ModalView
Builder.load_string('''
#: import Window kivy.core.window.Window
<ScanPreview>:
auto_dismiss: False
size_hint_x: 0.6
size_hint_y: None
height: Window.height / 9
pos_hint: {'top':0.7, 'x': 0.1}
background_normal: ''
background_color: (1, 1, 1, 0)
background: 'white.png'
Label:
id: sc_data
text: 'See me...'
<ScanCamera>:
orientation: 'vertical'
The_Camera:
id: camera
resolution: root.resolution
on_texture: root._on_texture(camera)
ToggleButton:
text: 'Stop'
on_press: camera.play = not camera.play
size_hint_y: None
height: '48dp'
''')
class ScanPreview(ModalView):
pass
class The_Camera(Camera):
pass
class ScanCamera(BoxLayout):
resolution = ListProperty([640, 480])
symbols = ListProperty([])
code_types = ListProperty(set(pyzbar.pyzbar.ZBarSymbol))
cam_cam = ObjectProperty(The_Camera())
the_preview = ObjectProperty(ScanPreview())
Symb = namedtuple('Symb', ['type','data'])
def __init__(self, **kwargs):
super(ScanCamera, self).__init__(**kwargs)
self.cam_cam.play = True
def _on_texture(self, instance):
#source: https://github.com/kivy-garden/garden.zbarcam/blob/develop
#/zbarcam/zbarcam.py
print(instance)
if not instance.texture == None:
print(instance.texture)
self.symbols = self._detect_qrcode_frame(
texture=instance.texture, code_types=self.code_types)
def _detect_qrcode_frame(cls, texture, code_types):
image_data = texture.pixels
size = texture.size
#source: https://github.com/kivy-garden/garden.zbarcam/blob/develop
#/zbarcam/zbarcam.py
# Fix for mode mismatch between texture.colorfmt and data returned
#by
# texture.pixels. texture.pixels always returns RGBA, so that
#should
# be passed to PIL no matter what texture.colorfmt returns. refs:
# https://github.com/AndreMiras/garden.zbarcam/issues/41
pil_image = Image.frombytes(mode='RGBA', size=size,
data=image_data)
symbols = []
print(pil_image)
print(size)
print(texture.tex_coords)
print(texture.target)
codes = pyzbar.pyzbar.decode(pil_image, symbols=code_types)
for code in codes:
symbol = CameraClick.Symb(type=code.type, data=code.data)
symbols.append(symbol)
print(symbols)
return symbols
class TestCamera(App):
title = 'Scan Camera'
def build(self):
return ScanCamera()
def on_stop(self):
cc = The_Camera()
print('Stop')
cc.play = False
def on_pause(self):
return True
def on_resume(self):
pass
TestCamera().run()
Desired result
The camera's texture must continuously update, which will allow the pyzbar and PIL module to decode the texture?
I don't know if this is how its done, but as I answered my own question I am posting the answer and marking it as such.
Answer
So I managed to solve my question by using the example code here: https://kivy-fork.readthedocs.io/en/latest/_modules/kivy/uix/camera.html and mixing in some functions of zbarcam.
By using self.canvas.ask_update() in the on_texture call it updates the texture. I have added to the code and it now updates the texture continuously and prints the barcode to a togglebutton. At the moment I have just tested it on Ubuntu Bionic Beaver. Will test it on android this weekend.
Answer code
import kivy
import gi
kivy.require('1.11.1')
gi.require_version('Gst', '1.0')
from collections import namedtuple
from PIL import Image as Img
from kivy.app import App
from gi.repository import Gst
import pyzbar.pyzbar
from kivy.uix.image import Image
from kivy.core.camera import Camera as CoreCamera
from kivy.properties import NumericProperty, ListProperty, \
BooleanProperty, ObjectProperty
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.core.window import Window
Builder.load_string('''
#: import Window kivy.core.window.Window
<ScanCamera>:
<BarcWin>:
ActionBar:
pos_hint: {'top': 1, 'right': 1}
color: (1,1,1,1)
canvas.before:
Color:
rgba: (0,0,0,1)
Rectangle:
pos: self.pos
size: self.size
ActionView:
use_separator: True
ActionPrevious:
title: ''
with_previous: False
app_icon: ''
ActionButton:
color: (0,0,0,1)
background_normal: ''
Image:
source: 'gear_2.png'
center_y: self.parent.center_y
center_x: self.parent.center_x
size: self.parent.width /1.7, self.parent.height/ 1.7
allow_stretch: True
ActionButton:
color: (0,0,0,1)
size_hint_x: 0.09
background_normal: ''
Image:
source: 'dustbin_backgrnd_792521.png'
center_y: self.parent.center_y
center_x: self.parent.center_x
size: self.parent.width /1.7, self.parent.height/ 1.7
allow_stretch: True
ScanCamera:
pos_hint: {'top': 0.9, 'right': 1}
size_hint: [1, 0.8]
canvas.before:
PushMatrix
Rotate:
angle: 0
origin: self.center
canvas.after:
PopMatrix
Line:
width: 2.
rectangle: (self.x + 40, self.y + 40, self.width/1.1, self.height/1.12)
ToggleButton:
id: show_bcode
pos_hint: {'bottom': 1, 'right': 1}
size_hint: [1, 0.1]
color: (1,1,1,1)
background_color: (0,0,0,0)
background_normal: ''
canvas.before:
Color:
rgba: (.18,.36,.61,1) if self.state=='down' else (0,0,0,1)
Rectangle:
pos: self.pos
size: self.size
text: 'Hier kom die barcode...'
''')
class BarcWin(FloatLayout):
cam_cam = ObjectProperty(None)
def __init__(self, **kwargs):
super(BarcWin, self).__init__(**kwargs)
self.cam_cam = ScanCamera()
def accept_in(self):
print('In')
def accept_out(self):
print('Out')
class ScanCamera(Image):
play = BooleanProperty(True)
index = NumericProperty(-1)
resolution = ListProperty([Window.width, Window.height])
symbols = ListProperty([])
code_types = ListProperty(set(pyzbar.pyzbar.ZBarSymbol))
Symb = namedtuple('Symb', ['type','data'])
app_ini_ = ObjectProperty(None)
got_bcode = BooleanProperty(False)
def __init__(self, **kwargs):
self._camera = None
super(ScanCamera, self).__init__(**kwargs)
if self.index == -1:
self.index = 0
on_index = self._on_index
fbind = self.fbind
fbind('index', on_index)
fbind('resolution', on_index)
on_index()
self.app_ini_ = App.get_running_app()
def on_tex(self, *l):
self.canvas.ask_update()
if not self.texture == None:
self.symbols = self._detect_qrcode_frame(texture=self.texture, code_types=self.code_types)
if not self.symbols == []:
for s in self.symbols:
if s.data:
if s.data.decode('utf-8') != "":
self.app_ini_.root.ids.show_bcode.text = s.data.decode('utf-8')
def _on_index(self, *largs):
self._camera = None
if self.index < 0:
return
if self.resolution[0] < 0 or self.resolution[1] < 0:
return
#first init of corecamera object
self._camera = CoreCamera(index=self.index,
resolution=self.resolution, stopped=True)
#when camera loads call _camera_loaded method to bind corecamera method with uix.image texture
self._camera.bind(on_load=self._camera_loaded)
if self.play:
self._camera.start()
self._camera.bind(on_texture=self.on_tex)
def _camera_loaded(self, *largs):
#bind camera texture with uix.image texture that is still equal to None
self.texture = self._camera.texture
def on_play(self, instance, value):
if not self._camera:
return
if value:
self._camera.start()
else:
self._camera.stop()
def _detect_qrcode_frame(self, texture, code_types):
if not self.got_bcode:
image_data = texture.pixels
size = texture.size
# Fix for mode mismatch between texture.colorfmt and data returned by
# texture.pixels. texture.pixels always returns RGBA, so that should
# be passed to PIL no matter what texture.colorfmt returns. refs:
# https://github.com/AndreMiras/garden.zbarcam/issues/41
pil_image = Img.frombytes(mode='RGBA', size=size,
data=image_data)
bcode = []
codes = pyzbar.pyzbar.decode(pil_image, symbols=code_types)
#print(pil_image, type(pil_image), dir(pil_image))
if codes != []:
for code in codes:
symbol = self.Symb(type=code.type, data=code.data)
bcode.append(symbol)
return bcode
else:
self.got_bcode = False
return []
class TestCamera(App):
title = 'Scan Camera'
def build(self):
return BarcWin()
def on_stop(self):
cc = ScanCamera()
print('Stop')
cc._camera.stop()
def on_pause(self):
return True
def on_resume(self):
pass
TestCamera().run()

How to Periodically Update Parent (Screen) Class UI from Child (BoxLayout) Class (Python with Kivy)

Goal:
Periodic update of parent (screen) class / UI from child (boxlayout) class. Theconf2.dat is occasionally updated (from various other screens), and I want the UI to update every 5 seconds or so by re-running this class.
Latest code update:
In the __init__ function, I have Clock.schedule_interval(self.create_button, 1), which should cause the create_button function to rerun every second.
At the top of the create_button function, I have self.box_share.clear_widgets(), which should clear all the widgets so they can be repopulated (per the instructions outlined further down the create_button function).
Action:
I run the code
I navigate to NoTags screen by clicking the button with text title 'updating sequence'
I make changes to buttons that were dynamically created under scrollview by clicking on them. They successfully change color. This information is written to the conf2.dat file.
I navigate to SequenceScreen screen by first clicking 'home' button, then clicking 'sequence display' button. This SequenceScreen screen is the screen I wish to have updated to reflect the changes made to conf2.dat file.
Result:
UI associated withSequenceScreen(Screen) class still does not update per changes made from UI associated with NoTags(Screen) class.
However, when I restart the app altogether, I find the SequenceScreen UI successfully updated.
Suspicion:
I'm just one line of code away from getting this UI to update properly.
Python Code:
from kivy.app import App
# kivy.require("1.10.0")
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.textinput import TextInput
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.properties import NumericProperty
from kivy.clock import Clock
from kivy.uix.widget import Widget
from kivy.uix.scrollview import ScrollView
#from kivy.base import runTouchApp
from kivy.properties import StringProperty, ObjectProperty, NumericProperty
from kivy.storage.dictstore import DictStore
import pickle
import datetime, threading
import time
from kivy.clock import mainthread
class BackHomeWidget(Widget):
pass
class SequenceBoxLayout_NoEdits(BoxLayout):
box_share = ObjectProperty()
config_file = DictStore('conf2.dat')
def __init__(self, **kwargs):
super(SequenceBoxLayout_NoEdits, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_interval(self.create_button, 1)
def create_button(self, *args):
self.box_share.clear_widgets()
top_button_share = 1.1
color = (.4, .4, .4, 1)
for i in range(25):
top_button_share -= .4
id_ = "part" + str(i + 1)
if self.config_file.exists(id_):
btn_color = self.config_file[id_]["background_color"]
else:
self.config_file.put(id_, background_color=color)
btn_color = color
button_share = Button(background_normal='',
background_color=btn_color,
id=id_,
pos_hint={"x": 0, "top": top_button_share},
size_hint_y=None,
height=60,
font_size = 30,
text= str(i+1)
self.box_share.add_widget(button_share)
#Clock.schedule_interval(self.parent.ids.updatedisplay.create_button(self, *args) , 1)
#self.parent.ids.updatedisplay.create_button(self, *args)
class SequenceBoxLayout_NoTag(BoxLayout):
box_share = ObjectProperty()
config_file = DictStore('conf2.dat')
def __init__(self, **kwargs):
super(SequenceBoxLayout_NoTag, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_once(self.create_button)
def create_button(self, *args):
df = pd.read_excel("Test.xlsx","Sheet1")
parts = df['parts'].values.tolist()
top_button_share = 1.1
color = (.4, .4, .4, 1)
for i in range(len(parts)):
top_button_share -= .4
id_ = "part" + str(i + 1)
if self.config_file.exists(id_):
btn_color = self.config_file[id_]["background_color"]
else:
self.config_file.put(id_, background_color=color)
btn_color = color
button_share = Button(background_normal='',
background_color=btn_color,
id=id_,
pos_hint={"x": 0, "top": top_button_share},
size_hint_y=None,
height=60,
font_size = 30,
text= str(i+1)+ ". " + str(parts[i]))
if self.parent.name == 'notags':
button_share.bind(on_press=self.update_buttons_notag)
self.box_share.add_widget(button_share)
def update_buttons_notag(self, button):
button.background_color = 0.86,0.54,0.04,1
self.config_file.put(button.id, background_color=(0.86,0.54,0.04,1))
class MainScreen(Screen):
pass
class AnotherScreen(Screen):
pass
class SequenceScreen(Screen):
pass
class NoTags(Screen):
pass
class ScreenManagement(ScreenManager):
pass
presentation = Builder.load_file("updatelistexample.kv")
class MainApp(App):
def build(self):
return presentation
if __name__ == "__main__":
MainApp().run()
KV Code:
#: import FadeTransition kivy.uix.screenmanager.FadeTransition
ScreenManagement:
transition: FadeTransition()
MainScreen:
AnotherScreen:
NoTags:
SequenceScreen:
<SmallNavButton#Button>:
font_size: 32
size: 125, 50
color: 0,1,0,1
<BigButton#Button>:
font_size: 40
size_hint: 0.5, 0.15
color: 0,1,0,1
<BackHomeWidget>:
SmallNavButton:
on_release: app.root.current = "main"
text: "Home"
pos: root.x, root.top - self.height
<MainScreen>:
name: "main"
FloatLayout:
BigButton:
on_release: app.root.current = "notags"
text: "updating sequence"
pos_hint: {"x":0.25, "top": 0.4}
BigButton:
on_release: app.root.current = "sequence"
text: "sequence display"
pos_hint: {"x":0.25, "top": 0.7}
<AnotherScreen>:
name: "newgarage"
<NoTags>:
name: "notags"
SequenceBoxLayout_NoTag:
BackHomeWidget:
FloatLayout:
BigButton:
text: "Select Parts w/o Tags"
pos_hint: {"x":0.5, "top": 0.6}
background_normal: ''
background_color: (0.4,0.4,0.4,1)
<SequenceBoxLayout_NoEdits>:
box_share: box_share
ScrollView:
GridLayout:
id: box_share
cols: 1
size_hint_y: None
size_hint_x: 0.5
spacing: 5
padding: 130
height: self.minimum_height
canvas:
Color:
rgb: 0, 0, 0
Rectangle:
pos: self.pos
size: self.size
<SequenceBoxLayout_NoTag>:
box_share: box_share
ScrollView:
GridLayout:
id: box_share
cols: 1
size_hint_y: None
size_hint_x: 0.5
spacing: 5
padding: 130
height: self.minimum_height
canvas:
Color:
rgb: 0, 0, 0
Rectangle:
pos: self.pos
size: self.size
<SequenceScreen>:
name: "sequence"
SequenceBoxLayout_NoEdits:
id: updatedisplay
BackHomeWidget:
Credit:
Based on advice provided by #Tshirtman in the comments thread of the posted question...
Summary:
The problem with the code had to do with the fact that I had two different DictStore pointing to the same file, which was tripping up the communication between both classes.
The solution was to instead use only one DictStore and define that variable in the App class, then reference that particular variable in the child classes [using App.get_running_app()], like so:
Define config_file in App class:
class MainApp(App):
config_file = DictStore('conf2.dat')
def build(self):
return presentation
Reference App variable in child classes:
class SequenceBoxLayout_NoEdits(BoxLayout):
...
def __init__(self, **kwargs):
super(SequenceBoxLayout_NoEdits, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_interval(self.create_button, 1)
def create_button(self, *args):
self.box_share.clear_widgets()
app = App.get_running_app()
...
for i in range(len(parts)):
...
if app.config_file.exists(id_):
btn_color = app.config_file[id_]["background_color"]
else:
app.config_file.put(id_, background_color=color)
...
...
class SequenceBoxLayout_NoTag(BoxLayout):
...
def __init__(self, **kwargs):
super(SequenceBoxLayout_NoTag, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_once(self.create_button)
def create_button(self, *args):
...
app = App.get_running_app()
...
for i in range(len(parts)):
...
if app.config_file.exists(id_):
btn_color = app.config_file[id_]["background_color"]
else:
app.config_file.put(id_, background_color=color)
...
...
def update_buttons_notag(self, button):
app = App.get_running_app()
...
app.config_file.put(button.id, background_color=(0.86,0.54,0.04,1))
Clock has a schedule_interval method, which works much like the schedule_once one, but will be called every n seconds instead of just once after n seconds.
So you could certainly just change that call in __init__ and start the create_button by calling self.box_share.clear_widgets() to remove widgets from the previous call before re-creating them.
This might be a bit wasteful, if you find yourself recreating a lot of widgets even if nothing changed, so you could add some logic to first check if the data didn't change, or even if it did, just reuse the old buttons if possible.
box = self.box_share
old_buttons = box.children[:]
box.clear_widgets()
# [your logic about computing the list of parts]
for i, p in enumerate(parts): # this is much better than doing range(len(parts))
# [the logic to get the content of the button]
if old_buttons: # check there are still buttons in the list of old buttons
btn = old_buttons.pop()
else:
btn = Button(
background_normal='',
background_color=btn_color,
pos_hint={"x": 0, "top": top_button_share},
# etc, all the things that are common to all your buttons
# but really, hardcoding them like this is a bit painful,
# you should use a subclass of button so you can style it
# in a kv rule so it would apply to all of them directly
)
btn.id=id_
btn.text = "{}. {}".format(i+1, p)
btn.pos_hint["top"] = top_button_share
# re-apply any other property that might have changed for this part
box.add_widget(btn)
But this logic is quite a common one, actually, and there are other things you can do to improve things in even more situations, though that's quite some work.
Thankfully, you are not the first one to need such thing, and we have been blessed with the wonderful RecycleView, which automates all of this and more, you just need to feed it a data directory, and it'll create the necessary widgets to fill the visible part of the scrollview if there is enough widgets to warrant scrolling, and automatically update when data changes, and when you scroll to see different parts of the list. I encourage you to check it, yourself. but the end result would certainly be something like.
<PartButton#Button>:
id_: None
part: ''
text: '{}. {}'.format(self.id, self.part)
<SequencedBoxLayout#Recycleview>:
parts: self.get_parts()
viewclass: 'PartButton'
data:
[
{
id_: i,
part: part
} for i, p in enumerate(self.parts or [])
]
RecycleBoxLayout:

Kivy: Modifying a child widget of another separate class

im currently looking into kivy to start with crossplatform development. i have a bit of python experience (but basic) and now wanted to code a little game in kivy to get into. i probably wont finish this but i like learning stuff while doing it with something im intrested in.
Anyway my "App" is supposed to be seperated in two seperate "screens" the top one is only used for displaying stuff and the all interactive stuff is controlled from the bottom "screen".
Now i want to display some text in old school way by getting it written letter by letter to the screen.
This is working fine but for some reason the Label widget is only updated on screen if i call the "print_something" function from the top screen, if i call it from the bottom screen the function is indeed called but the Label widget wont change on screen.
Am i doing something wrong?
Here is a stripped version of the code:
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.clock import Clock
Builder.load_string('''
<MainUI>:
orientation: 'vertical'
# both these variables can be the same name and this doesn't lead to
# an issue with uniqueness as the id is only accessible in kv.
<Screen1>:
print_txt: print_txt
layout: layout
RelativeLayout:
id: layout
pos: 0, 400
size: 480, 400
Button:
pos: 0, 200
size_hint: (1, 0.2)
text: "Test Print"
on_press: root.print_something('TEST PRINT FROM SCREEN1')
AnchorLayout:
anchor_x: 'center'
anchor_y: 'bottom'
Label:
id: print_txt
padding_x: 10
markup: True
text_size: self.size
halign: 'left'
valign: 'top'
size_hint: (1, 0.2)
text: ""
<Screen2>:
btn1: btn1
RelativeLayout:
pos: 0, 0
size: 480, 400
Button:
id: btn1
pos_hint: {'x': .15, 'center_y': .5}
size_hint: (0.7, 0.5)
text: "Test Print"
on_press: root.print_text()
''')
class Screen1(Widget):
print_txt = ObjectProperty(None)
layout = ObjectProperty(None)
def print_something(self, string):
print 'Function called...'
self.print_txt.text = ''
counter = [0]
string_len = len(string)
def print_step(dt):
if counter[0] == string_len:
return False
else:
self.print_txt.text += string[counter[0]]
counter[0] = counter[0] + 1
Clock.schedule_interval(print_step, 2.0/60.0)
print 'Function End..'
class Screen2(Widget):
btn1 = ObjectProperty(None)
def __init__(self):
super(Screen2, self).__init__()
def print_text(self):
print 'Trying to print Text from Screen2 to Screen1'
target = Screen1()
target.print_something('TEST PRINT FROM SCREEN2')
class MainUI(Widget):
def __init__(self):
super(MainUI, self).__init__()
self.screen1 = Screen1()
self.add_widget(self.screen1)
self.add_widget(Screen2())
class MainApp(App):
def build(self):
Window.size = (480, 800)
return MainUI()
if __name__ == '__main__':
MainApp().run()
Your Screen2 print_text method creates a new Screen1 instance, which is modified but not displayed anywhere so you don't see anything change.
You could change the call to instead something like
on_press: root.parent.screen1.print_text()
...to access the print_text function of the Screen1 instance that you actually want to update.

Why are widgets not created at __init__ function?

I'm new to kivy so I'm trying to do an App, useful to me, to learn this framework.
My need is to manage my shift work schedule so I started inserting calendar days into a GridLayout. After several troubles now I'm blocked because I've got this error:
File "turno.py", line 53, in load_content
self.gridMonthView.add_widget(DaysInfo(day=wid))
AttributeError: 'NoneType' object has no attribute 'add_widget'
The load_content is called from inside the __init__ function, the error happens when I try to add DaysInfo widget (one for each day of the current month).
The strange (for me) behaviour is that if I comment line n.47 (that calls the load_content method) the program goes running and then I'm able to use the load_content from inside functions prevMonth and nextMonth without having any error.
So the question is:
Why does it seem that inside the __init__ method I can't use add_widget for gridMonthView reference/object but it's possible from the other methods of the same class?
Maybe that is not possible to add widget before the __init__ function ends something that I don't know/understand?
So, here's the code:
A little module to handle dates: calendario.py
from calendar import Calendar
from datetime import date
IT_months = ['Gennaio', 'Febbraio', 'Marzo', 'Aprile',\
'Maggio', 'Giugno', 'Luglio', 'Agosto',\
'Settembre', 'Ottobre', 'Novembre', 'Dicembre']
IT_days = ['Lunedi',
'Martedi',
'Mercoledi',
'Giovedi',
'Venerdi',
'Sabato',
'Domenica']
class Calendario():
def __init__ (self):
self.currDay = date.today()
self.calen = Calendar(0)
# def __init__ (self):
def getMonth(self):
return IT_months[self.currDay.month-1]
# def getMonth(self):
def getYear(self):
return str(self.currDay.year)
# def getYear(self):
def getDaysOfMonth(self, listOfDays):
listOfDays = []
for td in self.calen.itermonthdates(self.currDay.year, self.currDay.month):
listOfDays.append(td.day)
# for td in ...
return listOfDays
# def getDaysOfMonth(self, listOfDays):
def setNextMonth(self):
if self.currDay.month == 12:
self.currDay = self.currDay.replace(month=1, year=(self.currDay.year+1))
else:
self.currDay = self.currDay.replace(month=(self.currDay.month+1))
# def setNextMonth(self):
def setPrevMonth(self):
if self.currDay.month == 1:
self.currDay = self.currDay.replace(month=12, year=(self.currDay.year-1))
else:
self.currDay = self.currDay.replace(month=(self.currDay.month-1))
# def setNextMonth(self):
the .kv file: turno.kv
#:kivy 1.9.0
#
# menu bar
#
<MenuBar>:
orientation: 'horizontal'
padding: 1
spacing: 1
size_hint_y: 0.15
Button:
text: "Icon"
size_hint_x: 0.3
on_press: root.menu()
Button:
text: "Title"
#size_hint: 1, 0.5
#
# day's info
#
<DaysInfo>:
orientation: 'vertical'
padding: 1
spacing: 1
Label:
color: 1,0,0,1
background_color: 1,1,1,1
id: f1
text: " "
Label:
id: f2
text: " "
Label:
id: f3
text: " "
#
# month view
#
<MonthView>:
gridMonthView: gridMonthView
#
orientation: "vertical"
#
# month selection
#
BoxLayout:
id: box1
orientation: 'horizontal'
padding: 1
spacing: 1
size_hint_y: 0.15
Button:
backgroud_color: 0,1,0,1
text: " << "
size_hint_x: 0.1
on_press: root.prevMonth()
Button:
id: idSelMonth
text: root.curMonth
size_hint_x: 0.5
Button:
id: isSelYear
text: root.curYear
size_hint_x: 0.3
Button:
text: " >> "
size_hint_x: 0.1
on_press: root.nextMonth()
#
# week's days
#
BoxLayout:
id: box2
orientation: 'horizontal'
padding: 1
spacing: 1
color: 0., 0.5, 0.5, 1
size_hint_y: 0.1
Label:
text: "Lu"
Label:
text: "Ma"
Label:
text: "Me"
Label:
text: "Gi"
Label:
text: "Ve"
Label:
text: "Sa"
Label:
text: "Do"
#
# Month's days
#
GridLayout:
id: gridMonthView
cols: 7
rows: 6
padding: 1
spacing: 1
size_hint_y: 0.6
#
# Turno Main Form
#
<TurnoMainForm>:
orientation: 'vertical'
padding: 20
spacing: 10
#width: 400
#height: 800
# menu bar
MenuBar:
# month view
MonthView:
id: id1
the app's source code: turno.py
# -*- coding: utf-8 -*-
"""
Created on Mon Oct 06 12:25:04 2015
#author: a809077
"""
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.properties import ObjectProperty, StringProperty
from kivy.config import Config
from calendario import Calendario
# impostazione della grandezza della finestra
Config.set('graphics', 'width', '480')
Config.set('graphics', 'height', '800')
class MenuBar(BoxLayout):
def __init__(self, **kwargs):
super(MenuBar, self).__init__(**kwargs)
print ("------- MenuBar -------")
for child in self.children:
print(child)
def menu (self):
print (" ====== click sul menu ======")
# end of class: MenuBar(BoxLayout)
##
## visualizza i dati mensili
##
class MonthView(BoxLayout):
gridMonthView = ObjectProperty(None)
curMonth = StringProperty()
curYear = StringProperty()
def __init__(self, **kwargs):
print ("------- Month View -------")
self.curMonth = TurnoApp.currCal.getMonth()
self.curYear = TurnoApp.currCal.getYear()
super(MonthView, self).__init__(**kwargs)
self.load_content(True)
self.printInfo()
def load_content(self, clearWidget = False):
if (clearWidget):
print "---- Clear Widgets ----"
self.gridMonthView.clear_widgets()
lod = list ()
lod = TurnoApp.currCal.getDaysOfMonth(lod)
for wid in lod:
self.gridMonthView.add_widget(DaysInfo(day=wid))
def prevMonth (self):
print (" ====== click sul mese precedente 1 ======")
TurnoApp.currCal.setPrevMonth()
self.curMonth = TurnoApp.currCal.getMonth()
self.curYear = TurnoApp.currCal.getYear()
#self.printInfo()
self.load_content(True)
def nextMonth (self):
print (" ====== click sul mese successivo ======")
TurnoApp.currCal.setNextMonth()
self.curMonth = TurnoApp.currCal.getMonth()
self.curYear = TurnoApp.currCal.getYear()
#self.printInfo()
self.load_content(True)
def printInfo (self):
print " ____ items ____"
for key, val in self.ids.items():
print("key={0}, val={1}".format(key, val))
print " ____ childs ____"
for child in self.children:
print("{} -> {}".format(child, child.id))
print " ____ walk ____"
for obj in self.walk():
print obj
# end of class: MonthView(GridLayout):
class DaysInfo(BoxLayout):
def __init__(self, **kwargs):
super(DaysInfo, self).__init__()
#print ("-- Days Info - {:d} ------".format(kwargs["day"]))
self.ids["f1"].text =str(kwargs["day"])
# end of class: DaysInfo(BoxLayout):
class TurnoMainForm(BoxLayout):
def __init__(self, **kwargs):
super(TurnoMainForm, self).__init__(**kwargs)
print ("-------TurnoMainForm-------")
for child in self.children:
print(child)
# end of class: TurnoMainForm(BoxLayout):
class TurnoApp (App):
# icon = 'mia_icona.png'
title = 'Turno Terna'
currCal = Calendario()
def build (self):
return TurnoMainForm()
#return MonthView()
# end of class: TurnoApp (App):
TurnoApp().run()
I don't try to reduce the code to an example, instead I post everything because it may be better to understand where the problem is and to give me some tip to improve the code.
Why does it seem that inside the init method I can't use add_widget for gridMonthView reference/object but it's possible from the other methods of the same class?
Because self.gridMonthView has not yet been set yet (i.e. modified from the default value of None) within the __init__. This is a technical limitation - imagine two widgets with kv rules that reference one another, in this case at least one of them can't reference the other in its __init__ because it will be the first one to actually have been instantiated. This is the kind of effect you see here, the widgets must all be instantiated before references between them can be set.
It works in other methods because you call them only later, after all the instantiation is complete.
You can use something like Clock.schedule_once(self.post_init, 0) and put your widget addition in the post_init method; scheduling with an interval of 0 makes sure it will happen before the next frame, but after everything currently in progress has completed.

kv Language id linked with another class Object as ObjectProperty kivy

I am trying to update a label text from another class using its update method in Clock but I couldn't understand why its not updating the label properly .I have a sample code below :
gui_v9 = '''
#:import Clock kivy.clock.Clock
<Level_1>:
on_enter: self.EnterLevel_1()
<ScoreBar>:
time_Label: timelabel
GridLayout:
rows: 4
cols: 1
size: root.size
#Space away from border
padding: 2
spacing: 10
canvas:
Color:
rgba: 204/255.0, 204/255.0, 0/255.0, 1
Rectangle:
# self here refers to the widget i.e FloatLayout
pos: self.pos
size: self.size
Button:
text: 'Score'
size_hint: .5, .5
Label:
text: "Level 1"
Label:
text: "Time :"
id: timelabel
Button:
text: 'Mute'
'''
class ScoreBar(Widget):
time_Label = ObjectProperty(None)
def __init__(self):
super(ScoreBar, self).__init__()
class Level_1(Screen,Widget):
def __init__(self, **kwargs):
super(Level_1, self).__init__(**kwargs)
self.layout = GridLayout(cols=2,spacing=(10),padding=10)
def EnterLevel_1(self):
print "Hi This is EnterLevel_1 . Level One Gui work area "
scoreBar = ScoreBar()
Field = tama(speed=3)
self.layout.add_widget(Field)
self.layout.add_widget(scoreBar)
self.add_widget(self.layout)
Clock.schedule_interval(Field.update, 10.0/100)
#Field
class tama(Widget):
def __init__(self, speed=1 ):
super(tama, self).__init__()
self.speed = speed
self.id = "Field"
self.size = (800,600)
self.Extra = 200
print ScoreBar().time_Label.text
def update(self,dt):
print ScoreBar().time_Label.text
ScoreBar().time_Label.text ="cdfdfd"
# Create the screen manager
Builder.load_string(gui_v9)
sm = ScreenManager()
sm.add_widget(Level_1(name='level_1'))
class MyJB(App):
def build(self):
return sm
if __name__ == '__main__':
MyJB().run()
The issue is that you have lines like
print ScoreBar().time_Label.text
This doesn't tell you anything about the existing ScoreBar, it makes a new one and returns information about that.
From the tama, you could refer to self.parent.children[1] to access the one you actually originally added, or devise another way to access the reference.

Categories

Resources