I am trying to make an APP with 2 screen:
First screen is a Button
Second screen shows a graph
When the Button of the first screen is pressed, the second screen appears with the graph.
I was able to plot the graph with 1 screen only, using matplotlib.
Here is my code:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use("module://kivy.garden.matplotlib.backend_kivy")
from kivy.garden.matplotlib import FigureCanvasKivyAgg
from kivy.uix.widget import Widget
class Sensores(Screen):
pass
class Grafico(Screen):
def build(self):
box = BoxLayout()
box.add_widget(FigureCanvasKivyAgg(plt.gcf()))
return box
class Menu(ScreenManager):
pass
presentation = Builder.load_file('sensor.kv')
class sensor(App):
def build(self):
return presentation
if __name__ == "__main__":
sensor().run()
KIVY
Menu:
Sensores:
Grafico:
<Sensores>
name: 'sensores'
BoxLayout:
Button:
text: "Sensor 01"
on_release:
root.Grafico()
<Grafico>
name: 'grafico'
I expect to have the graph in the second screen.
I see two problems with your code. First, in your kv file the Button action is incorrect:
Button:
text: "Sensor 01"
on_release:
root.Grafico()
If the Button is intended to switch to the other screen, it should be:
Button:
text: "Sensor 01"
on_release:
root.manager.current='grafico'
Second, in your Grafico class you have a build() method that is never called. If you change that from:
class Grafico(Screen):
def build(self):
box = BoxLayout()
box.add_widget(FigureCanvasKivyAgg(plt.gcf()))
return box
to:
class Grafico(Screen):
def on_enter(self, *args):
box = BoxLayout()
box.add_widget(FigureCanvasKivyAgg(plt.gcf()))
self.add_widget(box)
I think you will get the desired result. The key is that the on_enter() method is called when the Grafico Screen is displayed. The method is your code, but with a self.add_widget(box) added in order to add the box to the screen. See the Screen Documentation for more info.
Thank you very much!
It works now! Follow the code:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use("module://kivy.garden.matplotlib.backend_kivy")
from kivy.garden.matplotlib import FigureCanvasKivyAgg
from kivy.uix.widget import Widget
plt.plot([1,23,2,4])
plt.ylabel("alguns numeros legais")
class Sensores(Screen):
pass
class Grafico(Screen):
def on_enter(self, *args):
box = BoxLayout()
box.add_widget(FigureCanvasKivyAgg(plt.gcf()))
self.add_widget(box)
class Menu(ScreenManager):
pass
presentation = Builder.load_file('sensor.kv')
class sensor(App):
def build(self):
return presentation
if __name__ == "__main__":
sensor().run()
KV LANG
#:kivy 1.9.1
Menu:
Sensores:
Grafico:
name: 'grafico'
<Sensores>
name: 'sensores'
BoxLayout:
Button:
text: "Sensor 01"
on_release:
root.manager.current = 'grafico'
<Grafico>
name: 'grafico'
Related
To change an attribute of a class, say font_size of the Label class, I can do add this in the kv file:
<Label>
font_size: "15sp"
How do I access the font_size attribute via code?
Your question is too vague... in what context do you want to access the attribute? If it is at the instantiation then you could do this ...
from kivy.app import App
from kivy.uix.label import Label
class TestApp(App):
def build(self):
return Label(text='hello world',
font_size= '70sp')
if __name__ == '__main__':
TestApp().run()
But if you want to access the font size of a label that is created in kV... Probably best to assign the label a NumericProperty as font size in the KV file and then change the NumericProperty as needed...
from kivy.app import App
from kivy.uix.label import Label
from kivy.properties import NumericProperty
class TestApp(App):
FontSize = NumericProperty(50)
def ChangeFont(self):
self.FontSize +=10
if __name__ == '__main__':
TestApp().run()
KV...
BoxLayout:
Button:
text: 'press me'
on_press: app.ChangeFont()
Label:
text: 'Hello'
font_size: app.FontSize
And if you want to access a specific label widget you can make the label a class attribute and then modify it's text directly, if you had many labels you could add each to a list, assign the list as a class attribute and loop over the list changing each label.font_size, the example here only has one label...
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
class MyButton(Button):
pass
class TestApp(App):
L = Label(text = 'hello', font_size = 20)
def ChangeFont(self):
self.L.font_size +=10
def build(self):
B = BoxLayout()
B.add_widget(MyButton())
B.add_widget(self.L)
return B
if __name__ == '__main__':
TestApp().run()
KV...
<MyButton>:
text: 'press me'
on_press: app.ChangeFont()
As John said in the comment, use ids to identify your label.
kv file
<MyBoxLayout>
Label:
id: mylabel
font_size: '15dp'
Button:
on_release: root.change_font_size()
python file
class MyBoxLayout(BoxLayout):
def change_font_size(self):
self.ids.mylabel.font_size = '12dp'
Obviously you can do more with it than just change the size once, but thats the basic idea.
I've just started my first kivy app. The app is intended to start with button "Start Scan" and then show up the QR scanner built with ZBarCam.
I'm using Screens with the ScreenManager to change from the button view to the camera view (with zbarcam), the problem is that I realized that the camera is initialized from the beginning , so before clicking on the button the camera is already on (I know it because the led from the camera is on).
I don't know if Screen should not be used in this case, or if is there a way to tell the app to do not initialize all screens.
The code I'm using is the following:
QrApp.py:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
class QrReader(Screen):
pass
class ScanButton(Screen):
pass
class QrApp(App):
pass
if __name__ == '__main__':
QrApp().run()
qrapp.kv:
ScreenManager:
id: screen_manager
ScanButton:
id: scan_btn
name: 'scan_btn'
manager: 'screen_manager'
QrReader:
id: qr_reader
name: 'qr_reader'
manager: 'screen_manager'
<ScanButton>:
BoxLayout:
orientation: 'vertical'
Button:
text:'Start Scan'
font_size:"50sp"
color: [0, 255, 255, .67]
on_press: app.root.current = 'qr_reader'
<QrReader>:
#:import ZBarCam kivy_garden.zbarcam.ZBarCam
BoxLayout:
orientation: 'vertical'
ZBarCam:
id:qrcodecam
Label:
size_hint: None, None
size: self.texture_size[0], 50
text: ' '.join([str(symbol.data) for symbol in qrcodecam.symbols])
Thanks!
==== ALTERNATIVE BASED IN A COMMENT (still fails) ====
Based on the comment from n4321d I tried to add the ZBarCam as widget in the QrReader Screen. While I can now initiate the camera when the widget is added, I don't see how I can get the symbols that is, the text read from the QR.
This alternative code is the following:
QrApp.py:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.uix.label import Label
from kivy.uix.screenmanager import ScreenManager, Screen
class QrReader(Screen):
def on_enter(self):
from kivy_garden.zbarcam import ZBarCam
zbarcam = ZBarCam()
self.add_widget(zbarcam)
self.add_widget(Label(
text='PRINT SYMBOLS', #' '.join([str(symbol.data) for symbol in zbarcam.symbols] does not work
size_hint=(None,None),
size=(Window.width*0.1, Window.height*0.1),
center=(Window.width*0.3, Window.height*0.5)))
class ScanButton(Screen):
pass
class QrApp(App):
pass
if __name__ == '__main__':
QrApp().run()
qrapp.kv
ScreenManager:
id: screen_manager
ScanButton:
id: scan_btn
name: 'scan_btn'
manager: 'screen_manager'
QrReader:
id: qr_reader
name: 'qr_reader'
manager: 'screen_manager'
<ScanButton>:
BoxLayout:
orientation: 'vertical'
Button:
text:'Start Scan'
font_size:"50sp"
color: [0, 255, 255, .67]
on_press:
app.root.current = 'qr_reader'
<QrReader>:
BoxLayout:
orientation: 'vertical'
====== SOLUTION ========
My workaround solution is posted as an answer to this question here
First:
I would also add an on_leave function where you remove your cam widget otherwise you will keep adding new widgets every time you load it.
I dont have a working cam now , so i cannot test your code. Looking at your code I think you have to bind the text in your Label to the text in zbarcam.symbols with a function: self.label = Label(....); zbarcam.bind(symbols=lambda *x: setattr(self.label, "text", str(x[1]))) or somethign like that.
Here is an example using a random text generator instead of ZBarCam (since i cannot run that).
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.uix.label import Label
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ListProperty
import random
from kivy.clock import Clock
kv_str = """
ScreenManager:
id: screen_manager
ScanButton:
id: scan_btn
name: 'scan_btn'
manager: 'screen_manager'
QrReader:
id: qr_reader
name: 'qr_reader'
manager: 'screen_manager'
<ScanButton>:
BoxLayout:
orientation: 'vertical'
Button:
text:'Start Scan'
font_size:"50sp"
color: [0, 255, 255, .67]
on_press:
app.root.current = 'qr_reader'
<QrReader>:
BoxLayout:
orientation: 'vertical'
"""
class ZBarCam(Label):
symbols = ListProperty([])
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_interval(self.gen_rand_text, 1)
def gen_rand_text(self, *args):
self.text = random.choice(['aaaaa', 'bbbbb', 'ccccc'])
self.symbols.append(self.text)
if len(self.symbols) > 3:
del self.symbols[0]
class QrReader(Screen):
def on_enter(self):
self.zbarcam = ZBarCam()
self.add_widget(self.zbarcam)
self.label = Label(
text='PRINT SYMBOLS', #' '.join([str(symbol.data) for symbol in zbarcam.symbols] does not work
size_hint=(None,None),
size=(Window.width*0.1, Window.height*0.1),
center=(Window.width*0.3, Window.height*0.5))
self.add_widget(self.label)
self.zbarcam.bind(symbols = lambda *x: setattr(self.label, "text", str(x[1])))
def on_leave(self, *args):
self.remove_widget(self.zbarcam)
self.remove_widget(self.label)
class ScanButton(Screen):
pass
class QrApp(App):
def build(self):
return Builder.load_string(kv_str)
if __name__ == '__main__':
QrApp().run()
if it still does not work you might have to call self.zbarcam.start() too in the on_enter method after adding it
hope this helps
Thanks to answer from n4321d I finally found a solution.
I also struggled releasing the camera, as zbarcam.stop() stops zbarcam but camera was apparently still open (led was on). After digging a lot, I found this workaround which seems to work well to release the camera.
I also merged the button and qr reader in the same Class and I no longer use kv file, in this example. The final code is the following:
QrApp.py:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.clock import Clock
from kivy_garden.zbarcam import ZBarCam
class QrScanner(BoxLayout):
def __init__(self, **kwargs):
super(QrScanner, self).__init__(**kwargs)
btn1 = Button(text='Scan Me', font_size="50sp")
btn1.bind(on_press=self.callback)
self.add_widget(btn1)
def callback(self, instance):
"""On click button, initiate zbarcam and schedule text reader"""
self.remove_widget(instance) # remove button
self.zbarcam = ZBarCam()
self.add_widget(self.zbarcam)
Clock.schedule_interval(self.read_qr_text, 1)
def read_qr_text(self, *args):
"""Check if zbarcam.symbols is filled and stop scanning in such case"""
if(len(self.zbarcam.symbols) > 0): # when something is detected
self.qr_text = self.zbarcam.symbols[0].data # text from QR
Clock.unschedule(self.read_qr_text, 1)
self.zbarcam.stop() # stop zbarcam
self.zbarcam.ids['xcamera']._camera._device.release() # release camera
class QrApp(App):
def build(self):
return QrScanner()
if __name__ == '__main__':
QrApp().run()
There is no way to lazy-load kivy Screens. Try this as a workaround: Do not let ScreenManager know about all Screens. Use switch_to to remove the current screen and load the desired one instead. Check Kivy docs to learn how to use it.
Maybe so?
from kivy_garden.zbarcam import ZBarCam
from kivymd.uix.textfield import MDTextField
class MenuScreen(MDScreen):
zbarcam: ZBarCam = ObjectProperty()
qrfield: MDTextField = ObjectProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.__cam_release()
def scan(self, instance):
self.__cam_release()
def on_touch_down(self, touch):
if self.zbarcam.collide_point(*touch.pos):
self.__cam_open()
return super().on_touch_down(touch)
def __cam_release(self):
if self.zbarcam.xcamera._camera._device.isOpened():
self.zbarcam.stop()
self.zbarcam.xcamera._camera._device.release()
def __cam_open(self):
if not self.zbarcam.xcamera._camera._device.isOpened():
self.zbarcam.xcamera._camera._device.open(0)
self.zbarcam.start()
The double tap works on the first code that uses a .kv file however I don't know how to get it to work on a code that doesn't use a .kv file. The second code below is generating MDCards as posts with a title, image, and subtitle. I want to add double-tap to that image only. How can I achieve this?
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.behaviors import TouchBehavior
from kivymd.uix.button import MDRaisedButton
KV = '''
Screen:
MyButton:
text: "PRESS ME"
pos_hint: {"center_x": .5, "center_y": .5}
'''
class MyButton(MDRaisedButton, TouchBehavior):
def on_double_tap(self, *args):
print("<on_double_tap> event")
class MainApp(MDApp):
def build(self):
return Builder.load_string(KV)
MainApp().run()
How can I add a double-tap to the image within the MDCard?
from kivy.core.window import Window
from kivy.uix.scrollview import ScrollView
from kivymd.uix.card import MDCard
from kivymd.uix.gridlayout import MDGridLayout
from kivymd.app import MDApp
from kivymd.uix.behaviors import TouchBehavior
from kivymd.uix.button import MDRaisedButton
from kivymd.uix.label import MDLabel
from KivaMD.kivymd.utils.fitimage import FitImage
Window.size = (440, 760)
class MyButton(MDRaisedButton, TouchBehavior):
def on_double_tap(self, *args):
print("<on_double_tap> event")
class MainApp(MDApp):
def build(self):
blog = MDGridLayout(cols=1, spacing=40, size_hint_y=None, padding=[20,], md_bg_color=[0,0,0.1,.2])
blog.bind(minimum_height=blog.setter('height'))
for i in range(10):
post = MDCard(size_hint=(.9, None), size=(300, 300), radius=10)
container = MDGridLayout(cols=1)
post.add_widget(container)
container.add_widget(MDLabel(text="Title", halign='center'))
#ADD DOUBLE TAP TO THIS IMAGE
container.add_widget(FitImage(source="data/assets/img/placeholder.jpg", size_hint_y=None, height=200))
container.add_widget(MDLabel(text="Subtitle", halign='center'))
blog.add_widget(post)
scroll = ScrollView(size_hint=(1, None), size=(Window.width, Window.height))
scroll.add_widget(blog)
return scroll
MainApp().run()
Do it just as your show for the MyButton class:
class MyFitImage(FitImage, TouchBehavior):
def on_double_tap(self, *args):
print("MyFitImage: <on_double_tap> event")
Then use MyFitImage in place of FitImage in your code.
I created an application where, on start, a pop-up window appears asking for login credentials.
After providing the right credentials, this pop-up should close, so the "main window" behind it is accessible.
main.py:
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import ObjectProperty
from kivy.graphics import Rectangle
from kivy.graphics import Color
from kivy.graphics import Line
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.config import Config
from kivy.uix.popup import Popup
from kivy.clock import Clock
from kivy.core.window import Window
Config.set('graphics', 'width', '1024')
Config.set('graphics', 'height', '768')
class LoginWindow(Screen):
pass
class MainWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
class LoginPopup(Screen): # Popup Window
def login_popup(dt): # Function to call Popup Window
show = LoginPopup()
popupWindow = Popup(title="Please log in", content=show, size_hint=(None, None), size=(400, 125),
auto_dismiss=False)
popupWindow.open()
kv = Builder.load_file("my.kv")
class MainApp(App):
def dismiss(self):
self.dismiss()
def build(self):
Clock.schedule_once(LoginPopup.login_popup, 1) # Loading the login popup 1 second after initialising
return kv
if __name__ == "__main__":
MainApp().run()
my.kv
<LoginPopup>:
id: popupWindow
GridLayout:
rows: 2
FloatLayout:
size_hint: 1,0.5
rows: 1
cols: 2
Label:
pos: (0,40)
text: "Password: "
text_size: self.size
TextInput:
pos: (80,35)
size_hint_y: (.8)
size_hint_x: (.785)
password: True
id: password
multiline: False
Button:
id: login_button
text: "Login"
size_hint: 1,0.5
pos_hint: {"x":0,"y":0.1}
on_release:
root.login_popup.popupWindow.dismiss() if password.text == "XXX" else None
print(password.text)
Inside the my.kv I want to dismiss the pop-up with root.login_popup.popupWindow.dismiss() if password.text == "XXX" else None but I get an error that "'function' object has no attribute 'popupWindow'
Is this because the "popupWindow" object is instantiated with another name? How can I fix this?
(I took out some of the my.kv code of other screens as they are not in use.)
The popupWindow variable in your code is a local variable to the login_popup() method. You can make that a class level variable by changing your LoginPopup class to:
class LoginPopup(Screen): # Popup Window
popupWindow = None
def login_popup(dt): # Function to call Popup Window
show = LoginPopup()
LoginPopup.popupWindow = Popup(title="Please log in", content=show, size_hint=(None, None), size=(400, 125),
auto_dismiss=False)
LoginPopup.popupWindow.open()
Then you can access it in your kv as:
on_release:
root.popupWindow.dismiss() if password.text == "XXX" else None
print(password.text)
I`m trying to make a simple GUI with Kivy(1.9) using a popup to change some options from a list and save it to a db, for example. When i call popup(), Python(3.4.5) crash..
main.py:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.popup import Popup
from kivy.properties import ListProperty
from kivy.lang import Builder
Builder.load_string('''
<PopView>:
title: 'Popup'
size_hint: (.8, .8)
Button:
text: 'Save'
''')
class MainApp(App):
def build(self):
b = Button(text='click to open popup')
b.bind(on_click=self.view_popup())
return b
def view_popup(self):
a=PopView()
a.data=[1,2,3,4] #e.g.
a.open()
class PopView(Popup):
def __init__(self):
self.data = ListProperty()
def save_data(self):
#db.query(self.data)
pass
if __name__ in ('__main__', '__android__'):
MainApp().run()
Here are a couple of things.
First, if you are going to overite __init__ remember to call super
But in this simple case you dont need __init__
Then, there is no on_click event on Button. Use on_press or on_release
And last but not least: You dont need to call the method in the bind function. Only pass it (without ())
So now your example looks like this.
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.popup import Popup
from kivy.properties import ListProperty
from kivy.lang import Builder
Builder.load_string('''
<PopView>:
title: 'Popup'
size_hint: (.8, .8)
Button:
text: 'Save'
''')
class MainApp(App):
def build(self):
b = Button(text='click to open popup')
b.bind(on_release=self.view_popup)
return b
def view_popup(self,*args):
a = PopView()
a.data=[1,2,3,4] #e.g.
a.open()
class PopView(Popup):
data = ListProperty()
def save_data(self):
#db.query(self.data)
pass
if __name__ in ('__main__', '__android__'):
MainApp().run()