Im making a simple app in Kivy that needs to use the FileChooserListView widget. For some reason whenever I try using the widget, it only covers the lower left hand side of the app. I can't get it to fill the entire screen like its supposed to.
Here is what happens to my app:
main.py:
import kivy
kivy.require('1.8.0')
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
class AppRoot(BoxLayout):
img_picker = ObjectProperty(None)
def __init__(self, *args, **kwargs):
super(BoxLayout, self).__init__(*args, **kwargs)
self.img_picker = ImagePicker()
def change_to_img_picker(self):
self.clear_widgets()
self.add_widget(self.img_picker)
def change_to_about_us(self):
print "change_to_about_us"
class ImagePicker(BoxLayout):
def __init__(self, *args, **kwargs):
super(BoxLayout, self).__init__(*args, **kwargs)
def open(self, path, selection):
print "OPEN"
def selected(self, selection):
print "SELECTION"
class ImageApp(App):
def build(self):
return AppRoot()
if __name__ == "__main__":
ImageApp().run()
image.kv:
AppRoot:
<AppRoot>:
orientation: 'vertical'
padding: root.width * .02, root.height * .02
spacing: 10
Label:
text: "Some text should go here"
Button:
text: "Select Image"
size_hint_y: None
height: root.height * .1
on_release: root.change_to_img_picker()
Button:
text: "About the App"
size_hint_y: None
height: root.height * .1
on_release: root.change_to_about_us()
<ImagePicker>:
orientation: 'vertical'
id: img_picker
Button:
text: "open"
on_release: root.open(filechooser.path, filechooser.selection)
size_hint: 1, .2
FileChooserListView:
id: filechooser
on_selection: root.selected(filechooser.selection)
size_hint: 1, .8
The change_to_img_picker() function should replace the current widgets with the FileChooserListView and take up all the space. I'm not sure why it's not working. I would appreciate it if anyone would enlighten me as to why this is happening. Thank You in advance:)
super(BoxLayout, self).__init__(*args, **kwargs)
The problem is that you call super on BoxLayout, not for your ImagePicker class (the first argument should be the current class, not the parent). This messes up the binding that is normally created in the BoxLayout's __init__ (probably this is simply not called any more, you get the BoxLayout parent's __init__ instead), so the normal layout triggers are absent and the change of size etc do not affect the children.
I'm not completely sure of some of the details (in particular, I'm surprised that the layout for your root widget seems to work fine), but this seems to be the core of the problem.
Related
I'm trying to call a custom widget method from its ids.
But I received an AttributeError: 'LabelBox' object has no attribute 'change_first_text'.
As a simple as possible working example can be found here with the PanelApp.py file:
from kivy.app import App
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
Builder.load_file("panel.kv")
class LabelBox(BoxLayout):
def __init__(self, *args, **kwargs):
super(LabelBox, self).__init__(*args, **kwargs)
def change_first_text(self, text):
self.ids.first.text = text
class ButtonList(BoxLayout):
pass
class Test(TabbedPanel):
pass
class TabbedPanelApp(App):
def build(self):
self.test = Test()
self.btn_list = ButtonList()
self.vbox = BoxLayout(orientation="vertical")
self.vbox.add_widget(self.btn_list)
self.vbox.add_widget(self.test)
return self.vbox
def change_first(self, value):
print("Button clicked and new value is: '{}'".format(value))
self.test.ids.lbs.change_first_text(value)
if __name__ == '__main__':
TabbedPanelApp().run()
and the panel.kv file:
<ButtonList#ButtonList>:
orientation: "horizontal"
Button:
text: "change fisrt to me"
on_press: app.change_first(self.text)
Button:
text: "change two to me"
<LabelBox#BoxLayout>:
Label:
id: first
text: "first"
Label:
id: two
text: "two"
<Test>:
size_hint: .5, .5
pos_hint: {'center_x': .5, 'center_y': .5}
do_default_tab: False
TabbedPanelItem:
text: 'first tab'
LabelBox:
id: lbs
Calling the script cause a runtime error that I can't understand.
Have you any clue on how to manage such event callback through the application?
The problem in your case is that you are creating 2 classes called LabelBox:
1.
class LabelBox(BoxLayout):
def __init__(self, *args, **kwargs):
super(LabelBox, self).__init__(*args, **kwargs)
def change_first_text(self, text):
self.ids.first.text = text
2.
<LabelBox#BoxLayout>:
Label:
id: first
text: "first"
Label:
id: two
text: "two"
I understand that you only want to have a class so it is appropriate to do the creation with the inheritance in the .py and only the implementation of children in the .kv. The solution is to change delete #BoxLayout in the .kv
<LabelBox>:
Label:
id: first
text: "first"
Label:
id: two
text: "two"
Goal:
Periodic update of parent (screen) class / UI from child (boxlayout) class. Theconf2.dat is occasionally updated (from various other screens), and I want the UI to update every 5 seconds or so by re-running this class.
Latest code update:
In the __init__ function, I have Clock.schedule_interval(self.create_button, 1), which should cause the create_button function to rerun every second.
At the top of the create_button function, I have self.box_share.clear_widgets(), which should clear all the widgets so they can be repopulated (per the instructions outlined further down the create_button function).
Action:
I run the code
I navigate to NoTags screen by clicking the button with text title 'updating sequence'
I make changes to buttons that were dynamically created under scrollview by clicking on them. They successfully change color. This information is written to the conf2.dat file.
I navigate to SequenceScreen screen by first clicking 'home' button, then clicking 'sequence display' button. This SequenceScreen screen is the screen I wish to have updated to reflect the changes made to conf2.dat file.
Result:
UI associated withSequenceScreen(Screen) class still does not update per changes made from UI associated with NoTags(Screen) class.
However, when I restart the app altogether, I find the SequenceScreen UI successfully updated.
Suspicion:
I'm just one line of code away from getting this UI to update properly.
Python Code:
from kivy.app import App
# kivy.require("1.10.0")
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.textinput import TextInput
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.properties import NumericProperty
from kivy.clock import Clock
from kivy.uix.widget import Widget
from kivy.uix.scrollview import ScrollView
#from kivy.base import runTouchApp
from kivy.properties import StringProperty, ObjectProperty, NumericProperty
from kivy.storage.dictstore import DictStore
import pickle
import datetime, threading
import time
from kivy.clock import mainthread
class BackHomeWidget(Widget):
pass
class SequenceBoxLayout_NoEdits(BoxLayout):
box_share = ObjectProperty()
config_file = DictStore('conf2.dat')
def __init__(self, **kwargs):
super(SequenceBoxLayout_NoEdits, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_interval(self.create_button, 1)
def create_button(self, *args):
self.box_share.clear_widgets()
top_button_share = 1.1
color = (.4, .4, .4, 1)
for i in range(25):
top_button_share -= .4
id_ = "part" + str(i + 1)
if self.config_file.exists(id_):
btn_color = self.config_file[id_]["background_color"]
else:
self.config_file.put(id_, background_color=color)
btn_color = color
button_share = Button(background_normal='',
background_color=btn_color,
id=id_,
pos_hint={"x": 0, "top": top_button_share},
size_hint_y=None,
height=60,
font_size = 30,
text= str(i+1)
self.box_share.add_widget(button_share)
#Clock.schedule_interval(self.parent.ids.updatedisplay.create_button(self, *args) , 1)
#self.parent.ids.updatedisplay.create_button(self, *args)
class SequenceBoxLayout_NoTag(BoxLayout):
box_share = ObjectProperty()
config_file = DictStore('conf2.dat')
def __init__(self, **kwargs):
super(SequenceBoxLayout_NoTag, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_once(self.create_button)
def create_button(self, *args):
df = pd.read_excel("Test.xlsx","Sheet1")
parts = df['parts'].values.tolist()
top_button_share = 1.1
color = (.4, .4, .4, 1)
for i in range(len(parts)):
top_button_share -= .4
id_ = "part" + str(i + 1)
if self.config_file.exists(id_):
btn_color = self.config_file[id_]["background_color"]
else:
self.config_file.put(id_, background_color=color)
btn_color = color
button_share = Button(background_normal='',
background_color=btn_color,
id=id_,
pos_hint={"x": 0, "top": top_button_share},
size_hint_y=None,
height=60,
font_size = 30,
text= str(i+1)+ ". " + str(parts[i]))
if self.parent.name == 'notags':
button_share.bind(on_press=self.update_buttons_notag)
self.box_share.add_widget(button_share)
def update_buttons_notag(self, button):
button.background_color = 0.86,0.54,0.04,1
self.config_file.put(button.id, background_color=(0.86,0.54,0.04,1))
class MainScreen(Screen):
pass
class AnotherScreen(Screen):
pass
class SequenceScreen(Screen):
pass
class NoTags(Screen):
pass
class ScreenManagement(ScreenManager):
pass
presentation = Builder.load_file("updatelistexample.kv")
class MainApp(App):
def build(self):
return presentation
if __name__ == "__main__":
MainApp().run()
KV Code:
#: import FadeTransition kivy.uix.screenmanager.FadeTransition
ScreenManagement:
transition: FadeTransition()
MainScreen:
AnotherScreen:
NoTags:
SequenceScreen:
<SmallNavButton#Button>:
font_size: 32
size: 125, 50
color: 0,1,0,1
<BigButton#Button>:
font_size: 40
size_hint: 0.5, 0.15
color: 0,1,0,1
<BackHomeWidget>:
SmallNavButton:
on_release: app.root.current = "main"
text: "Home"
pos: root.x, root.top - self.height
<MainScreen>:
name: "main"
FloatLayout:
BigButton:
on_release: app.root.current = "notags"
text: "updating sequence"
pos_hint: {"x":0.25, "top": 0.4}
BigButton:
on_release: app.root.current = "sequence"
text: "sequence display"
pos_hint: {"x":0.25, "top": 0.7}
<AnotherScreen>:
name: "newgarage"
<NoTags>:
name: "notags"
SequenceBoxLayout_NoTag:
BackHomeWidget:
FloatLayout:
BigButton:
text: "Select Parts w/o Tags"
pos_hint: {"x":0.5, "top": 0.6}
background_normal: ''
background_color: (0.4,0.4,0.4,1)
<SequenceBoxLayout_NoEdits>:
box_share: box_share
ScrollView:
GridLayout:
id: box_share
cols: 1
size_hint_y: None
size_hint_x: 0.5
spacing: 5
padding: 130
height: self.minimum_height
canvas:
Color:
rgb: 0, 0, 0
Rectangle:
pos: self.pos
size: self.size
<SequenceBoxLayout_NoTag>:
box_share: box_share
ScrollView:
GridLayout:
id: box_share
cols: 1
size_hint_y: None
size_hint_x: 0.5
spacing: 5
padding: 130
height: self.minimum_height
canvas:
Color:
rgb: 0, 0, 0
Rectangle:
pos: self.pos
size: self.size
<SequenceScreen>:
name: "sequence"
SequenceBoxLayout_NoEdits:
id: updatedisplay
BackHomeWidget:
Credit:
Based on advice provided by #Tshirtman in the comments thread of the posted question...
Summary:
The problem with the code had to do with the fact that I had two different DictStore pointing to the same file, which was tripping up the communication between both classes.
The solution was to instead use only one DictStore and define that variable in the App class, then reference that particular variable in the child classes [using App.get_running_app()], like so:
Define config_file in App class:
class MainApp(App):
config_file = DictStore('conf2.dat')
def build(self):
return presentation
Reference App variable in child classes:
class SequenceBoxLayout_NoEdits(BoxLayout):
...
def __init__(self, **kwargs):
super(SequenceBoxLayout_NoEdits, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_interval(self.create_button, 1)
def create_button(self, *args):
self.box_share.clear_widgets()
app = App.get_running_app()
...
for i in range(len(parts)):
...
if app.config_file.exists(id_):
btn_color = app.config_file[id_]["background_color"]
else:
app.config_file.put(id_, background_color=color)
...
...
class SequenceBoxLayout_NoTag(BoxLayout):
...
def __init__(self, **kwargs):
super(SequenceBoxLayout_NoTag, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_once(self.create_button)
def create_button(self, *args):
...
app = App.get_running_app()
...
for i in range(len(parts)):
...
if app.config_file.exists(id_):
btn_color = app.config_file[id_]["background_color"]
else:
app.config_file.put(id_, background_color=color)
...
...
def update_buttons_notag(self, button):
app = App.get_running_app()
...
app.config_file.put(button.id, background_color=(0.86,0.54,0.04,1))
Clock has a schedule_interval method, which works much like the schedule_once one, but will be called every n seconds instead of just once after n seconds.
So you could certainly just change that call in __init__ and start the create_button by calling self.box_share.clear_widgets() to remove widgets from the previous call before re-creating them.
This might be a bit wasteful, if you find yourself recreating a lot of widgets even if nothing changed, so you could add some logic to first check if the data didn't change, or even if it did, just reuse the old buttons if possible.
box = self.box_share
old_buttons = box.children[:]
box.clear_widgets()
# [your logic about computing the list of parts]
for i, p in enumerate(parts): # this is much better than doing range(len(parts))
# [the logic to get the content of the button]
if old_buttons: # check there are still buttons in the list of old buttons
btn = old_buttons.pop()
else:
btn = Button(
background_normal='',
background_color=btn_color,
pos_hint={"x": 0, "top": top_button_share},
# etc, all the things that are common to all your buttons
# but really, hardcoding them like this is a bit painful,
# you should use a subclass of button so you can style it
# in a kv rule so it would apply to all of them directly
)
btn.id=id_
btn.text = "{}. {}".format(i+1, p)
btn.pos_hint["top"] = top_button_share
# re-apply any other property that might have changed for this part
box.add_widget(btn)
But this logic is quite a common one, actually, and there are other things you can do to improve things in even more situations, though that's quite some work.
Thankfully, you are not the first one to need such thing, and we have been blessed with the wonderful RecycleView, which automates all of this and more, you just need to feed it a data directory, and it'll create the necessary widgets to fill the visible part of the scrollview if there is enough widgets to warrant scrolling, and automatically update when data changes, and when you scroll to see different parts of the list. I encourage you to check it, yourself. but the end result would certainly be something like.
<PartButton#Button>:
id_: None
part: ''
text: '{}. {}'.format(self.id, self.part)
<SequencedBoxLayout#Recycleview>:
parts: self.get_parts()
viewclass: 'PartButton'
data:
[
{
id_: i,
part: part
} for i, p in enumerate(self.parts or [])
]
RecycleBoxLayout:
This is a follow up to a previous question I've asked on how to change the properties of a kivy widget (update a kivy label text in another class). I've been trying to figure out why the temperature reading on the Menuscreen updates but within the Mashscreen, the text doesn't update. It looks like the values are being passed to eh temperature1def method but the screen widget doesn't update.
Also, is it better to send the value using
Mashscreen().temperature1def(self.test_temp)
or is it better practice to use
self.stuff_p.text = str(self.test_temp) + u'\u00B0F'
within the MenuScreen to update the label within the Mashscreen? Thanks in advance.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.properties import ObjectProperty
from kivy.clock import Clock
sm = """
ScreenManager:
#Handling the gesture event.
id:manager
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 0.5
Rectangle:
pos: 0,0
size: 800, 480
MenuScreen:
id:MenuScreen
name:'MenuScreen'
manager: manager
Mashscreen:
id:Mashscreen
name: 'Mashscreen'
manager: manager
<MenuScreen>:
stuff_r: mainel1temp
Button:
text: "Go to mashscreen"
on_release:
root.manager.current = "Mashscreen"
Label:
id: mainel1temp
text:'0'
size_hint: None, None
size: 75,50
pos: 295,308
font_size:'22sp'
text_size: self.size
halign: 'left'
valign: 'middle'
<Mashscreen>:
stuff_p: temperature1
FloatLayout:
Label:
id: temperature1
text:'100'
size_hint: None, None
size: 75,50
pos: 50,275
font_size:'22sp'
text_size: self.size
halign: 'left'
valign: 'middle'
"""
class MenuScreen(Screen):
test_temp = 99
stuff_r = ObjectProperty(None)
def __init__(self,**kwargs):
super(MenuScreen,self).__init__(**kwargs)
Clock.schedule_interval((self.read_temp), 1)
#self.read_temp(1)
def read_temp(self, dt):
self.test_temp += 1
self.stuff_r.text = str(self.test_temp) + u'\u00B0F'
Mashscreen().temperature1def(self.test_temp)
#self.parent.ids.Mashscreen.stuff_p.text = str(self.test_temp) + u'\u00B0F'
class Mashscreen(Screen):
stuff_p = ObjectProperty(None)
def __init__(self, **kwargs):
super(Mashscreen, self).__init__(**kwargs)
def temperature1def(self, temp1):
print(temp1)
self.stuff_p.text = str(temp1)
class TestApp(App):
def build(self):
return Builder.load_string(sm)
if __name__ == '__main__':
TestApp().run()
First…
Mashscreen().temperature1def(self.test_temp)
This doesn't call the temperature1def method on your Mashscreen instance in the UI, instead, it creates a new Mashscreen instance, calls the method on it, and then let this object be garbage collected by python. If you want to update your UI, you need to get a reference to the widget you want to update.
You define your Mashscreen in the root rule of your application, so you can get it by its id in this object.
App.get_running_app() will return a reference to your currently running app, which has a root attribute, which is your root widget, any widget at the root of a rule can use its ids attribute to get a reference to any id defined in its scope, so.
App.get_running_app().root.ids.Mashscreen.temperature1def(self.test_temp)
will certainly be more like what you actually want to do.
Now, regarding the question about how to do it best in python kivy, i find that it's cleaner to do something like.
App.get_running_app().root.ids.Mashscreen.temperature = self.test_temp
and then to change your Mashscreen class to have a temperature NumericProperty, and to change your kv rule to use this value in the Label.
<Mashscreen>:
stuff_p: temperature1
FloatLayout:
Label:
id: temperature1
text: '%s' % root.temperature
size_hint: None, None
size: 75,50
pos: 50,275
font_size:'22sp'
text_size: self.size
halign: 'left'
valign: 'middle'
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)
I'm trying to create a customer management software, so I need to create a GUI. I chose Kivy because it's Open Source and LGPL.
This software is meant to have multiple panels, so I need to have ID's to access widgets in each panel. I created Kivy rules in kv language, but when I nest a class is another one, I can't access the ID's. Below an example code:
LayoutTestApp.py :
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.uix.boxlayout import BoxLayout
class SkipperList(GridLayout):
pass
class TestPanel(BoxLayout):
def __init__(self, **kwargs):
super(TestPanel, self).__init__(**kwargs)
print "TestPanel ids:", self.ids
class MasterPanel(TabbedPanel):
pass
class NjordApp(App):
def __init__(self, **kwargs):
super(NjordApp, self).__init__(**kwargs)
def build(self):
root = MasterPanel()
return root
if __name__ == '__main__':
application = NjordApp()
application.run()
njord.kv
#:kivy 1.9.0
<MasterPanel>
pos_hint: {'center_x': .5, 'center_y': .5}
do_default_tab: False
TabbedPanelItem:
text: 'Skippers'
BoxLayout:
padding: 10
spacing: 10
TestPanel:
<TestPanel>:
id: SkipperPanelId
BoxLayout:
padding: 10
spacing: 10
BoxLayout:
orientation: 'vertical'
Label:
text: 'List des mecs'
size_hint: 1, 0.09
Button:
id: button_up
size_hint: 1, 0.08
text:'/\\'
Button:
id: button_down
size_hint: 1, 0.08
text:'\/'
When I launch the software, the print only return {}.
Can someone tell me how to access button_up ID for example ?
Thanks advance.
The reason you don't see the ids is because you are printing in the constructor of TestPanel. It hasn't finished being created yet, let alone have anything added to it. If you print the ids after the GUI has been created (ie, from a button press) then you will see the ids:
class TestPanel(BoxLayout):
def __init__(self, **kwargs):
super(TestPanel, self).__init__(**kwargs)
print "TestPanel ids:", self.ids
def test(self, *x):
print self.ids
...
Button:
id: button_up
size_hint: 1, 0.08
text:'/\\'
on_press: root.test()
output:
{'button_down': <WeakProxy to <kivy.uix.button.Button object at 0x7f63159052c0>>, 'button_up': <WeakProxy to <kivy.uix.button.Button object at 0x7f63158f3738>>}