This questions has been asked and answered quite often, but I still can't get it to work. I want to access a widget that is a children of another widget. For this I used an ObjectProperty. When I try to access the ObjectProperty (by opening any file via the FileChooser popup) to change the label's text I get this error:
self.pdfpages.text = "change to this" AttributeError:
'kivy.properties.ObjectProperty' object has no attribute 'text'
Do I need to initialize the ObjectProperty? Or is there a problem with my .kv structure?
main.py
import kivy
kivy.require('1.10.0') # replace with your current kivy version !
# Kivy Imports
from kivy.app import App
#UI Eleemnts
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.popup import Popup
from kivy.uix.scrollview import ScrollView
from kivy.uix.scatter import Scatter
from kivy.uix.image import Image
#Core Elements
from kivy.core.image import Image as CoreImage
from kivy.properties import ObjectProperty
# Python Imports
import os
class Window(BoxLayout):
#add child widgets (in kv file)
pass
class PDFView(ScrollView):
pdfpages = ObjectProperty()
def newPage(self, filepath):
#here the error occurs
self.pdfpages.text = "change to this"
class SideBar(BoxLayout):
def openfile(self):
print("Button pressed")
OpenDialog().open()
class OpenDialog(Popup):
def cancelfile(self):
#close popup
self.dismiss()
print("No file opened")
def openfile(self, path, selection):
#selection can contain multiple files, [0] is first or only
self.dismiss()
print("Opened File: " + os.path.join(path, selection[0]))
#open PDFView class
PDFView.newPage(PDFView, os.path.join(path, selection[0]))
class PDFEditor(App):
title = "PDFEditor"
#gets called on startup
#load program
def build(self):
#return root node
return Window()
if __name__ == '__main__':
PDFEditor().run()
PDFEditor.kv
<Window>:
#this is the main (root) "window"
orientation: "horizontal"
SideBar:
size_hint: (.1, 1)
PDFView:
size_hint: (.9, 1)
<PDFView>:
#handler for ObjectProperty
pdfpages: pdfpages
Scatter:
#only zoom
BoxLayout:
#add Images in here somehow
orientation: "vertical"
Label:
id: pdfpages
text: "hi"
<SideBar>:
orientation: "vertical"
Button:
id: btn_openfile
on_release: root.openfile()
text: "Open File"
Label:
text: "Hello!"
<OpenDialog>:
title: "Choose File"
BoxLayout:
#Fullscreen
size: root.size
pos: root.pos
orientation: "vertical"
FileChooserListView:
id: chsr_open
BoxLayout:
size_hint_y: None
height: 30
Button:
text: "Cancel"
on_release: root.cancelfile()
Button:
text: "Open"
on_release: root.openfile(chsr_open.path, chsr_open.selection)
The problem here is that you set the text of a new instance, wich is not even on the window, rather than the one you allready have.
To fix this, you need to access the one you have.
First, make your Window as an App attribute, so you can reference it later.
class PDFEditor(App):
title = "PDFEditor"
def build(self):
self.root = Window()
return self.root
Then give the PDFView an id in kv
<Window>:
orientation: "horizontal"
SideBar:
size_hint: (.1, 1)
PDFView:
id: pdfview
size_hint: (.9, 1)
Then in your openfile method, you can get the running apps root attribute you created earlier, like this.
def openfile(self, path, selection):
self.dismiss()
print("Opened File: " + os.path.join(path, selection[0]))
app = App.get_running_app()
pdf = app.root.ids.pdfview
pdf.newPage(os.path.join(path, selection[0]))
That way you can access the ids of your Window class
change your openfile method to this:
def openfile(self, path, selection):
#selection can contain multiple files, [0] is first or only
self.dismiss()
print("Opened File: " + os.path.join(path, selection[0]))
#open PDFView class
PDFView().newPage(PDFView, os.path.join(path, selection[0]))
PDFView is the class, PDFView() is an instance of this class, what you need
edit
the label does not change because you are creating a new one without replacing it. So if I understand well all you want is to replace the text of your PDFView. There is many ways to do that but I don't want to edit much your code: try this:
...
class OpenDialog(Popup):
...
def openfile(self, path, selection):
#selection can contain multiple files, [0] is first or only
self.dismiss()
print("Opened File: " + os.path.join(path, selection[0]))
#open PDFView class
app.window.ids.pdfview.newPage(PDFView, os.path.join(path, selection[0]))
...
...
class PDFEditor(App):
...
def build(self):
#return root node
self.window = Window()
return self.window
app = PDFEditor()
if __name__ == '__main__':
app.run()
in your kv:
...
<Window>:
#this is the main (root) "window"
orientation: "horizontal"
SideBar:
size_hint: (.1, 1)
PDFView:
id: pdfview
size_hint: (.9, 1)
...
Related
I'm learning Kivy and I'm on the MDExpansionPanel widget.
Data source from a JSON I use the keys to assemble my panels and the values to compose my Contents.
It happens that I'm able to do it but an extra line always appears in my contents.
I would like your help to delete this line.
I will post my code below:
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.expansionpanel import MDExpansionPanel, MDExpansionPanelOneLine
from kivy.properties import StringProperty, ObjectProperty
json_data = {'01001BA476': {'price': '74.73',
'product_code': '000003',
'quantity': '100'},
'0100251633': {'price': '92.07',
'product_code': '000156',
'quantity': '1000'}}
KV = '''
<ClassDetails>
orientation: 'vertical'
adaptive_height: True
OneLineIconListItem:
id: info_line
text: root.text
on_press: root.action()
IconLeftWidget:
icon: 'star'
on_press: print(f'star pressed on line: {info_line.text}')
ScrollView:
MDGridLayout:
id: box
cols: 1
adaptive_height: True
'''
class ClassDetails(MDBoxLayout):
text = StringProperty()
action = ObjectProperty()
class InvoicePanel(MDExpansionPanel):
pass
class Test(MDApp):
def build(self):
return Builder.load_string(KV)
def fechar_pedido(self):
print('You clicked on the information line')
def on_start(self):
for class_title, class_details in json_data.items():
cd = ClassDetails()
expansion_panel = InvoicePanel(panel_cls=MDExpansionPanelOneLine(text=f'Invoice #: {class_title}'), content=cd)
self.root.ids.box.add_widget(expansion_panel)
for item in class_details.items():
cd.add_widget(ClassDetails(
text=str(class_details.values()), action=self.fechar_pedido))
Test().run()
I think the extra line is coming from setting your initial content to a ClassDetails instance, which has a OneLineIconListItem in its definition. Try replacing the content with a simple MDBoxLayout instead:
def on_start(self):
for class_title, class_details in json_data.items():
print(class_title, class_details)
# cd = ClassDetails()
cd = MDBoxLayout(orientation='vertical', adaptive_height=True)
expansion_panel = InvoicePanel(panel_cls=MDExpansionPanelOneLine(text=f'Invoice #: {class_title}'), content=cd)
self.root.ids.box.add_widget(expansion_panel)
for item in class_details.items():
print('\titem:', item)
cd.add_widget(ClassDetails(
text=str(class_details.values()), action=self.fechar_pedido))
I was trying to add the result to a new screen whose content should be dynamic. I tried a few of the trivial ways given in StackOverflow itself but failed to show any content. my code goes as such.
I am entering multiple data, separated by "," without spaces, using text input, and then splitting it which will be stored as a list. The data will be parsed and an equivalent number of labels should be shown on the output screen which I tried but was not successful in execution (line 51-56 in anbs.py).
For example, the input is "I,am,a,good,boy".
The result should be as it will be in the console, i.e. text of each Label should contain an individual item, but nothing goes to the output screen... just a big button that is used to traverse between the screens.
This is my anbs.py file.
from kivy.app import App
from kivy.lang.builder import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.popup import Popup
from kivy.uix.label import Label
from kivy.properties import ObjectProperty
from kivy.core.window import Window
from kivy.factory import Factory
Window.size = (600, 600)
class HelpWindow(Screen):
"""
This is a help window. It contains the functionality of all the given boxes
on the main window.
"""
def main_window(self):
sm.current = "main"
class MainWindow(Screen):
"""
This is the main window that contains the main form.
This connects the frontend of the app to the backend
"""
target = ObjectProperty(None)
def v_popup(self):
version_popup()
def help(self):
sm.current = "help"
def output_window(self):
sm.current = "output"
def get_results(self):
#OutputWindow.main(options = options)
out_window = OutputWindow()
out_window.main(self.target.text)
sm.current = "output"
class OutputWindow(Screen):
"""
This is the output window. All the generated results will be seen here.
"""
res = ObjectProperty(None)
res_out = ObjectProperty(None)
def main(self, options):
options = list(options.split(","))
for item in options:
print(item)
self.res_out.add_widget(Label(text=item))
def main_window(self):
sm.current = "main"
class WindowManager(ScreenManager):
pass
def version_popup():
"""
Version Popup Window.
"""
version = "v1.0"
version_text = "this is "+version+" for this app"
vpop = Popup(title="Version",
content=Label(text=version_text),
size_hint=(None, None), size=(400, 400))
vpop.open()
### main builder and WindowManager object
kv = Builder.load_file("start.kv")
sm = WindowManager()
### Adding screens to widget
screens = [MainWindow(name="main"), HelpWindow(name="help"), OutputWindow(name="output")]
for screen in screens:
sm.add_widget(screen)
sm.current = "main"
### main working
class AnbsApp(App):
def build(self):
return sm
if __name__ == '__main__':
AnbsApp().run()
and my start.kv file looks like this
<HelpWindow>
name:"help"
Button:
id:ms
text:"Main Screen"
on_release:
root.manager.transition.direction = "left"
root.main_window()
<MainWindow>
name:"main"
target : target
GridLayout:
cols:1
GridLayout:
cols:3
row_force_default: True
row_default_height: 50
Button:
id:hp
text:"Help"
on_release:
root.manager.transition.direction = "right"
root.help()
Button:
id:version
text: "Version"
on_release:
root.v_popup()
GridLayout:
cols:2
row_force_default: True
row_default_height: 30
Label:
text:"Target *"
TextInput:
id:target
multiline:False
GridLayout:
cols:3
row_force_default: True
row_default_height: 50
Label:
text:""
Button:
text:"Submit"
on_release:
root.get_results()
Label:
text:""
<OutputWindow>
name:"output"
res_out:res_out
GridLayout:
id:res_out
cols : 1
Button:
id:ms
text:"Main Screen"
on_release:
root.manager.transition.direction = "right"
root.main_window()
I can't really say whats the major point I am missing in it.
Your code:
def get_results(self):
#OutputWindow.main(options = options)
out_window = OutputWindow()
out_window.main(self.target.text)
sm.current = "output"
is creating a new instance of OutputWindow in the line:
out_window = OutputWindow()
and then calling the main() method of that new instance. However, that new instance is not in your GUI, so no effect is observed. To correct that, change the code to use the instance of OutputWindow that is in your GUI:
def get_results(self):
out_window = self.manager.get_screen('output') # get OutputWindow instance
out_window.main(self.target.text)
sm.current = "output"
You will also need to adjust the size/position of the Button in the OutputWindowso that it does not completely cover the GridLayout.
I have two screens. MainScreen and Playlist. MainScreen has a RecycleView method added that displays list of items. These items are created from folder names(directories). I want the files inside each folder(directories) to open on the Playlist Screen depending on which directory name is clicked.
I intend to clear the screen immediately after the user leaves the screen.
from kivymd.app import MDApp
from kivymd.uix.button import MDFlatButton, MDRectangleFlatButton
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.recycleview import RecycleView
from kivy.uix.screenmanager import ScreenManager, Screen
from kivymd.uix.screen import MDScreen
from pathlib import Path, PurePath
# Music Path
storageLocation = Path.cwd()
if Path('Books').is_dir():
storageLocation = Path.cwd() / 'Books'
class RecycleViewRow(BoxLayout):
text = StringProperty()
class MainScreen(MDScreen):
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
booksdir = [f for f in storageLocation.iterdir() if f.is_dir()]
self.children[0].data = [{'text': str(x), 'id': str(x)} for x in booksdir]
# print(self.children[0].data)
class Playlist(MDScreen):
def on_pre_enter(self, *args):
self.ids.Box.clear_widgets()
#self.ids.Box.add_widget(MDRectangleFlatButton(text="done", ))
print(self.manager.ids)
return
class Main(MDApp):
def build(self):
sm = ScreenManager()
sm.add_widget(MainScreen(name='MainScreen'))
sm.add_widget(Playlist(name='Playlist'))
sm.add_widget(Test(name="Test"))
return sm
Main().run()
.kv
<RecycleViewRow>:
orientation: 'vertical'
Button:
text: root.text
#on_press: app.root.message_box(root.text)
on_release:app.root.current = 'Playlist'
<MainScreen>:
RecycleView:
id: rv
viewclass: 'RecycleViewRow'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
<Playlist>
BoxLayout:
orientation: 'vertical'
id:Box
You can trigger a method from your RecycleViewRow, like this:
<RecycleViewRow>:
orientation: 'vertical'
Button:
text: root.text
on_release: app.fill_playlist(root.text)
Then, the the fill_playlist() method. you can fill the PlayList:
def fill_playlist(self, dir):
self.root.current = 'Playlist' # this also clears the play list
playlist = self.root.get_screen('Playlist')
for sub in os.listdir(dir):
playlist.ids.Box.add_widget(MDRectangleFlatButton(text=sub))
So, I'm new to kivy and can't get this code to work. I'm trying to update a button text with a popup text input. The popup shows when the button is pressed, and when it's dismissed it should update the text on the button with whatever text was typed in it.
I've tried many variations of this code, but no one have worked. Either nothing happens or I get this error:
AttributeError: 'super' object has no attribute '__getattr__'
Here it is:
main.py
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.popup import Popup
class MainWidget(GridLayout):
pass
class PopText(Popup):
def textChange(self):
MyButton().change()
def getText(self):
text = self.display
return text
class MyButton(AnchorLayout):
def change(self):
self.ids.equip_bt.text = PopText().getText()
print(self.ids.my_bt.text)
class MyApp(App):
def build(self):
return MainWidget()
if __name__ == "__main__":
MyApp().run()
my.kv
#:kivy 1.10.0
#:import F kivy.factory.Factory
<PopText>:
size_hint: .7, .3
title: "Input Text"
on_dismiss: self.textChange()
display: pop_text.text
TextInput:
id: pop_text
focus: True
multiline: False
on_text_validate: root.dismiss()
<MyButton>:
anchor_y: "top"
anchor_x: "right"
Button:
id: my_bt
text: "Input Text"
on_release: F.PopText().open()
<MainWidget>:
cols: 1
rows: 2
MyButton:
MyButton:
Any ideas on how to solve this?
Here is a minimum example of what you are trying to achieve. The hard part is connection the button from the Popup to the Button which opened it. I am going through the app class to achieve that. I got to admit it is not a pretty solution.
from kivy.app import App
from kivy.uix.popup import Popup
from kivy.uix.button import Button
class PopupBttn(Button):
def openPopup(self):
Pop = TextPopup().open(self.text)
class TextPopup(Popup):
def open(self, text, **kwargs):
super(TextPopup, self).open(**kwargs)
self.ids.txtipt.text = text
class MyApp(App):
pass
if __name__ == "__main__":
MyApp().run()
kv file:
BoxLayout:
PopupBttn:
id: bttn
text: 'open Popup'
on_press: self.openPopup()
<TextPopup>:
BoxLayout:
orientation: "vertical"
TextInput:
id: txtipt
Button:
text: "OK"
on_release: app.root.ids.bttn.text=root.ids.txtipt.text
on_release: root.dismiss()
Here is an updated version to use multiple buttons. Unfortunately, you will need to set ids and name to the string of id per Button.
python file
from kivy.app import App
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.properties import StringProperty
class PopupBttn(Button):
def openPopup(self):
print(self.name)
Pop = TextPopup().open(self.text, self.name)
class TextPopup(Popup):
bttnid = StringProperty()
text = StringProperty()
def open(self, text, id, **kwargs):
super(TextPopup, self).open(**kwargs)
self.ids.txtipt.text = text
self.bttnid = id
def setText(self):
App.get_running_app().root.ids[self.bttnid].text = self.text
class MyApp(App):
pass
if __name__ == "__main__":
MyApp().run()
kv file
BoxLayout:
orientation: 'vertical'
PopupBttn:
name: 'one'
id: one
text: 'I am the first Button'
PopupBttn:
name: 'two'
id: two
PopupBttn:
name: 'three'
id: three
PopupBttn:
name: 'four'
id: four
text: 'I am the fourth button'
<PopupBttn>:
text: 'open Popup'
on_press: self.openPopup()
<TextPopup>:
text: txtipt.text
BoxLayout:
orientation: "vertical"
TextInput:
id: txtipt
Button:
text: "OK"
on_release: root.setText()
on_release: root.dismiss()
In order to update both buttons, you need to assign unique id to each of them. Please refer to the example below for details.
Example
main.py
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.popup import Popup
from kivy.properties import ObjectProperty
class MainWidget(GridLayout):
btn_top = ObjectProperty()
btn_bottom = ObjectProperty()
class PopText(Popup):
pass
class MyButton(AnchorLayout):
my_btn = ObjectProperty()
class TestApp(App):
title = "Changing button text with popup text input Kivy"
def build(self):
return MainWidget()
if __name__ == "__main__":
TestApp().run()
test.kv
#:kivy 1.10.0
#:import F kivy.factory.Factory
<PopText>:
size_hint: .7, .3
title: "Input Text"
TextInput:
focus: True
multiline: False
on_text_validate:
app.root.btn_top.my_btn.text = self.text
app.root.btn_bottom.my_btn.text = self.text
root.dismiss()
<MyButton>:
my_btn: my_bt
anchor_y: "top"
anchor_x: "right"
Button:
id: my_bt
text: "Input Text"
on_release: F.PopText().open()
<MainWidget>:
btn_top: btn_top
btn_bottom: btn_bottom
cols: 1
rows: 2
MyButton:
id: btn_top
MyButton:
id: btn_bottom
Output
I'm trying to make a simple application in Python 3.5 and kivy that starts with a simple screen and when you click on it, goes to another one which shows 3 lists that let you chose the data:
The Python file:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.listview import ListItemButton
from kivy.properties import ListProperty
from dataTopy import rlists
# Transition des ecrans:
class MainScreen(Screen):
pass
class AnotherScreen(Screen):
pass
class ScreenManagement(ScreenManager):
pass
presentation = Builder.load_file("ex44.kv")
#
class FirstListItemButton(ListItemButton):
pass
class SecondListItemButton(ListItemButton):
pass
class ThirdListItemButton(ListItemButton):
pass
class Ex44(BoxLayout):
d1 = ListProperty([str(i) for i in range(1990,2014)] )
d2 = ListProperty(['']*100)
d3 = ListProperty(['']*100)
def change(self,c):
try: self.d2,self.d3 = rlists(int(c.text))
except:
import os
CurDir = os.getcwd()
print('Can not find data in ' + CurDir)
def change1(self,c):
print('M => '+c.text)
def change2(self,c):
print('F => '+c.text)
class Ex44App(App):
def build(self):
return presentation
if __name__ == '__main__':
Ex44App().run()
The kivy file:
#: import FadeTransition kivy.uix.screenmanager.FadeTransition
#: import ListAdapter kivy.adapters.listadapter.ListAdapter
#: import ex44 ex44
ScreenManagement:
transition: FadeTransition()
MainScreen:
AnotherScreen:
<MainScreen>:
name: "main"
Button:
on_release: app.root.current = "other"
text: "Next Screen"
font_size: 50
<AnotherScreen>:
name: "other"
BoxLayout:
Ex44
Button:
color: 0,1,0,1
font_size: 25
size_hint: 0.3,0.2
text: "Back Home"
on_release: app.root.current = "main"
pos_hint: {"right":1, "top":1}
<FirstListItemButton>:
on_press: app.root.change(*args)
<SecondListItemButton>:
on_press: app.root.change1(*args)
<ThirdListItemButton>:
on_press: app.root.change2(*args)
<Ex44>:
ListView:
adapter:
ListAdapter(data=root.d1,
selection_mode='single',
cls=ex44.FirstListItemButton)
ListView:
adapter:
ListAdapter(data=root.d2,
selection_mode='single',
cls=ex44.SecondListItemButton)
ListView:
adapter:
ListAdapter(data=root.d3,
selection_mode='single',
cls=ex44.ThirdListItemButton)
When I try to run the app, it tells me: "Unknown class "
It is weird because the class Ex44 works alone but not when I'm trying to add it into the main application logic.
I've tried to return a widget instead of a BoxLayout for the class, to return Ex44 alone in the kivy file, etc. but I always get the same error in return.
Is it possible to return a BoxLayout inside another one in Kivy?
You are building the kv file too soon (before the class is defined). move the Builder.from_file call to the build method
...
def build(self):
return Builder.load_file("ex44.kv")