Versions
Python: 3.7
OS: Windows 10
Kivy: 1.11.1
Kivy installation method: pip
Description
FileChooser overlaps text on scrolling through files list. Looks like the 1st content stays and on scroll the content of scrolled data is getting displayed on top of 1st content.
Code and Logs
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.popup import Popup
from kivy.properties import ObjectProperty, BoundedNumericProperty, StringProperty
from kivy.lang import Builder
class FirstWindow(Screen):
def show_load(self):
content = LoadDialog(load=self.load, cancel=self.dismiss_popup)
self._popup = Popup(title="Load file", content=content,
size_hint=(0.9, 0.9))
self._popup.open()
def dismiss_popup(self):
self._popup.dismiss()
def cancel(self):
pass
def load(path, selection):
print(path, selection)
class LoadDialog(FloatLayout):
load = ObjectProperty(None)
cancel = ObjectProperty(None)
class EditorApp(App):
def build(self):
kv = Builder.load_file("editor.kv")
self.screen_manager = ScreenManager()
screen = FirstWindow(name="first")
self.screen_manager.add_widget(screen)
self.screen_manager.current = "first"
return self.screen_manager
if __name__ == "__main__":
editor_app = EditorApp()
editor_app.run()
KV File
<FirstWindow>:
BoxLayout:
orientation: "vertical"
Button:
text: "Select Folder"
on_release: root.show_load()
<LoadDialog>:
BoxLayout:
size: root.size
pos: root.pos
orientation: "vertical"
FileChooserListView:
id: filechooser
BoxLayout:
size_hint_y: None
height: 30
Button:
text: "Cancel"
on_release: root.cancel()
Button:
text: "Load"
on_release: root.load(filechooser.path, filechooser.selection)
Screenshots
For people who are facing the same issue, I resolved by replacing FileChooser with Plyer's native filechooser. Disussion on Github can be found here
Just remove the line
kv = Builder.load_file("editor.kv")
your code will work fine
Related
I'm trying to make an app. On button click on the bottom right of the screen there appears a dialog window(popup). On "Done" click the popup window closes (close_dialog method), and a new List Item is expected to appear.
Unfortunately the error occurs on "Done" click:
AttributeError: 'DialogContent' object has no attribute 'get_screen'
Could you please tell me why does the error occur and how can I fix it?
I suppose that it is caused by the fact that DialogContent class inherits from BoxLayout (not from Screen) but I don't know how to fix it.
Code .py:
from kivy.lang import Builder
from kivy.core.window import Window
from kivymd.app import MDApp
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.scrollview import ScrollView
from kivymd.uix.button import MDFlatButton
from kivymd.uix.dialog import MDDialog
from kivymd.uix.textfield import MDTextField
from kivy.uix.textinput import TextInput
from kivy.uix.screenmanager import Screen, ScreenManager
from kivymd.uix.list import TwoLineAvatarListItem
Window.size = (288, 511)
class GroupScreen(Screen):
pass
class DialogContent(BoxLayout):
pass
class MainScreen(Screen):
dialog = None
def show_dialog(self, *args):
'''
Create group creation popup
'''
if not self.dialog:
self.dialog = MDDialog(
title="Create new group",
type="custom",
content_cls=DialogContent(),
auto_dismiss=False
)
self.dialog.open()
def close_dialog(self, *args):
'''
Close popup on Done click
'''
self.dialog.dismiss()
self.new_window()
def new_window(self, *args):
'''
Create new group button
'''
mylist = TwoLineAvatarListItem(text = self.dialog.content_cls.textfield.text,
secondary_text = "1,2,3...")
self.mdlist.add_widget(mylist)
class test2App(MDApp):
def build(self):
sm = ScreenManager()
sm.add_widget(MainScreen(name='main'))
sm.add_widget(GroupScreen(name='group'))
scroll = ScrollView()
return sm
if __name__ == '__main__':
test2App().run()
Code .kv:
ScreenManager:
MainScreen:
GroupScreen:
<DialogContent>:
textfield: textfield
orientation: "vertical"
spacing: "12dp"
size_hint_y: None
height: "120dp"
MDTextField:
id: textfield
hint_text: "Group name"
MDFlatButton:
id: btn1
text: "Done"
text_color: self.theme_cls.primary_color
on_release: root.get_screen['main'].close_dialog()
<MainScreen>:
name: 'main'
mdlist: mdlist
FloatLayout:
size_hint: 1, 0.89
ScrollView:
MDList:
id: mdlist
MDFloatingActionButton:
pos_hint: {'right': 0.95, 'y': 0.05}
icon: "android"
theme_text_color: "Custom"
text_color: app.theme_cls.primary_color
on_release:
root.show_dialog()
<GroupScreen>:
name: 'group'
MDLabel:
text: 'Welcome'
halign: 'center'
MDRectangleFlatButton:
text: 'Back'
pos_hint: {'center_x': 0.5, 'center_y': 0.3}
on_release: root.manager.current = 'main'
Change:
on_release: root.get_screen['main'].close_dialog()
to:
on_release: app.root.get_screen('main').close_dialog()
The app.root gets you a reference to the root widget of the app, which is the ScreenManager. Then you can use get_screen('main') to access the main Screen and call its close_dialog() method.
I'm trying to make a app. On button click on the bottom right of the screen there appears a dialog window(popup). On "Done" click the popup window closes (close_dialog method), and a new List Item appears. The text of the List Item is expected to be obtained from MDTextField of the popup by this code:
mylist = TwoLineAvatarListItem(text = DialogContent().textfield.text,
secondary_text = "1,2,3...")
As far as I know I need to refer to DialogContent class from another class Container in the .kv file, but I don't know how to do it properly. How can I fix it?
Code.py:
from kivy.lang import Builder
from kivy.core.window import Window
from kivymd.app import MDApp
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.scrollview import ScrollView
from kivymd.uix.button import MDFlatButton
from kivymd.uix.dialog import MDDialog
from kivymd.uix.textfield import MDTextField
from kivy.uix.textinput import TextInput
from kivy.uix.screenmanager import Screen, ScreenManager
from kivymd.uix.list import TwoLineAvatarListItem
Window.size = (288, 511)
class DialogContent(BoxLayout):
pass
class Container(Screen):
dialog = None
def show_dialog(self, *args):
'''
Create group creation popup
'''
if not self.dialog:
self.dialog = MDDialog(
title="Create new group",
type="custom",
content_cls=DialogContent(),
auto_dismiss=False
)
self.dialog.open()
def close_dialog(self, *args):
'''
Close popup on Done click
'''
self.dialog.dismiss()
self.new_window()
def new_window(self, *args):
'''
Create new group button
'''
mylist = TwoLineAvatarListItem(text = DialogContent().textfield.text,
secondary_text = "1,2,3...")
self.mdlist.add_widget(mylist)
class grudget4App(MDApp):
def build(self):
container = Container()
scroll = ScrollView()
return container
if __name__ == '__main__':
grudget4App().run()
Code .kv:
<DialogContent>:
textfield: textfield
orientation: "vertical"
spacing: "12dp"
size_hint_y: None
height: "120dp"
MDTextField:
id: textfield
hint_text: "Group name"
MDFlatButton:
id: btn1
text: "Done"
text_color: self.theme_cls.primary_color
on_release: app.root.close_dialog()
<Container>:
mdlist: mdlist
FloatLayout:
size_hint: 1, 0.89
ScrollView:
MDList:
id: mdlist
MDFloatingActionButton:
pos_hint: {'right': 0.95, 'y': 0.05}
icon: "icon.png"
theme_text_color: "Custom"
text_color: app.theme_cls.primary_color
on_release:
root.show_dialog()
Screen:
NavigationLayout:
ScreenManager:
Screen:
BoxLayout:
orientation: 'vertical'
MDToolbar:
title: ''
size_hint: 1, 0.14
left_action_items: [["menu", lambda x: nav_drawer.toggle_nav_drawer()]]
elevation:10
Widget:
MDNavigationDrawer:
id: nav_drawer
The code:
mylist = TwoLineAvatarListItem(text = DialogContent().textfield.text,
secondary_text = "1,2,3...")
is creating a new instance of DialogContent, unrelated to the DialogContent that appears in the display. To access the correct instance use:
mylist = TwoLineAvatarListItem(text = self.dialog.content_cls.textfield.text,
secondary_text = "1,2,3...")
I am new to python and kivymd and I am trying to develop a program for data entry. However, when I create a drop-down menu for a selection, an error occurred and I can't update the value of the text field after I select an item.
Here is the python code:
from kivymd.app import MDApp
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from kivy.clock import Clock
from kivy.properties import ObjectProperty
from kivymd.uix.menu import MDDropdownMenu
class AMRMenu(Screen):
def GIbutton(self):
sm.current = 'GI'
class GIWindow(Screen):
weather = ObjectProperty(None)
menu_weather_items = [{"text":"Sunny"},{"text":"Cloudy"},{"text":"Raining"}]
menu_FeedResponse_items=[{"text":"High"},{"text":"Medium"},{"text":"Low"}]
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.menu = MDDropdownMenu(
items=self.menu_weather_items,
width_mult=4,
caller = self.weather,
callback=self.set_item)
def set_item(self, instance):
def set_item(interval):
self.weather.text = instance.text
self.menu.dismiss()
Clock.schedule_once(set_item, 0.5)
class WindowManager(ScreenManager):
pass
sm = WindowManager()
class MainApp(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
kv = Builder.load_file("FR.kv")
def build(self):
screens = [AMRMenu(name = "menu"), GIWindow(name = "GI")]
for screen in screens:
sm.add_widget(screen)
sm.current = "menu"
return sm
if __name__ == "__main__":
MainApp().run()
And here is the kv. file:
<AMRMenu>:
name:"menu"
BoxLayout:
orientation: "vertical"
MDToolbar:
title: "Main Menu"
MDList:
OneLineListItem:
text: "General Information"
on_press: root.GIbutton()
OneLineListItem:
text: "Water Temperature"
OneLineListItem
text: "Feeding Amount"
OneLineListItem:
text: "Residue and Feeding response"
OneLineListItem:
text: "Dead fish"
OneLineListItem:
text: "Sell/Use"
<GIWindow>
name: "GI"
weather: weather
ScrollView:
id: screen
MDBoxLayout:
orientation: 'vertical'
adaptive_height: True
MDTextField:
id: weather
pos_hint: {'center_x': .5, 'center_y': .5}
hint_text: "Weather"
icon_right: "arrow-down-drop-circle-outline"
input_filter: lambda text, from_undo: text[:5 - len(self.text)]
on_focus: root.menu.open()
Here is the error message:
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/kivy/core/window/__init__.py", line 1297, in add_widget
(widget, widget.parent)
kivy.uix.widget.WidgetException: Cannot add <kivymd.uix.menu.MDDropdownMenu object at 0x7fd2fa31a6e0> to window, it already has a parent <kivy.core.window.window_sdl2.WindowSDL object at 0x7fd2f65826e0>
I don't know why I made this happen. It helps a lot if someone figures it out. Thank you for reading this question
The problem is that your kv line:
on_focus: root.menu.open()
is opening the menu every time the focus changes. So, it tries to open the menu even when the focus becomes false. An easy fix is to just open the menu when focus is True`:
on_focus: if self.focus: root.menu.open()
For the sake of brevity I'll explain my problem before posing the code. Here it goes:
I have a Screen class in Kivy that holds two widgets, a GridLayout and an Image. The latter is fine, but the buttons are extremely oversized:
And here's my main.py:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.factory import Factory
from kivy.uix.gridlayout import GridLayout
from kivy.uix.image import Image
from kivy.config import Config
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
import configparser
Builder.load_file('kv\MainMain.kv')
#read configurations
config = configparser.RawConfigParser()
config.read('config.ini')
#read config values
width = config.getint('Default', 'MAX_WINDOW_WIDTH')
height = config.getint('Default', 'MAX_WINDOW_HEIGHT')
border = config.getint('Default', 'BORDERLESS')
#apply config values
Config.set('graphics','width', width)
Config.set('graphics', 'height', height)
Config.set('graphics', 'borderless', border)
Config.set('input', 'mouse', 'mouse,multitouch_on_demand')
#create screen manager
class ScreenManager(ScreenManager):
pass
#create background widget
class BackGround(Image):
pass
#image buttons
class MainButtons(GridLayout):
pass
#create main screen:
class MainScreen(Screen):
pass
#create main app
class MainMainApp(App):
def build(self):
return MainScreen()
#register class
Factory.register(MainScreen, cls=MainScreen)
#run
if __name__ == '__main__':
MainMainApp().run()
And here's my kv file:
<ScreenManager>:
id: screen_manager
MainScreen:
id: main_screen
name: 'MainScreen'
manager: screen_manager
ReadScreen:
id: read_screen
name: 'ReadScreen'
manager: screen_manager
<MainScreen>:
BackGround:
id: back_ground
source: 'images\\app_background.jpg'
size: root.width, root.height
MainButtons:
cols: 1
pos: root.width / 2 - 100, root.height / 4
size: 20, 10
Button:
id: button_read
text: "Read"
on_press: root.callback_read()
Button:
id: button_add
text: "Add"
Button:
id: button_manage
text: "Manage"
I'm really swamped on this one. Thanks for your help.
You can use size_hint for this.
Here is an example
The pyhton file:
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen,ScreenManager
Builder.load_file("my.kv")
class MainButtons(GridLayout):
pass
class MainScreen(Screen):
pass
class MainMainApp(App):
def build(self):
self.sm = ScreenManager()
self.sm.add_widget(MainScreen(name="main"))
return self.sm
if __name__ == '__main__':
MainMainApp().run()
and kivy file:
<MainScreen>:
MainButtons:
cols: 1
rows: 4
Label:
font_size: "40sp"
text: "Something"
Button:
size_hint: (1,0.1)
text: "Read"
Button:
size_hint: (1,0.1)
text: "Add"
Button:
size_hint: (1,0.1)
text: "Manage"
And output will be:
And if you want the buttons to be smaller in width, you add ´size_hint_x: None´ to your kv file, like this.
<MainScreen>:
MainButtons:
cols: 1
rows: 4
Label:
font_size: "40sp"
text: "Something"
Button:
size_hint: (1,0.1)
size_hint_x:None
text: "Read"
Button:
size_hint: (1,0.1)
size_hint_x:None
text: "Add"
Button:
size_hint: (1,0.1)
size_hint_x:None
text: "Manage"
Output will now be:
I have built a Kivy GUI using the 'App' class that is designed to load a file using a file chooser and make it into a list. I have now learned that my whole chunk of code needs to be converted to a widget so that it can be integrated into an existing GUI scaffold. I am not sure how to go about 'widget-ifying' my code. Any help would be appreciated.
Below is my main.py file:
from kivy.app import App
from kivy.uix.accordion import Accordion, AccordionItem
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.lang import Builder
from kivy.factory import Factory
from kivy.uix.popup import Popup
from kivy.uix.button import Button
import os
from kivy.uix.widget import Widget
from kivy.uix.scrollview import ScrollView
from kivy.graphics import *
from kivy.properties import ObjectProperty
class OptionMenu(GridLayout):
loadfile = ObjectProperty(None)
savefile = ObjectProperty(None)
text_input = ObjectProperty(None)
def dismiss_popup(self):
self._popup.dismiss()
def show_load(self):
content = LoadDialog(load=self.load, cancel=self.dismiss_popup)
self._popup = Popup(title="Load file", content=content, size_hint=(0.9, 0.9))
self._popup.open()
def load(self, path, filename):
with open(os.path.join(path, filename[0])) as stream:
self.text_input.text = stream.read()
self.dismiss_popup()
def show_save(self):
content = SaveDialog(save=self.save, cancel=self.dismiss_popup)
self._popup = Popup(title="Save file", content=content,
size_hint=(0.9, 0.9))
self._popup.open()
def save(self, path, filename):
with open(os.path.join(path, filename), 'w') as stream:
stream.write(self.text_input.text)
self.dismiss_popup()
class GraphMenu(GridLayout):
pass
class UnnamedMenu(GridLayout):
pass
class LoadDialog(FloatLayout):
load = ObjectProperty(None)
cancel = ObjectProperty(None)
class SaveDialog(FloatLayout):
save = ObjectProperty(None)
text_input = ObjectProperty(None)
cancel = ObjectProperty(None)
class MainController(FloatLayout):
pass
class MainApp(App):
def build(self):
rt = MainController()
return rt
Factory.register('Main', cls=MainController)
Factory.register('LoadDialog', cls=LoadDialog)
Factory.register('SaveDialog', cls=SaveDialog)
if __name__ == '__main__':
MainApp().run()
Here is my main.kv file:
#kivy 1.0
<OptionMenu>
cols: 2
row:10
Button:
size_hint_y: 0.05
text: "Input File"
on_release: self.parent.show_load()
Button:
size_hint_y:0.05
text:"Save File"
on_release:self.parent.show_save()
TextInput:
id: text_input
text:''
RstDocument:
text: text_input.text
show_errors: True
<MainController>
BoxLayout:
orientation: 'horizontal'
Accordion:
size_hint_x: 0.3
orientation: 'vertical'
AccordionItem:
title:'Options'
OptionMenu:
id: 'AppOpt'
AccordionItem:
title:'Config'
UnnamedMenu:
id:ConfigTab
AccordionItem:
title:'Graph'
GraphMenu:
id:GraphTab
FloatLayout:
size_hint_x: 0.7
Label:
<scroller>
<LoadDialog>:
BoxLayout:
size: root.size
pos: root.pos
orientation: "vertical"
FileChooserListView:
id: filechooser
BoxLayout:
size_hint_y: None
height: 10
Button:
text: "Cancel"
on_release: root.cancel()
Button:
text: "Load"
on_release: root.load(filechooser.path, filechooser.selection)
<SaveDialog>:
text_input: text_input
BoxLayout:
size: root.size
pos: root.pos
orientation: "vertical"
FileChooserListView:
id: filechooser
on_selection: text_input.text = self.selection and self.selection[0] or ''
TextInput:
id: text_input
size_hint_y: None
height: 30
multiline: False
BoxLayout:
size_hint_y: None
height: 30
Button:
text: "Cancel"
on_release: root.cancel()
Button:
text: "Save"
on_release: root.save(filechooser.path, text_input.text)
I don't understand the question, your App does nothing special and all the functionality is in the MainController widget. What more separation do you need than that?
Factory.register('Main', cls=MainController)
Factory.register('LoadDialog', cls=LoadDialog)
Factory.register('SaveDialog', cls=SaveDialog)
Also, these Factory lines are unnecessary, widgets automatically register themselves.