My Kivy app has a button whose callback involves a UrlRequest. I'd like to provide a popup that askes the user to wait while the request is being completed. The problem is that executing the request itself blocks the popup from coming up. I've tried placing the open() method of the popup at different places with no luck. In the following example, the popup is opened in the on_progress() callback of the UrlRequest:
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
from kivy.garden.navigationdrawer import NavigationDrawer
from kivy.network.urlrequest import UrlRequest
from kivy.uix.popup import Popup
from kivy.uix.label import Label
import urllib
kv = '''
<ScreenTemplate#SignUpScreen>:
canvas:
Color:
rgb: (0.09,0.65,0.8)
Rectangle:
pos: self.pos
size: self.size
Button:
size_hint: .3, .2
pos_hint: {'center_x': .5,'center_y': .25}
text: 'Call Request'
# on_press: app.p.open()
on_release: app.callback1()
Label:
text: 'This is ' + root.name
font_size: '50sp'
<MyNavDrawer>:
BoxLayout:
orientation: 'vertical'
size_hint_y: .25
pos_hint: {'center_y':.5}
Button:
text: 'Screen 1'
on_press: app.callback2( 'screen1')
Button:
text: 'Screen 2'
on_press: app.callback2('screen2')
SMRoot:
<SMRoot>:
ScreenTemplate:
name: 'screen1'
ScreenTemplate:
name: 'screen2'
'''
Builder.load_string(kv)
class SMRoot(ScreenManager):
pass
class SignUpScreen(Screen):
pass
class myNavDrawer(NavigationDrawer):
pass
class myApp(App):
popup_opened = False
p=Popup(title="Posting request...",
content=Label(text="... Please wait"),
size=(100, 100),
size_hint=(0.5, 0.5),
auto_dismiss = False)
def build(self):
self.mynavdrawer = myNavDrawer()
return self.mynavdrawer
def on_success(self, req, results):
print 'In on_success: '+ results
self.p.title = 'Success'
self.p.content = Label(text=results)
self.p.auto_dismiss = True
popup_opened = False
def on_failure(self, req, results):
self.p.title = 'Failure'
self.p.content = Label(text=results)
print 'In on_failure: '+ results
self.p.auto_dismiss = True
def on_error(self, req, results):
self.p.title = 'Error'
self.p.content = Label(text=results.strerror)
print 'In on_error: '+ results.strerror
self.p.auto_dismiss = True
def on_progress (self, req, results, chunk):
if not self.popup_opened:
print 'In on_progress: '+ str(results)
self.p.open()
self.popup_opened = True
def callback1(self):
params={'show_env':'1'}
params = urllib.urlencode(params)
headers = {'Content-type': 'application/x-www-form-urlencoded',
'Accept': 'text/plain'}
url = 'https://httpbin.org/get'
req = UrlRequest(url,
self.on_success,
req_body = params,
req_headers = headers,
on_failure=self.on_failure,
on_error=self.on_error,
on_progress=self.on_progress,
timeout=4)
req.wait()
print 'After UrlRequest'
if __name__ == '__main__':
myApp().run()
Running this causes the popup to appear after the request is completed, which defeats the purpose.
Notice the commented on_press: app.p.open() binding in the Button under <ScreenTemplate#SignUpScreen>. This works perfectly, except that it's a workaround that is far from ideal. I'd like the popup to be opened anytime the UrlRequest is sent; the above workaround has to be applied to every button.
Any ideas on how to open the button from the main thread while the callback is running would be appreciated. I've tried using Clock.schedule_once() as well. From the docs, it seems EventDispatcher.dispatch() might do the trick, but I don't know what event to dispatch.
I think this line:
req.wait()
blocks you on the main thread.
remove it. you can also open the popup sooner - instead of the req.wait()
That being said, you can replicate this behavior using delayable (from kivyoav)
#delayable
def callback1(self):
...
while not reg.is_finished:
yield 0.02 # sleep for 20 ms...
... #req is ready now...
Disclaimer: I'm the author of kivyoav
I had the same issue as you do. The UrlRequest takes place in the main thread. This means that the UI won't be updated unless the main thread is allowed to run, because it's blocked inside the callback1 function.
What you want to do is create a new thread for the UrlRequest
Try using Threading
I solved a similar problem using threads. Any other attempts finished up the GIL blocking the popup to show or showing it after the operating method. I use def showTheMessage(self): when I want a popup to show until the method in the other thread is done.
# main screen
class MainScreen(Screen):
def __init__(self, **kwargs):
self.name="MAIN SCREEN"
super(Screen, self).__init__(**kwargs)
# popup class
class MsgBox(Popup):
def __init__(self, obj, **kwargs):
super(MsgBox, self).__init__(**kwargs)
self.obj = obj
# app class
class MyApp(App):
def step1(self):
# start the method in a thread
t1 = threading.Thread(target = self.yourBelowedMethod)
t1.start()
t1.join()
# dismiss the message popup once finished
self.popupMsg.dismiss()
def step2(self, *args):
# set the popup structure
self.popupMsg = MsgBox(self)
# run the activity popup in a thread
t3 = threading.Thread(target = self.popupMsg.open)
t3.start()
t3.join()
def showTheMessage(self):
# call step2 and step1
self.step2() # call the message popup
self.step1() # call the method
def build(self):
sm = Builder.load_string("""
ScreenManager
canvas:
Color:
rgb: 1, 1, 1
Rectangle:
size: self.size
MainScreen:
size_hint: 1, 1
auto_dismiss: False
title: "MainScreenTitle"
title_align: "center"
BoxLayout:
orientation: "vertical"
spacing: 10
Label:
text: "MainScreenLabel"
BoxLayout:
orientation: "horizontal"
spacing: 10
size_hint: 1, .8
BoxLayout:
orientation: "horizontal"
spacing: 10
size_hint: 1, .2
Button:
font_size: 50
text: "MessageButtonExit" # exit app
on_press:
self.background_color = 0,255,0,1
app.exit()
<MsgBox>:
size_hint: 1, .7
auto_dismiss: False
title: "yourTitle"
title_align: "center"
title_size: 30
BoxLayout:
orientation: "vertical"
Label:
font_size: '30sp'
text: "yourText"
BoxLayout:
orientation: "horizontal"
spacing: 10
size_hint: 1, .5
""")
return sm
Related
Kivy does not update the refreshed data on the screen.
If i restart the app, i can see the new data after calculation.
I want to see refreshed data on the screen after the calculation is done, without restarting the program again.
When i run the app, datas() function pulls the json file at the first time and also when i on the second calculation screen, before calculation, the Clock.schedule_once(self.datas) pulls the data again as well but sill i can't see the refreshed names on the screen.
PY File:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.metrics import dp
from kivy.uix.behaviors import ButtonBehavior
from kivy.clock import Clock, mainthread
import json
import threading
class Test(BoxLayout):
def __init__(self, **kwargs):
super(Test, self).__init__(**kwargs)
self.data = self.datas()
# Homepage Screen
def homepage(self, screenmanager):
screenmanager.current = 'homepage_screen'
Clock.schedule_once(self.clear_widgets)
# Clear Widgets
def clear_widgets(self, *args):
for child in [child for child in self.ids.gridsonuc.children]:
self.ids.gridsonuc.remove_widget(child)
# Under Over Screen
def second(self,screenmanager):
screenmanager.current = 'second_screen'
Clock.schedule_once(self.clear_widgets)
Clock.schedule_once(self.datas) # Before calculation, each time app pulls data again, but Kivy Does Not Update The Refreshed Data in The Screen!
Clock.schedule_once(self.calculate)
# or, if i can use threading system as well but this time i must add #mainthread above def calculate(self, *args): to make code work.
# in both scenario, Kivy Does Not Update The Refreshed Data in The Screen While APP is Running.
# mythread1 = threading.Thread(target=self.clear_widgets)
# mythread1.start()
# mythread2 = threading.Thread(target=self.datas)
# mythread2.start()
# mythread3 = threading.Thread(target=self.calculate)
# mythread3.start()
# Calculation
##mainthread
def calculate(self, *args):
for i in self.data['home']:
box = BoxLayout(size_hint_y = None, height = dp(50))
hometeams = Label(text = f'{[i]}', font_name = 'Roboto', font_size = dp(15), size_hint = (0.225, 1), halign='center', bold = True )
box.add_widget(hometeams)
self.ids.gridsonuc.add_widget(box)
def datas(self, *args):
# PLEASE CHANGE THE LOCATION!!!!!!!!!!!!!!!!!
with open ("C:\\Users\\Messi\\Desktop\\Python\\Projects\\Football Tips\\Kivy\\Testing Bugs\\Test1\\data.json", "r") as dosya:
dataApi = json.load(dosya)
print('datas updated')
return dataApi
class TestApp(App):
def build(self):
return Test()
if __name__ == '__main__':
TestApp().run()
KV File:
#:import NoTransition kivy.uix.screenmanager.NoTransition
<Test>:
ScreenManager:
transition: NoTransition()
id: sm
size: root.width, root.height
Screen:
name: 'homepage_screen'
BoxLayout:
size_hint: 1, 0.10
Button:
text: 'Calculate'
id: underOver_button_homepage
on_press: root.second(sm)
background_color: 0, 0, 0, 0
Screen:
name: 'second_screen'
BoxLayout:
spacing: '20dp'
orientation: 'vertical'
BoxLayout:
size_hint: 1, 0.80
ScrollView:
scroll_type: ['bars', 'content']
bar_margin: '5dp'
bar_color: 1, 0.4, 0.769, 1
bar_width: '5dp'
bar_inactive_color: 1, 0.4, 0.769, 1
GridLayout:
id: gridsonuc
cols: 1
spacing: '50dp'
size_hint_y: None
height: self.minimum_height
BoxLayout:
size_hint: 1, 0.10
Button:
text: 'Home'
id: home_button_underOver
on_press: root.homepage(sm)
background_color: 0, 0, 0, 0
data.json File
Please create a data.json file and PLEASE CHANGE THE LOCATION!!!!!!!!!!!!!!!!! in def datas(self, *args):
with open ("C:\Users\Messi\Desktop\Python\Projects\Football Tips\Kivy\Testing Bugs\Test1\data.json", "r") as dosya:
{"home": ["Manchester City", "Arsenal"]}
Video Of The Problem
https://www.youtube.com/watch?v=mMwryGLQ5SQ
Thanks for your help
The problem is that your display is created using self.data, but when you call the datas() method and read the updated json file, you don't do anything with the newly read data. Try changing:
def datas(self, *args):
# PLEASE CHANGE THE LOCATION!!!!!!!!!!!!!!!!!
with open ("C:\\Users\\Messi\\Desktop\\Python\\Projects\\Football Tips\\Kivy\\Testing Bugs\\Test1\\data.json", "r") as dosya:
dataApi = json.load(dosya)
print('datas updated')
return dataApi
to:
def datas(self, *args):
# PLEASE CHANGE THE LOCATION!!!!!!!!!!!!!!!!!
with open ("C:\\Users\\Messi\\Desktop\\Python\\Projects\\Football Tips\\Kivy\\Testing Bugs\\Test1\\data.json", "r") as dosya:
dataApi = json.load(dosya)
print('datas updated')
self.data = dataApi # update the self.data
return dataApi
I have to write a program in python using kivy that takes text in a textbox and passing it to function that do web scraping and a lot of things then return array of strings and the last element in the array is array of pairs so I am really confused and sent a lot of time .so I must write two kv files or just one file ? here is my simple kivy code as a start .
I tried but it is not working
#textInput.py
from app import *
Builder.load_file('textInput.kv')
require('1.10.0')
class MainScreen(BoxLayout):
def __init__(self):
self.super(MainScreen, self).__init__()
def btn_click(self):
name =self.BoxLayout.BoxLayout.TextInput
#the function that takes the output of the text field
get_company_name(name)
#
#
#
# here I will call the function that returns the array so how to pass the answer
# and also pass to where ? shall I use the same kv file or create another one
class Test(App):
def build(self):
self.title = 'CompanyInfoApp'
return MainScreen()
if __name__ == '__main__':
Test().run()
lbl: My_label
orientation: 'vertical'
# Third section title
Label:
size_hint: (1, .1)
text: 'Welcome To compnay info App'
font_size: 25
# Third section Box
BoxLayout:
Button:
text:"let's start"
on_press:root.btn_click()
size_hint: (1, .2)
padding: [180, 180, 180, 180]
BoxLayout:
Label:
pos_hint:{'x': .3, 'y': .6}
text: 'Enter the Company Name:'
text_size: self.width-20, self.height-20
TextInput:
height: self.minimum_height
pos_hint:{'x': .3, 'y': .6}
multiline: False
text: ''
Try to give your TextInput widget an id. Then you can access the text data of TextInput widget with the use of its id.
Here is a basic example:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import *
main_kv = """
<Main>:
orientation: 'vertical'
TextInput:
# TextInput's id
id: txtinput
text: ''
Button:
text: "print text"
on_press: root.btn_click()
"""
class Main(BoxLayout):
def btn_click(self):
name = self.ids['txtinput'].text
print(name)
class Test(App):
def build(self):
Builder.load_string(main_kv)
return Main()
Test().run()
How do I properly create active properties for switch_id and switch_id_popup so I can create a conditional statement inside of timer_loop with kivy clock?
I got great feedback on a similar question(thanks again eyllanesc), but I am unable to incorporate advice now that popup is involved.
Below is a sketch that illustrates my issue. I have identified all of the areas in question with arrows. Thank you in advance for any help.
from kivy.app import App
from kivy.lang import Builder
from kivy.clock import Clock
import time
from kivy.uix.popup import Popup
theRoot = Builder.load_string('''
<CustomPopup1>:
StackLayout:
active2: switch_id_popup.active #<---- switch_id_popup declared here(active2)
Switch: #<---- This switch (switch_id_popup)
id: switch_id_popup
size_hint: .5, .5
active: switch_id_popup.active
Button:
text: 'Close'
on_release: root.dismiss()
size_hint: .5, .5
StackLayout:
active1: switch_id.active #<---- switch_id declared here(active1)
orientation: 'lr-tb'
padding: 10
spacing: 5
Label:
text: "Zone 1 Valve"
size_hint: .25, .1
Switch: #<---- This switch (switch_id)
id: switch_id
size_hint: .25, .1
active: switch_id.active
Button:
text: "Program 1"
on_press: app.open_popup1()
size_hint: .5,.1
''')
class CustomPopup1(Popup):
pass
class theApp(App):
def build(self):
Clock.schedule_interval(self.timer_loop, 2)
return theRoot
def timer_loop(self, dt):
if theRoot.active1 and theRoot.active2: #<---- How do I make this work if switch_id_popup is within a popup?
print("Do something")
else:
print("Do nothing")
def open_popup1(self):
the_popup = CustomPopup1()
the_popup.open()
if __name__ == '__main__':
theApp().run()
As I mentioned in my previous solution, you should consider each class as a black box and expose properties that allow you to obtain and establish values. In the case of Popup, the active property must be part of the class and not the internal StackLayout. On the other hand in the open_popup method you are always creating a new Popup that will be deleted when you close it making the property is not accessible, so we deduce that there must be a Popup object with a larger scope for it must be created outside of theApp class or be a member of it. Lastly, active2 is not a property of theRoot.
Considering the above, the following solution is obtained:
from kivy.app import App
from kivy.lang import Builder
from kivy.clock import Clock
import time
from kivy.uix.popup import Popup
theRoot = Builder.load_string('''
<CustomPopup>:
active: switch_id_popup.active
StackLayout:
Switch:
id: switch_id_popup
size_hint: .5, .5
Button:
text: 'Close'
on_release: root.dismiss()
size_hint: .5, .5
StackLayout:
active: switch_id.active
orientation: 'lr-tb'
padding: 10
spacing: 5
Label:
text: "Zone 1 Valve"
size_hint: .25, .1
Switch:
id: switch_id
size_hint: .25, .1
Button:
text: "Program 1"
on_press: app.open_popup()
size_hint: .5,.1
''')
class CustomPopup(Popup):
pass
class TheApp(App):
def build(self):
self.the_popup = CustomPopup()
Clock.schedule_interval(self.timer_loop, 2)
return theRoot
def timer_loop(self, dt):
if self.root.active and self.the_popup.active:
print("Do something")
else:
print("Do nothing")
def open_popup(self):
self.the_popup.open()
if __name__ == '__main__':
TheApp().run()
I'm currently trying to create a customized MessageBox in Python 3.6 using Kivy 1.10.0. I want to use it first as a MessageBox for error message when user entered incorrect username or password.
I'm getting an attribute error whenever I call the open function from CalcPopUp class using the nextScreen function from CalcRoot class.
This is the codes that I have:
class CalcRoot(BoxLayout):
def __init__(self,**kwargs):
super(CalcRoot,self).__init__(**kwargs)
self.calc_popup = CalcPopUp(**kwargs)
def nextScreen(self, next_screen):
#I have some conditions inside this function which works fine
CalcPopUp.open(self, "Incorrect Login", True)`
class CalcPopUp(Popup):
popup_message = ObjectProperty()
popup_button = ObjectProperty()
def __init__(self, *args, **kwargs):
super(CalcPopUp,self).__init__(*args, **kwargs)
def open(self, app_message, with_button=True):
#if user selected the button attribute as true show button else remove
if with_button:
if self.popup_button in self.content.children:
self.content.remove_widget(self.popup_button)
# if answer is wrong, display button if not visible
else:
if self.popup_button not in self.content.children:
self.content.add_widget(self.popup_button)
#display text message
self.message.text = app_message
#display pop up
super(CalcPopUp, self).open()
This is the error that I'm getting:
AttributeError: 'CalcRoot' object has no attribute 'popup_button'
This is the content of the kivy file associated to my screenpop:
<CalcPopUp>:
size_hint: .8, .4
title: "Message"
title_size: root.height *.05
auto_dismiss: False
separator_color: COLOR("#fcfc02") #yellow
popup_button: popup_button
popup_message: popup_message
BoxLayout:
orientation: 'horizontal'
padding: root.width * .02, root.height * .02
spacing: min(root.height, root.width) * .02
Label:
id: popup_message
text: ""
halign: 'left'
font_size: root.height / 10
center_y: .5
markup: True
Button:
id: popup_button
text: 'Ok'
size_hint: 1, None
height: root.height / 20
on_release: root.dismiss()
Here's what I did:
First of all, remove lines 7 and 8 in the .kv file. I'm not sure whether there is an indentation error in your original post, but here's how the .kv file should look now:
<CalcPopUp>:
size_hint: .8, .4
title: "Message"
title_size: root.height *.05
auto_dismiss: False
BoxLayout: # this is indented to be inside CalcPopUp
orientation: 'horizontal'
... # etc., no changes other than indentation...
I've changed the .py file structure quite a bit, take a look and tell me if there's anything I need to make explain:
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.base import runTouchApp
from kivy.base import Builder
class CalcRoot(BoxLayout):
def __init__(self, **kwargs):
super(CalcRoot, self).__init__(**kwargs)
def nextScreen(self, next_screen):
# I have some conditions inside this function which works fine
popup = CalcPopUp("Incorrect Login", True)
popup.open()
class CalcPopUp(Popup):
popup_message = ObjectProperty()
popup_button = ObjectProperty()
def __init__(self, app_message, with_button=True, **kwargs):
super().__init__(**kwargs)
# if user selected the button attribute as true show button else remove
if with_button:
if self.popup_button in self.content.children:
self.content.remove_widget(self.popup_button)
# if answer is wrong, display button if not visible
else:
if self.popup_button not in self.content.children:
self.content.add_widget(self.popup_button)
# display text message
self.ids.popup_message.text = app_message
Builder.load_file("calcpopup.kv")
root = CalcRoot()
root.nextScreen(next_screen=None)
runTouchApp(root)
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.