Kivy image update - python

I'm absolutely new to GUI desing and trying to create a very simple App using python and kivy with a little bit of animations after pressing buttons. But as I'm completely new to kivy I'm stuck. I think the answer is very easy but after roundabout 8h of trial and error and googling through the internet I'm desperate :-)
I have created a little App with a button and an Image. What I'm trying to do is changing the Image after "button_pressed". The code in kivy file:
<FirstTest>:
my_image: my_image
source_image: self.source_image
Button:
on_press: root.gamer.changeImage()
My_Image:
id: my_image
source: self.parent.source_image
<My_Image>:
allow_stretch:True
keep_ratio: False
And the python Code (only the necessary part):
class Player(object):
def changeImage(self):
FirstTest.source_image = 'new_image.png'
class My_Image(Image):
pass
class FirstTest(FloatLayout):
my_image = ObjectProperty(None)
source_image = StringProperty(None)
source_image = 'First_Image.png'
gamer = Player()
def update(self, dt):
print(self.source_image) #To see in realtime if the image source changed
class MyApp(App):
def build(self):
game = FirstTest()
Clock.schedule_interval(partial(FirstTest.update, FirstTest), 1/60)
return game
When I start the App I see the Button and the "First_Image" loaded. But when I hit the button nothing changes. The only thing I see is that the source_image path changed in the console.
I do not understand why the Image isn't reloaded with the new source. I thought when I change the path I will get a new Image and if I do this repeated I would get some kind of animation. But not even a single image changes. If I try to change the Objectproperty of "my_image" I got an error message with "ObjectProperty has no attribute source"
What do I miss? Please help!
Thanks in advance!

The most obvious problem I see with your code is that you are confusing class objects with instance objects.
FirstTest is a class object, and when you do:
game = FirstTest()
You are creating an instance of FirstTest. The instance will have its own properties and methods. This means that you don't want to call FirstTest.update but game.update.
Also, FirstTest.source_image = ... is wrong, as you are not changing the image source on the instance object in the GUI, but modifying the class definition. You will need to modify game.source_image instead. The easiest way to do this is to save a reference of game in the App object (self.game = game in build), and then reference it with App.get_running_app().game.

Related

Python, Kivy - QRScanner and ScreenManager

I want to create an app with 2 (an more) screens (screen manager).
When i make app without that QR reader, i know how to move values from one screen to another. But here i have problem with that.
I spend 3 days on this problem and still dont have answer. Can You help me?
Heres code:
https://github.com/fornakter/Terminarz-Kivy-MD/blob/master/main.py
Errors are on class SecoundWindow, on line 16.
Comments explained errors wit i recive.
Thank You.
The documentation says connect_camera() must be called after on_start(). So change the definition of SecoundWindow to simply:
class SecoundWindow(Screen):
pass
And in your ReadQR App, add the following methods:
def on_start(self):
Clock.schedule_once(self.connect_camera)
def connect_camera(self, dt):
secoundWindow = self.root.get_screen('secound')
secoundWindow.ids.preview.connect_camera(camera_id='front', enable_analyze_pixels=True, default_zoom=0.0)
I needed to add camera_id to avoid SEGFAULT.
Unrelated, but the following lines of your code do nothing and can be deleted:
sm = ScreenManager()
sm.add_widget(FirstWindow(name='first'))
sm.add_widget(SecoundWindow(name='secound'))

Can't change image source from outside class

I want to change the source path of an image from outside of the class/screen where the image is shown.
I have a callback function called my_callback, which will be called at some point during runtime of the app:
def my_callback():
# do stuff
MDApp.get_running_app().manager.get_screen('my_class').ids.imageID.source = "my_image.png"
MDApp.get_running_app().manager.current = "my_class"
I would expect the above two lines of code to do the following:
Switch the kivy screen to the my_class screen
Update the source of the image with the id "imageID" (as defined in my .kv file).
Outcome (1) is successful, but outcome (2) is not: rather than showing the image "my_image.png", a black shape is shown of equivalent dimensions to the image "my_image.png".
How can I fix this?
Note that MDApp is used here in place of App, as I am using the KivyMD library for my project.
I still don't know why the method I posted in the question doesn't work, but I have found an alternative solution.
The function my_callback now looks like this:
def my_callback():
# do stuff
MDApp.get_running_app().manager.current = "my_class"
image = AsyncImage(source='my_image.png')
MDApp.get_running_app().manager.get_screen('my_class').ids.floatLayoutID.add_widget(image)
And in the .kv file, there is a nested floatLayout:
<MyClass>:
name: "my_class"
FloatLayout:
FloatLayout:
id: floatLayoutID

Changing screens in Python without Screen Manager Class

