How do I display a variable image with a KV file? - python

I am trying to make a card game with Python and Kivy and can not get the card to display. So far, ChaseTheAce.deal pick a random card and removes it from the deck. I can not figure out how to pass the string from the card dict to the Image in the KV file. I'm new to Kivy and am having trouble with the IDs and what information matches with information from the PY file. Any help is appreciated, thanks!
PY File
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.image import Image
from kivy.uix.widget import Widget
from kivy.properties import StringProperty
import random
import numpy as np
cards = ['ace_spades', 'king_spades']
dict = {'ace_spades':'ace_of_spades.png', 'king_spades':'king_of_spades2.png'}
class LoginScreen(Screen):
pass
class GameScreen(Screen):
pass
class ScoreScreen(Screen):
pass
class WindowManager(ScreenManager):
pass
class ChaseTheAce(App):
cardimagefile = StringProperty()
def deal(self):
mycard = random.choice(cards)
cards.remove(mycard)
cardimagefile = (dict[mycard])
def build(self):
return kv
kv = Builder.load_file("cta1.kv")
if __name__ == "__main__":
ChaseTheAce().run()
KV File
<GameScreen>:
ChaseTheAce:ChaseTheAce
name: "GameScreen"
GridLayout:
rows: 3
Image:
id: cardimage
source: ChaseTheAce.cardimagefile #<<<<<<<<<<<<<<<<<<<
allow_stretch: True
GridLayout:
dealer:deal
cols: 3
Button:
id: deal
text: "Deal"
on_release:
app.deal()
Edited to remove unrelated parts of the code.

There are a few errors in your example:
You need a root layout that contains all the stuff of your app.
You need a ScreenManager to manage your Screens (that you must put inside it).
To access your cardimagefile property in your .py file, you have to use self.cardimagefile, otherwise you are just creating a new, different, local cardimagefile variable.
To access your cardimagefile property in your .kv file, you have to use app.cardimagefile
Since I don't have your images, I added a print whenever the source of the Image is changing, and it works fine..
The py code:
cards = ['ace_spades', 'king_spades']
dict = {'ace_spades':'ace_of_spades.png', 'king_spades':'king_of_spades2.png'}
Builder.load_file("cta1.kv")
class LoginScreen(Screen):
pass
class GameScreen(Screen):
pass
class ScoreScreen(Screen):
pass
class WindowManager(ScreenManager):
pass
class RootLayout(FloatLayout): # create a root layout
pass
class ChaseTheAce(App):
cardimagefile = StringProperty()
def deal(self):
mycard = random.choice(cards)
cards.remove(mycard)
self.cardimagefile = (dict[mycard]) # the cardimagefile is not local needs self.
def build(self):
return RootLayout() # you must return the root layout here
if __name__ == "__main__":
ChaseTheAce().run()
The kv code:
<RootLayout>: # you need a root layout
WindowManager: # that contains a ScreenManager
GameScreen: # that manages the screens
# ChaseTheAce:ChaseTheAce # you don't need this
name: "GameScreen"
GridLayout:
rows: 3
Image:
id: cardimage
source: app.cardimagefile #<<<<<<<<<<<<<<<<<<<
allow_stretch: True
on_source: print(self.source)
GridLayout:
# dealer:deal # you don't need this
cols: 3
Button:
id: deal
text: "Deal"
on_release:
app.deal()

Related

How do I access ids in Kivy?

