Kivy ScrollView code error with scrollbars (or bug?) - python

I'm playing around with a Kivy Scrollview, adding scrollbars, etc, and getting a strange crash. I don't specifically think it's a bug, it's probably some configuration element on Scrollviews that I'm missing, but who knows?
Given this code:
"""
Source: https://stackoverflow.com/questions/35626320/kivy-image-scrolling
"""
from kivy.app import App
from kivy.uix.image import Image
from kivy.uix.scrollview import ScrollView
from kivy.core.window import Window
class TutorialApp(App):
def build(self):
some_img = Image(source='/home/data/map/Map_07C.jpg', size_hint=(None, None),
keep_ratio=True, size=(Window.width * 2, Window.height * 2))
sv = ScrollView(size=Window.size, bar_width=50,
scroll_type=['bars', 'content'], effect_cls='ScrollEffect')
sv.add_widget(some_img)
return sv
if __name__ == "__main__":
TutorialApp().run()
if I click or touch the Scrollbars in any way, I get this error:
File "kivy_env/lib/python3.8/site-packages/kivy/uix/scrollview.py", line 908, in on_scroll_move
self.effect_x.update(touch.x)
File "kivy_env/lib/python3.8/site-packages/kivy/effects/scroll.py", line 116, in update
self.displacement += abs(val - self.history[-1][1])
IndexError: list index out of range
However - if I first click the bitmap being scrolled, I can use the scrollbars with no problem.
So what's up? Is there some Scrollview configuration I'm missing? (It took me a while to even find the scroll_type option to enable the bars, at first I could only mouse-drag the bitmap). Or is it a bug - given that it's referencing history[-1], maybe that doesn't exist yet?

Yep, it's a bug. Just searched the Kivy repo on Github, found:
Effects Scroll Exception
The link does have a "workaround" patch, that you can manually apply to the installed library. Just tested the patch, it fixes my problem. Basically puts a try/except/return block around the line with history[-1]

technically is a bug but we can note from the desktop and mobile apps that they use different scroll_type=['bars', 'content'] in the desktop app we use bars
and in the mobile app we use content so the bug only occurs when we use two types of scroll_type so we can say that the scrollview does not design to use two type of scroll_type in the same time

Another workaround is calling a function on_touch_down that checks the mouse's x-position and changes the scroll type to either only ['Bars'] or only ['Content'] accordingly. Note, I set mine to check Window.width - 12 as that is the width of scroll bar that I use. Default is 2.
# tutorial.kv
<SV>:
on_touch_down: self.check_pos()
bar_width: 12
# main.py
from kivy.uix.scrollview import ScrollView
class SV(ScrollView):
def check_pos(self):
if Window.mouse_pos[0] <= (Window.width - 12):
self.scroll_type = ['content']
else:
self.scroll_type = ['bars']

Related

How can i change kivy window position using Window.top and Window.left

Someone questioned similar thing , answer was given but without example for it thus i am unable to solve this simple solution checked documentation and was unable to understand
from kivy.core.window import Window
How to do this :
Config.set('graphics','position','custom')
Config.set('graphics','left',500)
Config.set('graphics','top',10)
using Window.left and Window.top
Window.size = (1920, 150)
Window.top =????????
Window.left =??????????
I don't know the syntax or my kivy installation is bugged
Person in chat #inclement did not understand this question... so here is full example:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
import threading
from kivy.core.window import Window
class Hello(FloatLayout):
def __init__(self,**kwargs):
threading.Thread.__init__(self)
super(Hello,self).__init__(**kwargs)
Window.size = (600, 150)
#INSERT Window.left and under it Window.top to position this window on coordinates 50,50 on the top left of the screen
class Example(App):
def build(self):
return Hello()
if __name__ == '__main__':
Example().run();
Found out the answer i had problems with my code rather than syntax or installation of Kivy.
Answer to above example is simply inserting:
Window.top = 50
Window.left = 50
I had problems somewhere else in my code so i was very confused and wanted quick check with someone.
Feedback for #inclement: if you went with your thought answer is you were right on the money. That was the syntax and what i was looking for. You asking me additional questions was more confusing than helpful, intention of helping appreciated non the less =)

Center of Canvas in Python vs kivy language