I'm using gestures in all of my screens, and I cannot use a screen manager class to manage my screens, or so I believe. I can navigate the .kv file by using manger.current = 'some_screeen' but cannot in the .py file.
I've been trying Runner().ids.manager.current = 'some_screen' in the .py file but it doesn't work. There isn't even an error thrown. The screen doesn't change at all.
Essential Code (for the sake of brevity):
class Runner(gesture.GestureBox):
pass
MyApp(App):
def build(self):
return Runner()
Then in the KV file, I'm creating the screen manager.
<Runner>:
ScreenManager:
id: manager
Screen:
name: 'main_screen'
Button:
on_press:
manager.current = 'screen1'
Screen:
name: 'screen1'
Button:
on_press:
manager.current = 'home_screen'
I've been trying Runner().ids.manager.current = 'some_screen' in the .py file but it doesn't work. There isn't even an error thrown. The screen doesn't change at all.
It works fine, it just doesn't do what you believe. When you write Runner() you get a new instance of the Runner class, with its own children including its own ScreenManager. This one has nothing to do with the one you're displaying in your gui. When you set its current property the ScreenManager will dutifully change the screen, it's just you have no way to see that.
What you actually want is to change the current property of the widget that you are displaying in your gui. The best way to do this depends on the context, which you have omitted (always try to provide a full runnable example, it isn't clear what your failing code looked like). However, in this case the Runner instance is your root widget which is accessible with App.get_running_app().root, so you can write App.get_running_app().root.ids.manager.current = 'some_screen'. Again, there might be neater ways to do it depending on how you structure your code, but this is always an option.

Init screen in kv on button release using ScreenManager

I'm a beginner Kivy developer and I need some advice from you guys.
I'm using ScreenManager to jump between screens and as far as I noticed, all the screens are initialized just after the application starts, and I need them to be initialized with certain attributes from previous screens, like, selecting the category or stuff. Is there any way to do that?
I have two buttons in CategorySelectScreen both representing certain category, I want them to send a string attribute to DictScreen, where it will be used as an argument in CategorySelect method, which filters the items list, but the thing is, the application need that argument on start and without it the interpreter would just throw errors.
Also, I think I'm using kivy in a very bad way, could you please look into my code and give me some pro tips? Thanks in advance, cheers :)
kv file: http://pastebin.com/UdvGS7Wv
py files: http://pastebin.com/gJn9Mrip
When declaring your screens decide what object would be it's input. Then make this object a property. After that, setup on_... callback where you build your screen with widgets with values based on this input object. For example:
class DictScreen(Screen):
category_selected = ObjectProperty(None)
def on_category_selected(self, instance, value):
category_selected = value
self.clear_widgets()
self.add_widget(Button(text=category_selected.name))
And in previous screen, before you switch to DictScreen get its instance from app.root.ids, then assign category_selected to it and then set new current screen with ScreenManager. This way your DictScreen will be immediately build with choosen category right before you switch to it.
"before you switch to DictScreen get its instance" how this can be done? It's well explained here:
https://kivy.org/docs/api-kivy.uix.widget.html?highlight=widget#kivy.uix.widget.Widget.ids

Events passing in python with kivy

I am new to python and kivy, I am trying to clear all widgets in a host when any button is pressed, my code is as follows:
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.uix.label import Label
spots={}
class spot(Button):
'''
classdocs
'''
def __init__(self, **kwargs):
'''
Constructor
'''
super(spot,self).__init__(**kwargs)
self.ismine=False
if 'text' in kwargs:
self.text=kwargs['text']
else:
self.text="X"
def on_press(self, *args, **kwargs):
#return Button.on_press(self, *args, **kwargs)
game.spottouched(self, self.text)
class game(BoxLayout):
def __init__(self,**kwargs):
super(game,self).__init__(**kwargs)
self.m=minesholder(rows=25, cols=25)
self.add_widget(self.m)
self.attachtogrid()
def attachtogrid(self):
self.m.clear_widgets()
spots.clear()
for r in range(0,25):
for c in range(0,25):
idd=str(r)+","+str(c)
spots[idd]=idd
self.m.add_widget(spot(text=idd))
def spottouched(self,spotid):
#popup=Popup(title=spotid)
#popup.open()
self.clear_widgets()
The last line is for clearing the widgets, and spot class has the on_press event. I am trying to pass the event of on_press even of button on app to a holding boxlayout (game class), could you please tell/point me in right direction to lean on how pass the events?
Thank you.
I think this is what you want, based on your comment on #inclement's answer: Get rid of the on_press method on your button. You want to bind to the event instead - this will work more like WPF.
for r in range(0,25):
for c in range(0,25):
idd=str(r)+","+str(c)
spots[idd]=idd
s = spot(text=idd)
self.m.add_widget(s)
s.bind(on_press=self.spottouched)
So self.spottouched is like an event handler for the spots on_press event.
s.bind(on_press=self.spottouched)
is kind of like this:
AddHandler s.Click, AddressOf spottouched
Note that adding the handler this way, the handler will receive a single argument which is the spot instance. You can get spot.text from that instance.
game.spottouched(self, self.text)
You've misunderstood how python classes work. You want to clear the widgets from the instance of your game that you're actually using - something you created by writing game(). With a reference to that, you need to do instance.spottouched(spotid) to call the method of that instance.
What you're doing instead is calling the method via the class definition, which really makes no sense here and you don't want to do it. Actually, you basically never want to do this.
Anyway, the way to fix this depends on how you actually constructed your app, but it boils down to you need to keep a reference to the game instance somewhere that the spot instance can see it. The best way depends on the rest of your code, but you can always do it via the app class, e.g.
App.get_running_app().game = game()
and later
App.get_running_app().game.spottouched(self.text)
This is just an example, it might be a bad way to do things sometimes.
Also, begin your widget names with capital letters! kv language uses this to identify them, you will hit problems using it if you don't name them this way. It's also a strong python convention worth following.

Categories