Kivy: How select directory by FileChooser without kv language? - python

In Kivy manual is example for using FileChooser with kivy language. I want to use FileChooser only in python code. When I mark directory by mouse, press button Select Dir and actually value is in variable FileChooser.path. Selection without using this button has not result.
In kv file from example is used event on_selection, I binded this event with my function, but without effect.
My questions:
How do I get value to path with only mouse using?
Which class use event on_selection?
Thank You!
class Explorer(BoxLayout):
def __init__(self, **kwargs):
super(Explorer,self).__init__(**kwargs)
self.orientation = 'vertical'
self.fichoo = FileChooserListView(size_hint_y = 0.8)
self.add_widget(self.fichoo)
control = GridLayout(cols = 5, row_force_default=True, row_default_height=35, size_hint_y = 0.14)
lbl_dir = Label(text = 'Folder',size_hint_x = None, width = 80)
self.tein_dir = TextInput(size_hint_x = None, width = 350)
bt_dir = Button(text = 'Select Dir',size_hint_x = None, width = 80)
bt_dir.bind(on_release =self.on_but_select)
self.fichoo.bind(on_selection = self.on_mouse_select)
control.add_widget(lbl_dir)
control.add_widget(self.tein_dir)
control.add_widget(bt_dir)
self.add_widget(control)
return
def on_but_select(self,obj):
self.tein_dir.text = str(self.fichoo.path)
return
def on_mouse_select(self,obj):
self.tein_dir.text = str(self.fichoo.path)
return
def on_touch_up(self, touch):
self.tein_dir.text = str(self.fichoo.path)
return super().on_touch_up(touch)
return super().on_touch_up(touch)