I am a beginner to Kivy (though not to Python), and I am struggling to get the ids from a kv string into my main code. I have the following, but the 'print' statement tells me that there are no IDs. The application itself runs with no errors.
import kivy
kivy.require('2.1.0')
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.image import Image
kvString = """
MainScreen:
id: maincontainer
cols: 1
thumbnails: thumbnails.__self__
GridLayout:
id: thumbnails
cols: 3
rows: 3
Image:
source: "test.png"
"""
class MainScreen(GridLayout):
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
# This prints 0
print("Ids: {}".format(len(self.ids.items())))
class ExampleApp(App):
def build(self):
root = Builder.load_string(kvString)
return root
if __name__ == "__main__":
ExampleApp().run()
When I ran your code, I got a critical warning that there are no screens, and therefore the app will terminate. As soon as I switched MainScreen to a screen, it worked out perfectly. Here is the code:
.py
import kivy
kivy.require('2.1.0')
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.image import Image
sm = ScreenManager()
#I removed some unnecessary code such as the cols, thumbnail, etc.
kvString = """
<MainScreen>
GridLayout:
id: thumbnails
cols: 3
rows: 3
Image:
source: "test.png"
"""
#NEEDS TO INHERIT SCREEN
class MainScreen(Screen):
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
# This now prints 1
print(f'There are {len(self.ids.items())} id(s)')
class ExampleApp(App):
def build(self):
root = Builder.load_string(kvString)
#Adds the screen
sm.add_widget(MainScreen(name='screen'))
return sm
if __name__ == "__main__":
ExampleApp().run()

Instance of a custom class assigned to Kivy ObjectProperty doesn't update GUI

I am trying to use Kivy Properties to refresh GUI after property is changed. However, when I try to assign an instance of custom class to property it doesn't work. After modifying property GUI is not refreshed with new value.
MWE:
python file:
from typing import List
from kivy.app import App
from kivy.properties import ObjectProperty
from kivy.uix.screenmanager import Screen
class SomeStruct:
def __init__(self, atr1: List[int], atr2: str):
self.atr1 = atr1
self.atr2 = atr2
class Root(Screen):
prop1 = ObjectProperty(None)
prop2 = ObjectProperty(None)
def __init__(self, **kw):
super().__init__(**kw)
self.prop1 = SomeStruct(atr1=[1, 2, 3], atr2="test1")
self.prop2 = "control1"
def modify1(self):
self.prop1.atr1 = [3, 2, 1]
def modify2(self):
self.prop1.atr2 = "test2"
def modify3(self):
self.prop2 = "control2"
class Mwe(App):
pass
if __name__ == '__main__':
Mwe().run()
kv file:
Root:
BoxLayout:
orientation: 'vertical'
Button:
text: str(root.prop1.atr1)
on_press: root.modify1()
Button:
text: root.prop1.atr2
on_press: root.modify2()
Button:
text: root.prop2
on_press: root.modify3()
Button 1 and 2 change classe's varaibles but text on these buttons is not updated. Button 3 works as it should.
Any help would be aprreciated, I really want to avoid making every varaible from class a separate property.

Kivy - rebuild class/ Boxlayout with updated content

