Customizing Popup Screen in Kivy 1.10.0 - python

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)

Related

How can I reach a spesific member of a kivy screen inside another class?

I'm quite new to python. I'm trying to enable a button of a kivy screen inside a popup but I failed to adress the related screen (and therefore button of that screen) as I am outside of its class. Can you show me how I can adress it properly ? Below I try to simulate the issue as plain as possible. (If you see any other areas of development, feel free to adress.) Thanks in advance...
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.uix.screenmanager import ScreenManager, Screen
popupWindow = ""
class S_Mananager(ScreenManager):
pass
class S_Primary(Screen):
def show_pop_up(self):
global popupWindow
popupWindow = Popup(
content = pup_Pass(),
size_hint = (None,None),
size = (250, 300)
)
popupWindow.open()
class pup_Pass(BoxLayout):
def btn_OK(self):
global popupWindow
popupWindow.dismiss()
S_Mananager.get_screen("scr_primary").ids.btn_enable_me.disabled = False
class S_Secondary(Screen):
pass
class Screens(App):
def build(self):
pass
if __name__ == '__main__':
Screens().run()
KV File:
<pup_Pass>:
orientation: "vertical"
size: root.width, root.height
Label:
text: "Label"
Button:
text: "OK"
on_release: root.btn_OK()
S_Mananager:
S_Primary:
S_Secondary:
<S_Primary>:
name: "win_Main"
BoxLayout:
id: "scr_primary"
orientation: "vertical"
size: root.width, root.height
Label:
text: 'Label'
Button:
text: 'Button'
on_release: root.show_pop_up()
Button:
id: "btn_enable_me"
text: 'Enable Me by Popup !'
on_release: root.show_pop_up()
disabled: True
<S_Secondary>:
Two problems:
You are trying to call get_screen() as though it were a class method, but it is an instance method. You need to call get_screen() through an instance of ScreenManager.
You should not use strings for ids.
The fix is to first change the id for the Button:
Button:
id: btn_enable_me # not a string
Then modify your python to use the instance of ScreenManager in your GUI:
def btn_OK(self):
global popupWindow
popupWindow.dismiss()
App.get_running_app().root.get_screen("win_Main").ids.btn_enable_me.disabled = False
Note that get_screen() requires the name of a Screen (which should be a string), not the id.

Passing Kivy Widget to another class