Few changes need to be done.
There's no such event on_selection, there's property selection in FileChooserListView. You can use functions on_<propname> inside class that has these property, but when you use bind, you should use only bind(<propname>=.
Second thing is that by default as you can see in the doc selection contains list of selected files, not directories. To make directories actually selctable, you should change dirselect property to True.
Last on is on_mouse_select signature: property fires with it's value, you should count that.
Summary changes are:
self.fichoo.dirselect = True
self.fichoo.bind(selection = self.on_mouse_select)
# ... and
def on_mouse_select(self, obj, val):
After that you would be do the same as with button.
If you want to fill input with not actually path you're in, but selected path, you can do following:
def on_touch_up(self, touch):
if self.fichoo.selection:
self.tein_dir.text = str(self.fichoo.selection[0])
return super().on_touch_up(touch)

Related

Avoid overlap caused by dynamic resizing in Kivy

I'm working on a GridLayout (here called CostosInput) that adds TextInputs to itself when a button is pressed. However, when it does, the layout doesn't resize the widgets around it, which causes it to overlap once a certain amount of TextInput widgets are added. How can I fix that?
It is part of another GridLayout, which has one column and is contained in a ScrollView. The widget below it (the one it overlaps with) is a Label.
I've tried looking in the Kivy docs, but the only stuff I've found is related to the size of Layouts and Widgets, and not necessarily their placement. I thought it'd be something like the layout_hint_with_bounds(sh_sum, available_space, min_bounded_size, sh_min_vals, sh_max_vals, hint) (link to the Kivy docs), but that's an internal function. I apologize if something doesn't conform to convention.
Here's the code for the CostoInput class.
class CostosInput(GridLayout):
def __init__(self, *args, **kwargs):
super(CostosInput, self).__init__()
self.cols = 2
self.spacing = 5
self.padding = 5
self.size_hint_y = 0.2
# used in add_lines()
self.count = 1
add = Orange_Button(text='AGREGAR COSTO')
add.bind(on_press=self.add_lines)
rem = Orange_Button(text='QUITAR COSTO')
rem.bind(on_press=self.del_lines)
self.add_widget(add)
self.add_widget(rem)
self.add_widget(Orange_Label(text='concepto'))
self.add_widget(Orange_Label(text='costo'))
self.add_lines()
# this function is adds TextInputs
# when the add button is pressed
def add_lines(self, *args, **kwargs):
text = Normal_TI()
text.text = 'costo ' + str(self.count)
text.bind(text=self.change)
flot = FloatInput()
flot.bind(text=self.change)
self.add_widget(text)
self.add_widget(flot)
self.size_hint_y += 0.1
self.count += 1
# this has nothing to do with the ui
def del_lines(self, *args, **kwargs):
for x in range(0, len(self.children) - 4, 2):
if len(self.children) > 4:
try:
if self.children[x].focus or self.children[x + 1].focus:
self.remove_widget(self.children[x])
self.remove_widget(self.children[x])
except Exception as err:
print('del lines: no se pudieron quitar,'
'err {}'.format(err))
# this also has nothing to do with the ui
def change(self, value, *args):
dicto = dict()
for x in range(0, len(self.children) - 4, 2):
dicto[self.children[x + 1].text] = self.children[x].text
print(dicto)
producto.costos = dicto
Here's the code for the class the label is instantiated with. The text attribute is the only part that changes.
class Orange_Label(Label):
def __init__(self, *args, **kwargs):
super(Orange_Label, self).__init__(*args, **kwargs)
self.size_hint_y = None
self.size = (self.width, 10)
self.color = (0.95668627451, 0.6941176471, 0.5137254902, 1)

Anchoring widgets in kivy 1.9.0 RelativeLayout

I have created a RelativeLayout subclass that positions its children in a grid by supplying them positions in the code (Python, not kv file). It works, but items are placed some 25 pixels to the upper-right from the position of layout itself, as shown by the canvas block. Python code for Layout subclass:
class RLMapWidget(RelativeLayout):
def __init__(self, map=None, **kwargs):
super(FloatLayout, self).__init__(**kwargs)
# Connecting to map, factories and other objects this class should know about
self.tile_factory = TileWidgetFactory()
self.map = map
# Initializing tile widgets for BG layer and adding them as children
for x in range(self.map.size[0]):
for y in range(self.map.size[1]):
tile_widget = self.tile_factory.create_tile_widget(self.map.get_item(layer='bg',
location=(x, y)))
# tile_widget.pos = (50*x, 50*y)
tile_widget.pos = self._get_screen_pos((x, y))
self.add_widget(tile_widget)
# Initializing widgets for actor layers
for x in range(self.map.size[0]):
for y in range(self.map.size[1]):
if self.map.has_item(layer='actors', location=(x, y)):
actor_widget = self.tile_factory.create_actor_widget(self.map.get_item(layer='actors',
displayed location=(x, y)))
actor_widget.pos=(50*x, 50*y)
self.add_widget(actor_widget)
# Map background canvas. Used solely to test positioning
with self.canvas.before:
Color(0, 0, 1, 1)
self.rect = Rectangle(size = self.size, pos=self.pos)
self.bind(pos=self.update_rect, size=self.update_rect)
# Initializing keyboard bindings and key lists
self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
self._keyboard.bind(on_key_down=self._on_key_down)
# The list of keys that will not be ignored by on_key_down
self.used_keys=['w', 'a', 's', 'd']
def redraw_actors(self):
for actor in self.map.actors:
actor.widget.pos = self._get_screen_pos(actor.location)
def _get_screen_pos(self, location):
"""
Return screen coordinates (in pixels) for a given location
:param location: int tuple
:return: int tuple
"""
return (location[0]*50, location[1]*50)
# Keyboard-related methods
def _on_key_down(self, keyboard, keycode, text, modifiers):
"""
Process keyboard event and make a turn, if necessary
:param keyboard:
:param keycode:
:param text:
:param modifiers:
:return:
"""
if keycode[1] in self.used_keys:
self.map.process_turn(keycode)
self.redraw_actors()
def _keyboard_closed(self):
self._keyboard.unbind(on_key_down=self._on_key_down)
self._keyboard = None
def update_rect(self, pos, size):
self.rect.pos = self.pos
self.rect.size = self.size
class CampApp(App):
def build(self):
root = FloatLayout()
map_factory = MapFactory()
map = map_factory.create_test_map()
map_widget = RLMapWidget(map=map,
size=(map.size[0]*50, map.size[1]*50),
size_hint=(None, None))
root.add_widget(map_widget)
return root
if __name__ == '__main__':
CampApp().run()
Factory class that makes tiles:
class TileWidgetFactory(object):
def __init__(self):
pass
def create_tile_widget(self, tile):
tile.widget = Image(source=tile.image_source,
size_hint=(None, None))
return tile.widget
def create_actor_widget(self, actor):
actor.widget = Image(source='Tmp_frame_black.png',
size_hint=(None, None))
return actor.widget
Okay, solved it myself. It turns out that if I supply the size to child widgets in factory, they are positioned properly. Although it solves the problem I have, I'd still be grateful if someone can explain where this quirk does come from.

Kivy - Why Label does not update?

I want to change a text of a label but I can't do it, I can see it changing on the shell but not on the UI. I even directly change the text of the label by referencing its id but still its not updating. Anyone knows how to do this?
class MainApp(Screen, EventDispatcher):
title = "Top 10 Plays of 2015"
def __init__(self,*args,**kwargs):
super(MainApp, self).__init__(*args, **kwargs)
def change_curr_title(self, title, *args):
self.title = title
self.ids.lblTitle.text = self.title
print(self.ids.lblTitle.text)
pass
class OtherVideos(BoxLayout, EventDispatcher):
def __init__(self, *args, **kwargs):
super(OtherVideos,self).__init__(*args, **kwargs)
self.loadVideos()
def loadVideos(self):
self.clear_widgets()
con = MongoClient()
db = con.nba
vids = db.videos.find()
vidnum = 1
for filename in vids:
myid = "vid" + str(vidnum)
getfilename = filename['filename']
button = Button(id=myid,
text=getfilename,
color=[0,0.7,1,1],
bold=1)
button.bind(on_release=partial(self.change_Title, getfilename))
self.add_widget(button)
vidnum += 1
def change_Title(self, title, *args):
main = MainApp()
main.change_curr_title(title)
This is the construction of my kivy:
<MainApp>:
....
BoxLayout:
....
BoxLayout:
....some widgets
BoxLayout:
OtherVideos:
...this is where the buttons are generated...
BoxLayout:
Label:
id: lblTitle
text: root.title
Is there anyway to upload my whole code on this? like the file itself, so you guys can look at it.
EDIT: I can easily update the label when I'm making a new method like this without a parameter and binding it to a button through kivy
def update_label(self):
self.ids.lblTitle.text = "New Title"
I don't know why buttons with events created dynamically doesn't work.
Here:
def change_Title(self, title, *args):
main = MainApp() # !
main.change_curr_title(title)
you are creating a new object of screen (MainApp), which isn't connected to anything. To make it work, main should link to the existing instance of MainApp screen.
The OtherVideos box layout needs to have a reference to it, preferably in kv file.
Edit
In order to create a link from MainApp to OtherVideos, create an ObjectProperty:
class OtherVideos(BoxLayout):
main = ObjectProperty()
def __init__(self, *args, **kwargs):
super(OtherVideos,self).__init__(*args, **kwargs)
self.loadVideos()
...
which will be populated in kv file:
OtherVideos:
main: root
...this is where the buttons are generated...
Then, in the change_Title function, use this reference:
def change_Title(self, title, *args):
self.main.change_curr_title(title)

kivy: my function points to other/new widget when scheduled with clock

I have my code set up to initially add a few button widgets.
The first button will spawn a rectangular box. This is written as addfunction().
the second button is bound to perform hookupfull(), which does two things - gethooks() and hookfunc():
First it grabs the pos, width, and height of the children of my MainWindowWidget, and puts it in a list for each of those, then deletes extra entries from the buttons (I have my buttons as children of the MainWindowWidget currently, but I only want the properties of the boxes). This is written as gethooks()
Secondly it calculates fancy coordinates from the list and draws a line. This is written as hookfunc()
So, if I press the first button twice and the second button once, it will create two boxes and then draw a line connecting them together. This works fine. Next on my agenda is to schedule stuff like canvas.clear() and redraw the line, etc. every 1/60th of a second. I then create a third button widget to set a flag to start update loop run.
However if I try to schedule hookupfull() with Clock.schedule_interval() it doesn't work as I think it should - not sure how to explain it or what is going on, but the scheduled code doesn't seem to "go" to the MainWindowWidget I want. It seems to be spawning a whole bunch of other MainWindowWidgets.
I figured it's the way I'm referring to the widgets or something with the arguments (which is what I assume to be the (self, *args) portion of the code) or the way I'm declaring the method/function (I'm not sure of the difference between methods and functions still, sorry)
So, I tried to debug it by adding stuff like print self in several places to see what self was.
My code:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.scatter import Scatter
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.graphics import Color, Line
from kivy.clock import Clock
boxscale = 1
global startenable
global debug_1
startenable = False
debug_1 = True
class Scatterer(Scatter):
pass
class Drawer(FloatLayout):
pass
class MainWindowWidget(FloatLayout):
def addfunction(self, *args):
s = Scatterer()
d = Drawer()
s.size = 60 * boxscale , 111 * boxscale
# default size * scale
d.size = s.size
self.add_widget(s)
s.add_widget(d)
print "button is pressed"
def startfunc(obj):
global startenable
if startenable == False:
startenable = True
else:
startenable = False
print'startenable set to',startenable
def gethooks(self, *args):
# get hook locations
self.p = []
for child in self.children:
self.p.append(child.pos)
del self.p[(len(self.p)-3):(len(self.p))]
self.w = []
for child in self.children:
self.w.append(child.width)
del self.w[(len(self.w)-3):(len(self.w))]
self.h = []
for child in self.children:
self.h.append(child.height)
del self.h[(len(self.h)-3):(len(self.h))]
if debug_1 == True:
print 'getting hook location........'
print 'self.p:',self.p # list of positions
print 'length of self.p:',len(self.p)
print 'widths:',self.w # list of widths
print 'heights:',self.h# list of heights
print self
def hookfunc(self, *args):
# draw line based on hooks' position
self.h_01_x = \
self.p[0][0]
self.h_01_y = \
self.p[0][1] + (self.h[0]/2)
self.h_02_x = \
self.p[1][0] + self.w[1]
self.h_02_y = \
self.p[1][1] + (self.h[1]/2)
with self.canvas:
Line(bezier=(
self.h_01_x, self.h_01_y,
self.h_01_x - 20, self.h_01_y,
self.h_02_x + 20, self.h_02_y,
self.h_02_x, self.h_02_y,
), width=2)
print self
def hookupfull(self, *args):
self.gethooks()
self.hookfunc()
def update(self, *args):
global debug_1
if startenable == True:
mww= MainWindowWidget()
print mww
mww.hookupfull()
else: # if startenable is false
pass
class Test2App(App):
def build(self):
Clock.schedule_interval(MainWindowWidget.update, \
5.0/60.0)
return MainWindowWidget()
if __name__ == '__main__':
Test2App().run()
kv file:
#:kivy 1.0.9
<MainWindowWidget>
NewButton:
text: 'add'
pos: 100, 0
on_release: root.addfunction()
NewButton:
text: 'start'
pos: 200, 0
on_release: root.startfunc()
NewButton:
text: 'hook up full'
pos: 400, 0
on_release: root.hookupfull()
<NewButton#Button>:
font_size: 15
size_hint: None, None
size: 100, 100
<Scatterer>:
do_rotation: False
size_hint: None, None
size: self.size
<Drawer>:
size: self.size
canvas:
Color:
rgba: 0, 1, 0, 0.3
Rectangle:
pos: self.pos
size: self.size
What I found out was, when I clicked the button that does hookupfull(), which print self, they always return the same thing such as <__main__.MainWindowWidget object at 0x7f110fcef530> no matter how many times hookupfull() is called.
However when the same function is scheduled, self always returns different widgets.
Some other questions I'm wondering about:
Is there a way to assign id's or references to dynamically created widgets? For example, in my code, pressing the first button will create a new Scatter object. Is there a way to point to that specific Scatter object?
How do I refer to widget grandchildren? For example, in my code, I have a FloatLayout object set up as a child of a Scatter object, which is a child of the MainWindowWidget. The FloatLayout object changes in size due to the Scatter, which acts like a controller (and to my knowledge, doesn't actually change in size). I would like to access the properties such as pos and width of the FloatLayout.
arguments for the method/function declarations such as (self, *args) - what do they do?
Each function you define in your class should have self as the first parameter (except for static methods) by convention. It's the instance object of the class when the function is called. You were also creating new instances of the main widget in some functions: refer to inclement's comments above. It doesn't make much sense to do this. Please read the manual: https://docs.python.org/2/tutorial/classes.html#class-objects
Regarding your other questions:
Is there a way to assign id's or references to dynamically created
widgets?
You can assign and id directly by using the named id parameter when instantiating the object e.g. Scatterer(id='myid'). You can refer to it by its id but my personal preference would be to track the objects with a list (see below)
How do I refer to widget grandchildren?
Simply put, don't! Track them directly in a list instead and use references to their properties instead of creating secondary lists of dimensions:
def __init__(self, *args, **kwargs):
self.box_list = [] # <---- set up list to track boxes
super(MainWindowWidget, self).__init__(*args, **kwargs)
....
....
if len(self.box_list) > 1:
box1 = self.box_list[-2] # grab the last two boxes added to the list
box2 = self.box_list[-1]
h_01_x = box1.x
h_01_y = box1.y + (box1.height/2)
h_02_x = box2.x + box2.width
h_02_y = box2.y + (box2.height/2)
with self.canvas:
Line(bezier=(
h_01_x, h_01_y,
h_01_x - 20, h_01_y,
h_02_x + 20, h_02_y,
h_02_x, h_02_y,
), width=2)
I would like to access the properties such as pos and width of the
FloatLayout.
The scatter does have both size and pos. Both are updated when you change the object. It would also be better if you declared your FloatLayout directly in kivy as a child of Scatterer.. There's no need to create them separately
arguments for the method/function declarations such as (self, *args) - what do they do?
self is a reference to the instance whose function has been called. *args is the expansion of the list of unnamed parameters. Take a look at this question: *args and **kwargs?
As a final comment, if you call canvas.clear as you mentioned in your description then you'll erase the buttons and boxes too because you're using the canvas of the main widget. Create another class to contain your line and link its position to the boxes. When you call canvas.clear on that object then it'll clear only the line. If you update the line object when the box position/size changes then you can also move the rendering of the line into the kivy file and get rid of the extraneous clock scheduling.

Tkinter Canvas - Draw text on top

I have this class to create a Statusbar:
class Statusbar(Canvas):
'''Creates a statusbar widget'''
def __init__(self, master = None, **options):
if not master: master = Tk()
self.master, self.options = master, options
self.barFill, self.addText, self.value = self.options.get('barFill', 'red'), self.options.get('addText', True), 0
for option in ('barFill', 'addText'):
if option in self.options: del self.options[option]
Canvas.__init__(self, master, **self.options)
self.offset = self.winfo_reqwidth() / 100
self.height = self.winfo_reqwidth()
if self.addText: self.text = self.create_text(self.winfo_reqwidth()/2, self.winfo_reqheight()/2, text = '0%')
self.bar = self.create_rectangle(0, 0, self.value, self.height, fill = self.barFill)
def setValue(self, value):
'''Sets the value of the status bar as a percent'''
self.value = value * self.offset
self.coords(self.bar, 0, 0, self.value, self.height)
if self.addText: self.itemconfigure(self.text, text = str(self.value/self.offset) + '%')
def change(self, value):
'''Changes the value as a percent'''
self.value += (value * self.offset)
self.coords(self.bar, 0, 0, self.value, self.height)
if self.addText: self.itemconfigure(self.text, text = str(self.value/self.offset) + '%')
My issue is that the text is always drawn under rectangle. So when the rectangle reaches the text, you can't see the text anymore. How can I fix this? Thanks in advance.
The fact that one object sits atop another is called the stacking order. By default, objects created later have a higher stacking order than those that were created earlier. So, one solution is to draw the rectangle and then draw the text.
You can also move things up or down the stacking order using the lift and lower commands of a canvas. You must give it an id or tag of what you want to lift or lower, and optionally an id or tag of the object you want the first object(s) to be above or below.
So, for example, you could raise the text above the rectangle like this:
self.lift(self.text, self.bar)
If you want to get really fancy, you can create the notion of layers. I gave an example in another answer, here: https://stackoverflow.com/a/9576938/7432
In my programming class, we said put whatever text you don't want to be blocked drawn last. So put the text at the bottom of what function you are using to draw with

Categories