I want to hide the progress bar at below code as soon as screen appears. In order to do that I defined a hide_widget function and added the code to "on_pre_enter" section. However it doesn't work. What am I missing here ?
By the way if you can introduce a shorter way to hide a widget in kivy, it is also very welcome.
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
Builder.load_string("""
<MyLayout>
BoxLayout:
orientation: "vertical"
size: root.width, root.height
spacing: 20
padding: 50
Button:
text: "Test"
ProgressBar:
id: pgb_excel_read
min: 0
max: 100
value: 0
size_hint: ( 1, 0.1)
""")
def hide_widget(wid, dohide=True):
if hasattr(wid, 'saved_attrs'):
if not dohide:
wid.height, wid.size_hint_y, wid.opacity, wid.disabled = wid.saved_attrs
del wid.saved_attrs
elif dohide:
wid.saved_attrs = wid.height, wid.size_hint_y, wid.opacity, wid.disabled
wid.height, wid.size_hint_y, wid.opacity, wid.disabled = 0, None, 0, True
class MyLayout(Widget):
def on_pre_enter(self, *args):
hide_widget(self.ids.pgb_excel_read, True)
class MyApp(App):
def build(self):
return MyLayout()
if __name__ == '__main__':
MyApp().run()
on_pre_enter function is only avaliable for Screen classes. But you try to use it in your custom Widget. So there is a nothing to trigger this function. We can trigger it by __init__:
class MyLayout(Widget):
def __init__(self):
super(MyLayout, self).__init__()
hide_widget(self.ids.pgb_excel_read, True)
For hide any widget we just need to change 2 things:
Disabled : True
Opacity : 0
Short way to do that, lets stop triggering any function and just change variables: (Also Remove hide_widget function)
class MyLayout(Widget):
def __init__(self):
super(MyLayout, self).__init__()
# hide_widget(self.ids.pgb_excel_read, True)
self.ids.pgb_excel_read.opacity = 0
self.ids.pgb_excel_read.disabled = True
I want to create a list of coloured labels. The thing is that I could do it with the kv file, but I need to do it through the build() method. So I tried replicate what I have done, but it does not work. And I can't understand why.
This is what I've coded
from kivy.app import App
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.graphics import *
class RL(RelativeLayout): # Creates the background colour for each label
def __init__(self, **kwargs):
super().__init__(**kwargs)
with self.canvas:
Color(.7, 0, .5, 1)
Rectangle(size_hint=self.size)
class MainMenu(BoxLayout):
N_LBLS = 8
labels_text = []
RL_list = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
button = Button(text='do something')
button.bind(on_release=self.change_text)
box = BoxLayout(orientation='vertical', padding= 10, spacing = 15)
for i in range(0, self.N_LBLS):
self.RL_list.append(RL())
self.labels_text.append(Label(text=f'{i}º label', size_hint=self.size))
self.RL_list[i].add_widget(self.labels_text[i])
box.add_widget(self.RL_list[i])
self.add_widget(button)
self.add_widget(box)
def change_text(self, instance):
for lbl in self.labels_text:
if lbl.text[0] == '5':
lbl.text = 'Text changed'
class MainApp(App):
def build(self):
return MainMenu()
if __name__ == '__main__':
MainApp().run()
It's supposed to make a button to the left, and a list of 8 coloured labels to the right.
The problem is that you are setting size_hint=self.size in each Label. The self.size is the size of the MainMenu, which is [100,100] when that code is executed. Note that size_hint is a multiplier that is applied to the parents size to calculate the widgets size. So a size_hint of [100,100] makes each Label 100 times bigger than the MainMenu. So your code is working, but the Labels are so large that the text is off the screen. Start by just removing size_hint=self.size.
And, to set a background color on a Label, you can just use the canvas of that Label, rather than some container. Here is a version of your code that does that:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
class ColorLabel(Label):
pass
Builder.load_string('''
<ColorLabel>:
bg_color: [.7, 0, .5, 1]
canvas.before:
Color:
rgba: self.bg_color
Rectangle:
pos: self.pos
size: self.size
''')
class MainMenu(BoxLayout):
N_LBLS = 8
labels_text = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
button = Button(text='do something')
button.bind(on_release=self.change_text)
box = BoxLayout(orientation='vertical', padding=10, spacing=15)
for i in range(0, self.N_LBLS):
self.labels_text.append(ColorLabel(text=f'{i}º label'))
box.add_widget(self.labels_text[i])
self.add_widget(button)
self.add_widget(box)
def change_text(self, instance):
for lbl in self.labels_text:
if lbl.text[0] == '5':
lbl.text = 'Text changed'
lbl.bg_color = [0, 1, 0, 1]
class MainApp(App):
def build(self):
return MainMenu()
if __name__ == '__main__':
MainApp().run()
I'm trying to get a live graph working in my code.
I can get a live graph working when everything is within the same python file, though when I try to separate the graph class into its own python file, the graph doesn't update. I have no real indication of why the graph isn't updating, but I think I may be creating a bunch of LogGraph objects as opposed to adding points to the actual LogGraph which is in my.kv, but I am not sure how to not do that.
My actual code is a bit involved and confusing, so I mocked up an example code here which should be behaving exactly the same:
main.py
from math import sin
import kivy
from kivy_garden.graph import Graph, MeshLinePlot
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from data import startdata
class MainWindow(Screen):
def pressrun(self):
self.ids.runlbl.text = 'Started'
startdata()
pass
class MyApp(App):
def build(self):
wm.add_widget(MainWindow())
return wm
class WindowManager(ScreenManager):
def __init__(self, **kwargs):
super(WindowManager, self).__init__(**kwargs)
wm = WindowManager()
kv = Builder.load_file("my.kv")
MyApp().run()
my.kv
#: import LogGraph graphs.LogGraph
<MainWindow>:
id: main
BoxLayout:
orientation: "vertical"
LogGraph:
BoxLayout:
orientation: "horizontal"
Button:
text: "Start Data Gen"
on_release:
root.pressrun()
Label:
id: runlbl
text: "Not Started"
graphs.py
from kivy_garden.graph import Graph, MeshLinePlot
class LogGraph(Graph):
def __init__(self, **kwargs):
super(LogGraph, self).__init__(**kwargs)
self.xlabel = 'X'
self.ylabel = 'Y'
self.x_ticks_major = 25
self.x_ticks_minor = 5
self.x_grid_label = True
self.y_ticks_major = 1
self.y_grid_label = True
self.xmin = 0
self.xmax = 100
self.ymin = 0.1
self.ymax = 10
self.ylog = True
self.x_grid = True
self.y_grid = True
self.plot = MeshLinePlot(color=[1, 0, 0, 1])
self.add_plot(self.plot)
self.plot.points = [(1,1)]
def update_xaxis(self,xmin = 0):
self.xmin = xmin
self.xmax = xmin + 10
def update_yaxis(self,ymin = 0):
self.ymin = ymin
self.ymax = ymin + 10
def update_points(self, point, *args):
self.plot.points.append([point,point])
# x axis resize
if point > self.xmax:
self.update_xaxis(self.xmax)
# y axis resize
if point > self.ymax:
self.update_yaxis(self.ymax)
data.py
from kivy.clock import Clock
from functools import partial
from graphs import LogGraph
class DataStore():
def __init__(self):
self.i = 1
self.dataarray = []
def start(self):
self.clock = Clock.schedule_interval(self.getData, 1/60)
def cancel(self):
self.clock.cancel()
def wait(self):
print('Waited!')
def getData(self):
i = self.i + 1/60
LogGraph.update_points(LogGraph(), i)
pass
def startdata():
ds = DataStore()
ds.start()
Three main problems with your code:
Your code kv = Builder.load_file("my.kv") is loading the my.kv file a second time. It will be loaded automatically because the file is named correctly for that to happen. You should eliminate that code.
Your scheduled calls to DataStore.getData() will not work because your DataStore instance is not saved anywhere, and so it gets garbage collected.
The getData() method of DataStore creates a new instance of LogGraph each time it runs, but does not use the instance of LogGraph that is in your GUI.
To fix these problems, start by adding to your kv to allow access:
#: import LogGraph graphs.LogGraph
<MainWindow>:
id: main
name: 'main' # added to enable access
BoxLayout:
orientation: "vertical"
LogGraph:
id: graph # added to enable access
BoxLayout:
orientation: "horizontal"
Button:
text: "Start Data Gen"
on_release:
root.pressrun()
Label:
id: runlbl
text: "Not Started"
Then in the startdata() method, add a return
def startdata():
ds = DataStore()
ds.start()
# return the DataStore instance so it can be saved
return ds
Then save the returned DataStore in the pressrun() method:
def pressrun(self):
self.ids.runlbl.text = 'Started'
self.dataStore = startdata()
And the getData() method must be modified to access the LogGraph that is in the GUI:
def getData(self, dt): # time interval argument is required
self.i += dt
# access the LogGraph instance in the GUI
lg = App.get_running_app().root.get_screen('main').ids.graph
lg.update_points(self.i)
# LogGraph.update_points(LogGraph(), i)
Immediately after the super() function is called, it creates a duplicate WidgetClass instance.
My understanding of the super() I've used is that it refers to the EditImageLayout class to inherit from. To this end I've tried to implement different variations of the super() but admittedly I'm only guessing at this stage.
Updated to full working, I've cut out a few hundred lines
Run as > OK > Select an Image > Open > Crop (dual instances start running here)
App_R3App.py
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.widget import Widget
from kivy.graphics import Line, Color
from kivy.properties import NumericProperty, ObjectProperty, StringProperty, ListProperty
from kivy.uix.image import Image
import io
from kivy.core.image import Image as CoreImageKivy
from kivy.uix.bubble import Bubble
from kivy.core.window import Window
__version__ = '0.1'
class FirstScreen(Screen):
pass
class SecondScreen(Screen):
def hl(self, image_address):
self.new_image_address = image_address # for the sake of this
self.callback_image(self.new_image_address, image_address, "Auto-cropped image")
def callback_image(self, new_image_address_tmp, image_address_tmp, title):
if new_image_address_tmp:
third_screen = self.manager.get_screen("_third_screen_")
new_image_address_tmp = [k.replace("\\", "/") for k in new_image_address_tmp]
third_screen.callback_image(new_image_address_tmp[0], image_address_tmp[0], title)
class ThirdScreen(Screen, BoxLayout):
# class attribute references
image_size = (0, 0)
image_pos = (0, 0)
image_address = ""
new_image_address = ""
title = "Upload"
rect_box = ObjectProperty(None)
t_x = NumericProperty(0.0)
t_y = NumericProperty(0.0)
x1 = y1 = x2 = y2 = NumericProperty(0.0)
def __init__(self, **kwargs):
super(ThirdScreen, self).__init__(**kwargs)
pass
def callback_image(self, new_image_address, image_address, title):
sm.current = "_third_screen_"
self.new_image_address = new_image_address
self.image_address = image_address
self.title = title
self.ids.main_image.source = self.new_image_address
self.ids.main_title.text = self.title
def enable_cropping(self):
# overwrite class attributes
ThirdScreen.image_address = self.image_address
ThirdScreen.new_image_address = self.new_image_address
print("enable_cropping")
sm.current = "_edit_image_screen_"
return True
class EditImageScreen(Screen):
def __init__(self, **kwargs):
print("EditImageScreen")
super(EditImageScreen, self).__init__(**kwargs)
self.layout = None
def on_pre_enter(self):
print("on_pre_enter")
self.layout = EditImageLayout()
self.add_widget(self.layout)
class EditImageLayout(FloatLayout):
color_button = ListProperty([1, .3, .4, 1])
button_color = ListProperty([0, 0, 0, 1])
rectangle_selector = ObjectProperty()
text_size_rectangle = ObjectProperty()
image_layout = ObjectProperty()
bubble_buttons = ObjectProperty()
bubble_buttons_undo_confirm = ObjectProperty()
def __init__(self, **kwargs):
print("EditImageLayout")
self.sm = kwargs.pop('sm', None)
self.crop_image_screen = kwargs.pop('crop_image_screen', None)
# This is where the problem occurs
super(EditImageLayout, self).__init__(**kwargs)
self.rectangle_selector.bind(size_selected=self.on_change_size_rectangle_selector)
self.rectangle_selector.bind(size_selected_temp=self.update_text_size_rectangle)
self.bind(on_touch_down=self.bubble_buttons.hide)
self.bubble_buttons.resize_button.bind(on_press=self.on_press_resize_button)
self.bubble_buttons_undo_confirm.undo_button.bind(on_press=self.on_press_undo_button)
self.bubble_buttons_undo_confirm.confirm_button.bind(on_press=self.on_press_confirm_button)
def on_change_size_rectangle_selector(self, instance, size_selected):
print("on_change_size_rectangle_selector")
if not self.rectangle_selector.tap_not_draw_a_line():
self.bubble_buttons.show()
else:
self.text_size_rectangle.text = ''
def on_press_resize_button(self, instance):
print("on_press_resize_button")
self.image_layout.resize_image(width=self.rectangle_selector.size_selected[0],
height=self.rectangle_selector.size_selected[1])
self.rectangle_selector.delete_line()
self.text_size_rectangle.text = ''
self.bubble_buttons_undo_confirm.show()
def on_press_undo_button(self, instance):
print("on_press_undo_button")
size = self.image_layout.old_size
self.image_layout.resize_image(width=size[0], height=size[1])
self.bubble_buttons_undo_confirm.hide()
def on_press_confirm_button(self, instance):
print("on_press_confirm_button")
self.bubble_buttons_undo_confirm.hide()
def update_text_size_rectangle(self, instance, size):
print("update_text_size_rectangle")
self.text_size_rectangle.text = str('({0}, {1})'.format(int(size[0]), int(size[1])))
class ImageLayout(Image):
image = ObjectProperty()
path_image = StringProperty('image_tmp.jpg')
path_image_tmp = StringProperty('image_tmp.jpg')
old_size = ListProperty([0, 0])
def __init__(self, **kwargs):
print("ImageLayout")
super(ImageLayout, self).__init__(**kwargs)
self.path_image = ThirdScreen.image_address
self.image = CoreImage(self.path_image,
data=io.BytesIO(open(self.path_image, "rb").read()),
ext=self.path_image[self.path_image.rfind('.') + 1::])
self.source = self.path_image
def resize_image(self, width, height, pos_x=None, pos_y=None):
pos_x, pos_y = abs(Window.width - width)/2 , abs(Window.height - height)/2
self.image.resize(self.path_image,
self.path_image_tmp,
int(width),
int(height))
self.source = self.path_image_tmp
self.pos = pos_x, pos_y
self.old_size = self.size
self.size = width, height
self.reload()
class CoreImage(CoreImageKivy):
def __init__(self, arg, **kwargs):
print("CoreImage")
super(CoreImage, self).__init__(arg, **kwargs)
def resize(self, fname, fname_scaled, width, height):
try:
img = Image.open(fname)
except Exception as e:
print('Exception: ', e)
return
img = img.resize((width, height), Image.ANTIALIAS)
try:
img.save(fname_scaled)
except Exception as e:
print('Exception: ', e)
return
class TouchSelector(Widget):
# Points of Line object
Ax = NumericProperty(0)
Ay = NumericProperty(0)
Bx = NumericProperty(0)
By = NumericProperty(0)
Cx = NumericProperty(0)
Cy = NumericProperty(0)
Dx = NumericProperty(0)
Dy = NumericProperty(0)
# Object line
line = ObjectProperty()
# List of line objects drawn
list_lines_in_image = ListProperty([])
# Size of the selected rectangle
size_selected = ListProperty([0, 0])
# Size previous of the selected rectangle
size_selected_previous = ListProperty([0, 0])
# Size temporary of the selected rectangle
size_selected_temp = ListProperty([0, 0])
# Line Color and width
line_color = ListProperty([0.2, 1, 1, 1])
line_width = NumericProperty(1)
# First tap in TouchSelector
first_tap = True
def __init__(self, *args, **kwargs):
super(TouchSelector, self).__init__(*args, **kwargs)
self.bind(list_lines_in_image=self.remove_old_line)
def on_touch_up(self, touch): # on button up
self.size_selected = abs(self.Cx - self.Dx), abs(self.Cy - self.By)
self.size_selected_previous = self.size_selected
print(self.Dx, self.Dy, self.Cx, self.Cy)
def on_touch_down(self, touch):
with self.canvas:
Color(self.line_color)
# Save initial tap position
self.Ax, self.Ay = self.first_touch_x, self.first_touch_y = touch.x, touch.y
# Initilize positions to save
self.Bx, self.By = 0, 0
self.Cx, self.Cy = 0, 0
self.Dx, self.Dy = 0, 0
# Create initial point with touch x and y postions.
self.line = Line(points=([self.Ax, self.Ay]), width=self.line_width, joint='miter', joint_precision=30)
# Save the created line
self.list_lines_in_image.append(self.line)
print("on_touch_down")
def remove_old_line(self, instance=None, list_lines=None):
if len(self.list_lines_in_image) > 1:
self.delete_line()
def delete_line(self, pos=0):
try:
self.list_lines_in_image.pop(pos).points = []
except:
pass
def on_touch_move(self, touch):
# Assign the position of the touch at the point C
self.Cx, self.Cy = touch.x, touch.y
# There are two known points A (starting point) and C (endpoint)
# Assign the positions x and y known of the points
self.Bx, self.By = self.Cx, self.Ay
self.Dx, self.Dy = self.Ax, self.Cy
# Assign points positions to the last line created
self.line.points = [self.Ax, self.Ay,
self.Bx, self.By,
self.Cx, self.Cy,
self.Dx, self.Dy,
self.Ax, self.Ay]
self.size_selected_temp = abs(self.Cx - self.Dx), abs(self.Cy - self.By)
def tap_not_draw_a_line(self):
return (self.size_selected[0] == 0 and self.size_selected[1] == 0)
class BaseBubbleButtons(Bubble):
def __init__(self, **kwargs):
super(BaseBubbleButtons, self).__init__(**kwargs)
def hide(self, instance=None, value=None):
self.opacity = 0
def show(self, instance=None, value=None):
self.opacity = 1
class BubbleButtons(BaseBubbleButtons):
resize_button = ObjectProperty()
cut_button = ObjectProperty()
rotate_button = ObjectProperty()
class BubbleButtonsUndoConfirm(BaseBubbleButtons):
undo_button = ObjectProperty()
confirm_button = ObjectProperty()
class App_R3App(App):
Builder.load_file('App_R3.kv')
def build(self):
return sm
def on_start(self):
return True
def on_pause(self):
return True
def on_resume(self):
return True
def on_stop(self):
return True
if __name__ == '__main__':
# Create the screen manager
sm = ScreenManager()
sm.add_widget(FirstScreen(name='_first_screen_'))
sm.add_widget(SecondScreen(name='_second_screen_'))
sm.add_widget(ThirdScreen(name='_third_screen_'))
sm.add_widget(EditImageScreen(name='_edit_image_screen_'))
App_R3App().run()
App_R3.kv
#:import Window kivy.core.window.Window
<MyScreenManager>:
FirstScreen:
id: first_screen
SecondScreen:
id: second_screen
ThirdScreen:
id: third_screen
EditImageScreen:
id: edit_image_screen
<FirstScreen>:
name: '_first_screen_'
BoxLayout:
orientation: "horizontal"
Label:
id: first_screen_label
text: "Hi, I'm the home page"
BoxLayout:
orientation: "vertical"
Button:
text: "Okay!"
on_press: root.manager.current = '_second_screen_'
Button:
text: "Cancel!"
on_press: app.stop()
<SecondScreen>:
name: '_second_screen_'
id: file_chooser
BoxLayout:
id: file_chooser_box_layout
orientation: "horizontal"
Button
text: "Open"
on_press: root.hl(file_chooser_list_view.selection)
FileChooserListView:
id: file_chooser_list_view
<ThirdScreen>:
name: '_third_screen_'
id: third_screen
xx1: root.x1
yy1: root.y1
tt_x: root.t_x
tt_y: root.t_y
BoxLayout:
orientation: "vertical"
id: third_screen_boxlayout
Label:
id: main_title
text: root.title
size_hint: (1, 0.1)
BoxLayout:
id: image_box_layout
# limits the box layout to the position of the image
Image:
id: main_image
source: root.image_address
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
BoxLayout:
id: button_boxlayout
orientation: "horizontal"
padding: 10
size_hint: (1, 0.15)
Button:
id: accept_button
text: "Okay"
size_hint: (0.33, 1)
on_press: root.image_accepted_by_user(root.image_address)
Button:
id: crop_button
text: "Crop"
size_hint: (0.33, 1)
on_press: root.enable_cropping()
Button:
id: cancel_button
text: "Cancel"
size_hint: (0.33, 1)
on_press: root.manager.current = '_first_screen_'
<EditImageLayout>:
rectangle_selector: rectangle_selector
text_size_rectangle: text_size_rectangle
image_layout: image_layout
bubble_buttons: bubble_buttons
bubble_buttons_undo_confirm: bubble_buttons_undo_confirm
canvas.before:
Color:
rgba: (1, 1, 1, 0.2)
Rectangle:
pos: self.pos
size: Window.width, Window.height
Label:
id: text_size_rectangle
pos_hint_x: None
pos_hint_y: None
pos: Window.width*.45, Window.height*.45
color: (1, 1, 1, 1)
ImageLayout:
id: image_layout
size: Window.width*.8, Window.height*.8
pos: Window.width*.1, Window.height*.1
size_hint: None, None
pos_hint_x: None
pos_hint_y: None
TouchSelector:
id: rectangle_selector
BubbleButtons:
id: bubble_buttons
size_hint: (None, None)
size: (200, 40)
pos_hint_x: None
pos_hint_y: None
pos: Window.width*.4, Window.height*.1
opacity: 0
arrow_pos: 'top_mid'
BubbleButtonsUndoConfirm:
id: bubble_buttons_undo_confirm
size_hint: (None, None)
size: (200, 40)
pos_hint_x: None
pos_hint_y: None
pos: Window.width*.4, Window.height*.9
opacity: 0
arrow_pos: 'top_mid'
<BubbleButtons>:
resize_button: resize_button
cut_button: cut_button
rotate_button: rotate_button
BubbleButton:
id: resize_button
text: 'Resize'
BubbleButton:
id: cut_button
text: 'Cut'
BubbleButton:
id: rotate_button
text: 'Rotate'
<BubbleButtonsUndoConfirm>:
undo_button: undo_button
confirm_button: confirm_button
BubbleButton:
id: undo_button
text: 'Undo'
BubbleButton:
id: confirm_button
text: 'Confirm'
Console output, aka what the code prints (you can see that ImageLayout and CoreImage run twice)
EditImageScreen
enable_cropping
on_pre_enter
EditImageLayout
ImageLayout
CoreImage
ImageLayout
CoreImage
What I suspect is happening is that the super() is calling the base class EditImageLayout, the static elements of that base class are calling the .kv file and initating the ImageLayout and CoreImage classes from there. At the same time, "self" goes into action and does the exact same thing. This causes trouble later on when I implement on_touch_down over it (on_touch_down then appears twice, etc. )
Problem
ImageLayout and CoreImage were called twice.
[INFO ] [GL ] Unpack subimage support is available
enable_cropping
on_pre_enter
EditImageLayout
ImageLayout
CoreImage
ImageLayout
CoreImage
[INFO ] [Base ] Leaving application in progress...
Process finished with exit code 0
Root Cause
The double calls to ImageLayout and CoreImage were due to two kv files, App_R3.kv and app_r3.kv present as shown in the screen shots below. In the application, the app class is App_R3App(App): and it is using Builder.load_file('App_R3.kv') to load kv code into application.
Solution
Remove kv file, app_r3.kv. In the example, we renamed it to app_r3-off.kv
Kv language » How to load KV
There are two ways to load Kv code into your application:
By name convention:
Kivy looks for a Kv file with the same name as your App class in
lowercase, minus “App” if it ends with ‘App’ e.g:
MyApp -> my.kv
If this file defines a Root Widget it will be attached to the App’s
root attribute and used as the base of the application widget tree.
By Builder:
You can tell Kivy to directly load a string or a file. If this string
or file defines a root widget, it will be returned by the method:
Builder.load_file('path/to/file.kv')
or:
Builder.load_string(kv_string)
Recommendations
Since class rule, <MyScreenManager>: is defined in your kv file, you should use it rather than define sm = ScreenManager() in your Python code. On top of that, keep the view/design separate.
kv file
Add missing class rule for EditImageScreen.
<EditImageScreen>:
name: '_edit_image_screen_'
<EditImageLayout>:
Python code
Add class definition for MyScreenManager
In build() method, replace return sm with return MyScreenManager()
Before App_R3().run(), remove all references to sm
Since each screen has by default a property manager that gives you the instance of the ScreenManager used. In callback_image() and enable_cropping() methods, replace sm.current with self.manager.current
In __init__() method of EditImageLayout() class, remove self.sm = kwargs.pop('sm', None). If you need to access the root/ScreenManager, use sm = App.get_running_app().root
Snippet
class MyScreenManager(ScreenManager):
pass
...
def callback_image(self, new_image_address, image_address, title):
self.manager.current = "_third_screen_"
self.new_image_address = new_image_address
...
def enable_cropping(self):
...
print("enable_cropping")
self.manager.current = "_edit_image_screen_"
...
def __init__(self, **kwargs):
print("EditImageLayout")
self.crop_image_screen = kwargs.pop('crop_image_screen', None)
...
class App_R3App(App):
def build(self):
return MyScreenManager()
if __name__ == '__main__':
App_R3App().run()
ScreenManager » Basic Usage
Each screen has by default a property manager that gives you the
instance of the ScreenManager used.
I have a ScrollView that's supposed to have an update feature when you overscroll to the top (like in many apps). I've found a way to trigger it when the overscroll exceeds a certain threshold, but it triggers it a lot of times, as the on_overscroll event is triggered on every movement. So is there a way to limit it?
My code looks like this:
from kivy.app import App
from kivy.uix.scrollview import ScrollView
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.effects.dampedscroll import DampedScrollEffect
class Effect(DampedScrollEffect):
def on_overscroll(self, *args):
super().on_overscroll(*args)
if self.overscroll < -50:
print('hey')
class TestApp(App):
def build(self):
sv = ScrollView(effect_cls = Effect,
size_hint_y = 0.2)
gl = GridLayout(cols = 1,
size_hint_y = None)
gl.bind(minimum_height = gl.setter('height'))
for i in range(5):
gl.add_widget(Button(text = str(i),
size_hint = (None, None)))
sv.add_widget(gl)
return sv
TestApp().run()
So, as you can see, when the overscroll goes beyond 50, it prints a simple message. But when you actually try it, you'll see that it prints it many times. What I want for it is to trigger an event, stay untriggerable for some time (like a second) and update the content. I've tried messing with boolean flags and Clock, but it didn't work. What could be done here?
I would use a stateful decorator here:
class call_control:
def __init__(self, max_call_interval):
self._max_call_interval = max_call_interval
self._last_call = time()
def __call__(self, function):
def wrapped(*args, **kwargs):
now = time()
if now - self._last_call > self._max_call_interval:
self._last_call = now
function(*args, **kwargs)
return wrapped
class Effect(DampedScrollEffect):
def on_overscroll(self, *args):
super().on_overscroll(*args)
if self.overscroll < -50:
self.do_something()
#call_control(max_call_interval=1)
def do_something(self):
print('hey')
I know this an old question but someone might find it useful
This is a sample from tshirtman's github gist
from threading import Thread
from time import sleep
from kivy.app import App
from kivy.lang import Builder
from kivy.factory import Factory
from kivy.clock import mainthread
from kivy.properties import ListProperty, BooleanProperty
KV = '''
FloatLayout:
Label:
opacity: 1 if app.refreshing or rv.scroll_y > 1 else 0
size_hint_y: None
pos_hint: {'top': 1}
text: 'Refreshing…' if app.refreshing else 'Pull down to refresh'
RecycleView:
id: rv
data: app.data
viewclass: 'Row'
do_scroll_y: True
do_scroll_x: False
on_scroll_y: app.check_pull_refresh(self, grid)
RecycleGridLayout:
id: grid
cols: 1
size_hint_y: None
height: self.minimum_height
default_size: 0, 36
default_size_hint: 1, None
<Row#Label>:
_id: 0
text: ''
canvas:
Line:
rectangle: self.pos + self.size
width: 0.6
'''
class Application(App):
data = ListProperty([])
refreshing = BooleanProperty()
def build(self):
self.refresh_data()
return Builder.load_string(KV)
def check_pull_refresh(self, view, grid):
max_pixel = 200
to_relative = max_pixel / (grid.height - view.height)
if view.scroll_y < 1.0 + to_relative or self.refreshing:
return
self.refresh_data()
def refresh_data(self):
Thread(target=self._refresh_data).start()
def _refresh_data(self):
self.refreshing = True
sleep(2)
self.append_data([
{'_id': i, 'text': 'hello {}'.format(i)}
for i in range(len(self.data), len(self.data) + 50)
])
self.refreshing = False
#mainthread
def append_data(self, data):
self.data = self.data + data
if __name__ == "__main__":
Application().run()