I am building a dynamic user interface using Python and Kivy. Because I need to add and remove widgets dynamically I want to use a separate class to handle adding and removing widgets from a GridLayout. I called this class LayoutManager.
The GridLayout is defined in my kv-File (id: "config_box_layout"). Inside my root widget class in my python code I am referencing the GridLayout via id. This is working properly. This reference is passed to the LayoutManager in the constructor. I tried passing it via ObjectProperty or GridLayout.
The problem is that I always end up with this kind of error if I try to remove widgets from the Layout:
'kivy.properties.ObjectProperty' object has no attribute 'remove_widget'
If I try to remove a widget in the save-method inside my Tab class using config_box_layout.remove_widget(some newly created Label) it's working properly.
I think the problem is that Kivy and all the kv-widgets are weakly-referenced and handling those references over to other classes seem to be not the intended use case.
I try to seperate classes to avoid doing all the coding stuff in one big fat main Layout class.
Looking forward to any help! :)
main.py
import kivy
from util.layoutManager import LayoutManager
from kivy.app import App
kivy.require('1.11.1')
from kivy.uix.label import Label
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.properties import ObjectProperty
from kivy.uix.popup import Popup
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from functools import partial
class ChooseDirectoryDialog(FloatLayout):
add = ObjectProperty(None)
cancel = ObjectProperty(None)
save = ObjectProperty(None)
class Tab(TabbedPanel):
config_add_src = ObjectProperty()
ingest_layout = ObjectProperty()
configManager = ConfigManager()
config_box_layout = ObjectProperty()
layoutManager = LayoutManager(config_box_layout)
def add_src(self):
content = ChooseDirectoryDialog(cancel=self.cancel, save=self.save)
self._popup = Popup(title="Add Source", content=content,
size_hint=(0.9, 0.9))
self._popup.open()
def cancel(self):
self._popup.dismiss()
def delete_source(self, description, widget):
self.configManager.delete_source(description)
self.remove_source_from_ui(description)
def remove_source_from_ui(self, description):
self.layoutManager.remove_configuration(description)
def save(self, srcpath, destpath, description):
desc_label = Label(text=description)
desc_label.size_hint_y = None
desc_label.height = 30
self.config_box_layout.add_widget(desc_label)
srcpath_label = Label(text=srcpath)
srcpath_label.size_hint_y = None
srcpath_label.height = 30
self.config_box_layout.add_widget(srcpath_label)
destpath_label = Label(text=destpath)
destpath_label.size_hint_y = None
destpath_label.height = 30
self.config_box_layout.add_widget(destpath_label)
deleteButton = Button(text="Quelle löschen")
deleteButton.size_hint_y = None
deleteButton.height = 30
deleteButton.bind(on_press=partial(self.delete_source, description))
self.config_box_layout.add_widget(deleteButton)
self.layoutManager.add_configuration(description,
desc_label, srcpath_label, destpath_label, deleteButton)
self.configManager.add_source(description, srcpath, destpath)
self._popup.dismiss()
def copyToDestination(self, srcpath, destpath):
pass
class AutoIngest(App):
def build(self):
return Tab()
if __name__ == '__main__':
#Builder.load_file('autoingest.kv')
AutoIngest().run()
autoingest.kv
#:kivy 1.11.1
<Tab>:
do_default_tab: False
config_add_button: add_button
config_box_layout: config_box
TabbedPanelItem:
text: 'Konfiguration'
TabbedPanel:
tab_width: 200
do_default_tab: False
TabbedPanelItem:
text: 'Quellen verwalten'
StackLayout:
orientation: "lr-tb"
padding: 10
Button:
size_hint: .2, .1
id: add_button
text: 'Quelle hinzufügen'
on_press: root.add_src()
GridLayout:
id: config_box
cols: 4
Label:
size_hint_y: None
height: 30
text: "Bezeichnung"
Label:
size_hint_y: None
height: 30
text: "Quell-Pfad"
Label:
size_hint_y: None
height: 30
text: "Ziel-Pfad"
Label:
size_hint_y: None
height: 30
text: "Aktionen"
<ChooseDirectoryDialog>:
text_input: text_input
BoxLayout:
size: root.size
pos: root.pos
orientation: "vertical"
Label:
size_hint_y: None
height: 30
text: "Bezeichnung"
TextInput:
id: text_input
size_hint_y: None
height: 30
multiline: False
Label:
size_hint_y: None
height: 30
text: "Quellverzeichnis auswählen"
FileChooserListView:
id: source_chooser
Label:
size_hint_y: None
height: 30
text: "Zielverzeichnis auswählen"
FileChooserListView:
id: destination_chooser
BoxLayout:
size_hint_y: None
height: 30
Button:
text: "Cancel"
on_release: root.cancel()
Button:
text: "Add"
on_release: root.save(source_chooser.path, destination_chooser.path, text_input.text)
layoutManager.py
from kivy.uix.gridlayout import GridLayout
from kivy.properties import ObjectProperty
class LayoutManager:
#I alrey tried to pass the GridLayout itself. This didn't work either.
def __init__(self, configlayout: ObjectProperty):
self.configurations = {}
self.configlayout = configlayout
def remove_configuration(self, description):
widgets = self.configurations.get(description)
for x in widgets:
self.configlayout.remove_widget(x)
def add_configuration(self, description, *widgets):
self.configurations[description] = {'widgets': widgets}
I believe the problem is that the Tab instance ids have not been setup when your lines:
config_box_layout = ObjectProperty()
layoutManager = LayoutManager(config_box_layout)
are executed, so config_box_layout is not yet set.
You can force the creation of the LayoutManager to delay until the ids are created using kivy.clock in the build() method of the App:
class AutoIngest(App):
def build(self):
Clock.schedule_once(self.setup_layoutManager)
return Tab()
def setup_layoutManager(self, dt):
self.root.layoutManager = LayoutManager(self.root.config_box_layout)
Also, the line:
layoutManager = LayoutManager(config_box_layout)
can be removed from the Tab class.
Once that is working you will discover issues in your remove_configuration() method of LayoutManager.

How to reference to an instance created in KV file