I have made an easy app where i try to show my issue. When using python to draw a line in kivy (using the with self.canvas method) the line gets drawn from a center_x and center_y of 50.
Using kivy Lang draws the line correctly in the center. Here is a simple code example:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Line
class TestWidget(Widget):
def draw(self):
with self.canvas:
Line(points=[self.center_x,self.center_y,self.center_x,self.center_y+100], width=2)
class TestApp(App):
def build(self):
test = TestWidget()
test.draw()
return test
if __name__ == '__main__':
TestApp().run()
and the corresponding test.kv file:
#:kivy 1.11.1
<TestWidget>:
canvas:
Line:
width: 5
points: (self.center_x,self.center_y,self.center_x,self.center_y+100)
result is like this:
Any idea why using python code is not working?
Both codes are not equivalent, in the case of python you are only establishing that the position of the line is based on the center of the widget at that time, that is, at the beginning, instead in .kv it is indicating that the line position is always it will be with respect to the center of the Widget.
TL; DR;
Explanation:
In kv the binding is a natural and implicit process that in python is not doing it, besides that its implementation is simpler, for example the following code:
Foo:
property_i: other_object.property_j
In that case its equivalent in python is:
foo = ...
other_object = ...
other_object.bind(property_j=foo.setter("property_i"))
And it is not:
foo.property_i = other_object.property_j
Since with the bind you are indicating before a change of property_j update property_i with that value, but in the last code you only indicate at this moment copy the value of property_j in property_i.
In your particular case, the center you take is before displaying the widget and kivy taking default configurations and other properties into consideration, changes the geometry of the widget after it is displayed.
Solution
Making the necessary modifications taking into account the previous explanation the following code is an equivalent of the .kv code
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Line
class TestWidget(Widget):
def __init__(self, **kwargs):
super(TestWidget, self).__init__(**kwargs)
with self.canvas:
self.line = Line(width=2)
self.bind(center=lambda *args: self.draw())
def draw(self):
self.line.points = [
self.center_x,
self.center_y,
self.center_x,
self.center_y + 100,
]
class TestApp(App):
def build(self):
test = TestWidget()
return test
if __name__ == "__main__":
TestApp().run()
With kv language your declarations automatically update when their dependencies change. That means that when the widget's centre goes from (50, 50) to something else, the line is redrawn in the new location.
Your Python code doesn't do this, it just draws the line once using whatever its centre is at the moment the code runs. That value is (50, 50), since that's the default before positioning has taken place.
The solution is to write code in Python that updates the line when the widget centre changes, something along the lines of declaring the line with self.line = Line(...) and self.bind(centre=some_function_that_updates_self_dot_line).

Is it possible to change a Kivy app taskbar icon?

I have a kivy app, here's the code:
class ComicNotificatorApp(App):
def build(self):
Window.size = (300, 300)
self.title = 'Comics Notificator'
self.icon = 'assets/icon.png'
return Label(text=to_display)
and I'd like to change the taskbar icon of the app
and while I'm at it, how can I change the size of the window to automatically fit the size of the label, as opposed to setting it manually?
Thank you!
stackoverflow question <-- this link mention a similar question and I think this may help
kivy doc for your ref about kivy config object
and I only know how to change the window's icon but not the task bar one
...
class MyKivyApp(App):
def build(self):
self.title = 'window's title'
self.icon = <icon>: str <----
...
the taskbar one maybe you can change it when packaging
how can I change the size of the window to automatically fit the size of the label
maybe you can try to use the kivy.config.Config.set() , if I get it correctly , you want to set the size of the window == size of label ? if so then set the size of the label first, then use kivy.config.Config.set('graphics', 'width', <size>: str) kivy.config.Config.set('graphics', 'height', <size>: str)
# I assume you won't change the label size after starting the kivy screen 😅
import kivy
kivy.config.Config.set('graphics', 'width', <label_width>: str)
kivy.config.Config.set('graphics', 'width', <label_height>: str)
...
because the config have to set at the very beginning , before the kivy window is created
e.g.
import kivy
# set config here
# import another stuff
hope this help : )
A bit late but for your main question Is it possible to change a kivy app taskbar icon, no it isn't possible to change the taskbar icon if you are running the app as a script, not an executable of some kind. However, the taskbar icon will automatically sync with your window icon when run as an executable(by packaging your app using pyinstaller or py2exe, anything like that could work)

Key Error when binding minimum_height to boxlayout in scrollview