In my Kivy-App, i generate Buttons via a python-class based on a dictionary (in the following example i use a list but that's just an example for the underlying problem).
Within the App, the dictionary gets changed and i want to display that change (obviously) in my App (by adding/ removing/ rearranging the Buttons).
To achieve this, my approach is to either restart the entire App or only reload that particular BoxLayout. Unfortunately, non of my attempts worked out so far and i could not find any (working) solution on the internet.
This is my code example:
Python Code:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
buttonlist = []
counter = 0
class MainWindow(BoxLayout):
def addbutton(self):
global buttonlist
global counter
buttonlist.append(counter)
counter += 1
class ButtonBox(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation = "vertical"
global buttonlist
for button in buttonlist:
b = Button(text=str(button))
self.add_widget(b)
class KivyApp(App):
def build(self):
return MainWindow()
KivyApp().run()
KV Code:
<MainWindow>:
BoxLayout:
ButtonBox:
Button:
text: "add Button"
on_press: root.addbutton()
My closest attempt was something containing a restart-Method like:
def restart(self):
self.stop()
return KivyApp().run()
and calling:
App.get_running_app().restart()
But for some reason, this does not stop the App but opens a second instance of the App within the first one (resulting in App in App in App in App if pressed often)
You can rebuild the ButtonBox by first calling clear_widgets() on the ButtonBox instance. Here is a modified version of your code that does that:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
kv = '''
<MainWindow>:
BoxLayout:
ButtonBox:
id: box
Button:
text: "add Button"
on_press: root.addbutton()
'''
buttonlist = ['Abba', 'Dabba', 'Doo']
counter = 3
class MainWindow(BoxLayout):
def addbutton(self):
global buttonlist
global counter
buttonlist.append(str(counter))
counter += 1
self.ids.box.reload()
class ButtonBox(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation = "vertical"
self.reload()
def reload(self):
# method to rebuild the ButtonBox contents
global buttonlist
self.clear_widgets()
for button in buttonlist:
b = Button(text=str(button))
self.add_widget(b)
class KivyApp(App):
def build(self):
Builder.load_string(kv)
return MainWindow()
KivyApp().run()
I used your kv as a string, just for my own convenience.

Kivy - Update a label with sensor data?

New to kivy, and OOP.
I'm trying to update a label in kivy with data I pull from a temp sensor. The code that pulls in the sensor data is in labeltempmod. I created a function getTheTemp() that is called every second. In the function I try to assign the text of the label via Label(text=(format(thetemp)), font_size=80). The program ignores this. What am I doing wrong here?
#This is a test to see if I can write the temp to label
import labeltempmod
import kivy
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.label import Label
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.textinput import TextInput
from kivy.uix.boxlayout import BoxLayout
def getTheTemp(dt):
thetemp = labeltempmod.readtemp()
Label(text=(format(thetemp)), font_size=80)
print thetemp
class LabelWidget(BoxLayout):
pass
class labeltestApp(App):
def build(self):
# call get_temp 0.5 seconds
Clock.schedule_interval(getTheTemp, 1)
return LabelWidget()
if __name__ == "__main__":
labeltestApp().run()
Here is the kivy language file:
<LabelWidget>:
orientation: 'vertical'
TextInput:
id: my_textinput
font_size: 80
size_hint_y: None
height: 100
text: 'default'
FloatLayout:
Label:
id: TempLabel
font_size: 150
text: 'Temp Test'
Thanks.
Sorry but you never update something You are just creating another label
Try this:
class LabelWidget(BoxLayout):
def __init__(self, **kwargs):
super(LabelWidget, self).__init__(**kwargs)
Clock.schedule_interval(self.getTheTemp, 1)
def getTheTemp(self, dt):
thetemp = labeltempmod.readtemp()
self.ids.TempLabel.text = thetemp
print thetemp
class labeltestApp(App):
def build(self):
return LabelWidget()
if __name__ == "__main__":
labeltestApp().run()
Update : for your last request, I think the best way to do that is:
...
class LabelWidget(BoxLayout):
def __init__(self, **kwargs):
super(LabelWidget, self).__init__(**kwargs)
self.Thetemp = None
Clock.schedule_interval(self.getTheTemp, 1)
def getTheTemp(self, dt):
if self.Thetemp is None:
self.thetemp = labeltempmod.readtemp()
else:
self.thetemp = labeltempmod.readtemp(self.theTemp)
self.ids.TempLabel.text = str(self.thetemp)

Kivy references across screens

I want to access root widgets ids from other rootwidgets, but I can't seem to fully grasp how referencing works in Kivy and using a ScreenManager with different screens makes it even harder for me.
I want to achieve the following:
Edit: single file version
(This code assumes you're going to build a complex app, so I don't want to load all code at startup. Hence the kv_strings are loaded when switching screen, and not put into kv code of the ScreenManager. Code is based on the Kivy Showcase.)
Code main.py, Edit 2: working code (see answer why)
#!/usr/bin/kivy
# -*- coding: utf-8 -*-
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.clock import Clock
from kivy.properties import StringProperty, ObjectProperty
kv_foo = '''
<FooScreen>:
id: fooscreen_id
BoxLayout:
id: content
orientation: 'vertical'
spacing: '20dp'
padding: '8dp'
size_hint: (1, 1)
BoxLayout:
orientation: 'vertical'
Label:
id: important_text
size_hint_y: 0.3
text: app.imp_text
Button:
id: magic_change
size_hint_y: 0.3
text: "Change text above to text below (after screen switch)"
on_press: app.change_text()
ScreenManager:
id: sm
on_current_screen:
idx = app.screen_names.index(args[1].name)
'''
class FooScreen(Screen):
# 'content' refers to the id of the BoxLayout in FooScreen in foo.kv
def add_widget(self, *args):
if 'content' in self.ids:
return self.ids.content.add_widget(*args)
return super(FooScreen, self).add_widget(*args)
class FooApp(App):
imp_text = StringProperty("Should change to text from id: magic_text")
screen_magic = ObjectProperty()
magic_layout = ObjectProperty()
def build(self):
self.title = 'Foo'
self.root = root = Builder.load_string(kv_foo)
# Trying stuff with References
self.sm = self.root.ids.sm # ScreenManager
# Setting up screens for screen manager
self.screens = {}
self.available_screens = [kv_mainmenu, kv_magic]
self.screen_names = ['MainMenu', 'Magic']
self.go_screen(0)
# Go to other screen
def go_screen(self, idx):
print("Change MainScreen to: {}".format(idx))
self.index = idx
# Go to not main menu
if idx == 0:
self.root.ids.sm.switch_to(self.load_screen(idx), direction='right')
# Go to main menu
else:
self.root.ids.sm.switch_to(self.load_screen(idx), direction='left')
# Load kv files
def load_screen(self, index):
if index in self.screens:
return self.screens[index]
screen = Builder.load_string(self.available_screens[index])
self.screens[index] = screen
# if index refers to 'Magic' (kv_magic), create reference
if index == 1:
Clock.schedule_once(lambda dt: self.create_reference())
return screen
# Trying to get id's
def create_reference(self):
print("\nrefs:")
# Get screen from ScreenManager
self.screen_magic = self.sm.get_screen(self.screen_names[1])
# screen.boxlayout.magiclayout
self.magic_layout = self.screen_magic.children[0].children[0]
def change_text(self):
# Get text from id: magic_text
if self.magic_layout:
self.imp_text = self.magic_layout.ids['magic_text'].text
kv_mainmenu = '''
FooScreen:
id: mainm
name: 'MainMenu'
Button:
text: 'Magic'
on_release: app.go_screen(1)
'''
kv_magic = '''
<MagicLayout>
id: magic_layout
orientation: 'vertical'
Label:
id: magic_text
text: root.m_text
FooScreen:
id: magic_screen
name: 'Magic'
MagicLayout:
id: testmagic
'''
class MagicLayout(BoxLayout):
m_text = StringProperty("Reference between widgets test")
if __name__ == '__main__':
FooApp().run()
Question
How can I set up proper references that the button "Change text above..." can retrieve magic_text.text ("Reference between widgets test") and change self.imp_text to magic_text.text?
I found a way to reference to Kivy widgets not loaded at the startup of the app without using globals. Thanks #inclement for ScreenManager.get_screen().
I had to add the following code:
class FooApp(App):
screen_magic = ObjectProperty()
magic_layout = ObjectProperty()
...
# Trying to get id's
def create_reference(self):
print("\nrefs:")
# Get screen from ScreenManager
self.screen_magic = self.sm.get_screen(self.screen_names[1])
# screen.boxlayout.magiclayout
self.magic_layout = self.screen_magic.children[0].children[0]
def change_text(self):
# Get text from id: magic_text
if self.magic_layout:
self.imp_text = self.magic_layout.ids['magic_text'].text
self.screen_magic is assigned the screen I need (<FooScreen>) and self.magic_layout is assigned the widget I need (<MagicLayout>). Then I can use the ids from <MagicLayout> to access the Label magic_text's text.
(For full code see updated question)

Categories