I want to call a method add_category_to_tree() from TreeCategory class, when pressing SAVE button in AddCategoryPopup class. However, I have problems how to reference to the TreeCategory instance, which is created in KV file.
I was trying to search solution, but nothing works. Currently i get AttributeError: 'super' object has no attribute '__getattr__' error.
How should I do this properly ? Thank you for help
class TreeCategory(TreeView):
def __init__(self, **kwargs):
super(TreeCategory, self).__init__(**kwargs)
def add_category_to_tree(self, name):
self.add_node(TreeViewLabel(text = name.upper()))
class AddCategoryPopup(Popup):
def save(self):
self.ids.tree.add_category_to_tree(self.ids.entry.text) # ????
db.adding_to_db('kategorie', 'nazwa', self.ids.entry.text)
self.dismiss()
def close(self):
self.dismiss()
class MainScreen(BoxLayout):
tree = ObjectProperty(None)
def add_category_button(self):
popup = AddCategoryPopup(title = 'Dodawanie nowej kategorii')
return popup.open()
class GuiCookBookApp(App):
def build(self):
self.title = "Książka kucharska"
return MainScreen()
if __name__ == "__main__":
db = DatabaseManager("cookbook.sqlite")
GuiCookBookApp().run()
KV file:
<AddCategoryPopup>:
BoxLayout:
orientation: 'vertical'
TextInput:
id: entry
multiline: False
hint_text: 'Podaj nazwę kategorii...'
BoxLayout:
orientation: 'horizontal'
Button:
text: 'SAVE'
on_press: root.save()
Button:
text: 'CANCEL'
on_press: root.close()
<MainScreen>:
orientation: "vertical"
display: entry
tree: tree
BoxLayout:
id: menu
size_hint_y: .1
Button:
text: 'Dodaj kategorię'
on_press: root.add_category_button()
BoxLayout:
id: recipe_view
orientation: "horizontal"
TreeCategory:
id: tree
hide_root: True
size_hint: .25, 1
With self.ids in Python you can only access the ids in KV from that particular class. So self.ids.tree is only possible inside the MainScreen class in Python not in the AddCategoryPopup class.
You could create an ObjectProperty 'topwidget' in your AddCategoryPopup rule and pass in the Main class when you instantiate the popup. Something like:
popup = AddCategoryPopup(topwidget=self)
Then in the 'save' method of your custom popup class you can do something like:
self.topwidget.tree...
You can do it quite a few ways. For instance you can put the .kv in your main .py file.
w = Builder.load_string('''
Widget:
height: self.width / 2. if self.disabled else self.width
x: self.y + 50
''')
https://kivy.org/docs/api-kivy.lang.builder.html
You can simply name the .kv file
guicookbookapp.kv
and leave it in the root directory of your project.
https://kivy.org/docs/examples/gen__application__app_with_kv__py.html
You can also add the following
from kivy.lang import Builder
Builder.load_file('guicookbookapp.kv')
Hopefully I understood your question correctly.

Kivy: Modifying a child widget of another separate class

im currently looking into kivy to start with crossplatform development. i have a bit of python experience (but basic) and now wanted to code a little game in kivy to get into. i probably wont finish this but i like learning stuff while doing it with something im intrested in.
Anyway my "App" is supposed to be seperated in two seperate "screens" the top one is only used for displaying stuff and the all interactive stuff is controlled from the bottom "screen".
Now i want to display some text in old school way by getting it written letter by letter to the screen.
This is working fine but for some reason the Label widget is only updated on screen if i call the "print_something" function from the top screen, if i call it from the bottom screen the function is indeed called but the Label widget wont change on screen.
Am i doing something wrong?
Here is a stripped version of the code:
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.clock import Clock
Builder.load_string('''
<MainUI>:
orientation: 'vertical'
# both these variables can be the same name and this doesn't lead to
# an issue with uniqueness as the id is only accessible in kv.
<Screen1>:
print_txt: print_txt
layout: layout
RelativeLayout:
id: layout
pos: 0, 400
size: 480, 400
Button:
pos: 0, 200
size_hint: (1, 0.2)
text: "Test Print"
on_press: root.print_something('TEST PRINT FROM SCREEN1')
AnchorLayout:
anchor_x: 'center'
anchor_y: 'bottom'
Label:
id: print_txt
padding_x: 10
markup: True
text_size: self.size
halign: 'left'
valign: 'top'
size_hint: (1, 0.2)
text: ""
<Screen2>:
btn1: btn1
RelativeLayout:
pos: 0, 0
size: 480, 400
Button:
id: btn1
pos_hint: {'x': .15, 'center_y': .5}
size_hint: (0.7, 0.5)
text: "Test Print"
on_press: root.print_text()
''')
class Screen1(Widget):
print_txt = ObjectProperty(None)
layout = ObjectProperty(None)
def print_something(self, string):
print 'Function called...'
self.print_txt.text = ''
counter = [0]
string_len = len(string)
def print_step(dt):
if counter[0] == string_len:
return False
else:
self.print_txt.text += string[counter[0]]
counter[0] = counter[0] + 1
Clock.schedule_interval(print_step, 2.0/60.0)
print 'Function End..'
class Screen2(Widget):
btn1 = ObjectProperty(None)
def __init__(self):
super(Screen2, self).__init__()
def print_text(self):
print 'Trying to print Text from Screen2 to Screen1'
target = Screen1()
target.print_something('TEST PRINT FROM SCREEN2')
class MainUI(Widget):
def __init__(self):
super(MainUI, self).__init__()
self.screen1 = Screen1()
self.add_widget(self.screen1)
self.add_widget(Screen2())
class MainApp(App):
def build(self):
Window.size = (480, 800)
return MainUI()
if __name__ == '__main__':
MainApp().run()
Your Screen2 print_text method creates a new Screen1 instance, which is modified but not displayed anywhere so you don't see anything change.
You could change the call to instead something like
on_press: root.parent.screen1.print_text()
...to access the print_text function of the Screen1 instance that you actually want to update.

kivy filechooserlistview not filling up screen

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.

Categories

Resources