How do I bind minimum_height to a BoxLayout in a ScrollView on the python side?
This is where I am at:
class BrokenScreen(screen):
def __init__(self, **kwargs):
super(BrokenScreen,self).__init__(**kwargs)
# build widgets
self.pop = Popup(auto_dismiss=False,size=(.8,.8))
self.box = BoxLayout(orientation='vertical', size_hint_y=None)
self.scroll = ScrollView()
# bind height, this is the line bringing about the error
self.box.bind(minimum_height=self.box.setter('height'))
# integrate
self.scroll.add_widget(self.box)
self.pop.add_widget(self.scroll)
When I attempt to compile, I receive the following error:
KeyError: 'minimum_height'
What do?
BoxLayout does have an attribute named minimum_height.
Which is automatically computed minimum height needed to contain all children.
Therefore, you are not suppose to modify minimum_height in BoxLayout
To prevent such modification, Kivy has made it as read only.
That is the reason why you have no access to modify it.
Best way to prove it is to open up boxlayout.py inside of your Kivy folder.
minimum_height = NumericProperty(0)
'''Automatically computed minimum height needed to contain all children.
.. versionadded:: 1.10.0
:attr:`minimum_height` is a :class:`~kivy.properties.NumericProperty` and
defaults to 0. It is read only.
'''
Miket25 asked if box has the minimum_height argument.
I was able to resolve my problem by using a GridLayout instead. So, probably not (EDIT: I was wrong about whether or not box has minimum_height, see the accepted response).
# build widgets
self.pop = Popup(auto_dismiss=False,size=(.8,.8))
self.grid = GridLayout(cols=1, size_hint_y=None) #<- change this
self.scroll = ScrollView(size=self.size)
# bind height, this is the line bringing about the error
self.grid.bind(minimum_height=self.grid.setter('height')) # only changed var name
# integrate
self.scroll.add_widget(self.grid) # changed var name
self.pop.add_widget(self.scroll)

Kivy Sending text from spinner to another function

I've been trying to figure out how to get the value selected in a spinner into another function. I basically need a user to select some option and then press "save" prompting another function to write the data to a file (right now I just have it setup to print). When I run the form.finkle function it prints kivy.uix.button.Button object at 0x02C149D0
I'm sure its an easy fix, but I've been stuck on it for days.
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.popup import Popup
from kivy.uix.textinput import TextInput
from kivy.uix.spinner import Spinner
condspin = Spinner(text='Condition',values=('Good','Fair','Poor','Missing'))
typespin = Spinner(text='Type', values=('Metal','Wood','Pin','Missing'))
commlabel = Label(text='Comment')
commtext = TextInput(text="")
class Goose(App):
def build(self):
layout = GridLayout(cols=2,rows=6,padding=10,spacing=10)
layout.add_widget(Button(text='Hunter Parking'))
layout.add_widget(Button(text='Boat Launch'))
layout.add_widget(Button(text='ETRAP'))
layout.add_widget(Button(text='Monument',on_press=form.monform))
layout.add_widget(Button(text='Camp Site'))
layout.add_widget(Button(text='Sign'))
layout.add_widget(Button(text='Building'))
layout.add_widget(Button(text='Trail Head'))
layout.add_widget(Button(text='Dam'))
layout.add_widget(Button(text='Day Use'))
layout.add_widget(Button(text='Pavilion'))
layout.add_widget(Button(text='Misc Point'))
return layout
class form():
def finkle(condtest):
print condtest
def monform(self):
monbox = GridLayout(cols=2,rows=8,padding=20,spacing=20)
monpopup = Popup(title="Monument",content=monbox)
closebut = Button(text="Close")
closebut.bind(on_press=monpopup.dismiss)
savebut = Button(text="Save Point")
savebut.bind(on_press=form.finkle)
condtest = condspin.text
monbox.add_widget(condspin)
monbox.add_widget(typespin)
monbox.add_widget(commlabel)
monbox.add_widget(commtext)
monbox.add_widget(savebut)
monbox.add_widget(closebut)
monpopup.open()
Goose().run()
Since you have made the spinner global, you could just do print(condspin.text). More generally, you could pass the spinner as an argument, e.g.
from functools import partial
savebut.bind(on_press=partial(self.finkle, condspin))
and redefine the finkle method as
def finkle(self, spinner, button)
Note that I changed form.finkle to self.finkle and added both the self and spinner arguments. It's bad practice to call the method via the class like that.
There are some significant other bad style things in your code, and I would recommend some other changes. Mostly I would make use of kv language for basically everything, it will make the widget trees much clearer, more robust to changes later, and also make this binding very simple (you'd be able to refer to the spinner text via a kv id). Also, the form class is semi-unnecessary, you could replace it with a FormWidget that is the GridLayout you make in the monform function, adding all its children and behaviour in kv.

Categories