How to dynamically update label texts in kivy that is imported from excel file? - python-3.x

I am creating a questionnaire form in kivy. I have added few label widgets in my GUI. I don't want to define label texts statically in my code, instead my objective is to dynamically update label texts that is fetched from an excel file.
For example: my excel file has 2 questions:
Name of the company?
Department?
I have 2 label widgets in my GUI, and the text of widgets should be:
Name of the company?
Department?
respectively and has to be dynamically fetched from the excel file.
I encountered an error when i tried to run my code.
Questionnaire.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
import pandas as pd
class FetchData():
file = pd.read_excel("Questionnaire.xlsx")
Quest = file['QUESTIONS']
class Questions(Widget):
Data = FetchData().Quest
qvars =[]
company = ObjectProperty(None)
department = ObjectProperty(None)
qvars.append(company)
qvars.append(department)
def validate(self):
for i in range(len(self.qvars)):
self.qvars[i].text = self.Data[i]
class QuestionnaireApp(App):
def build(self):
return Questions()
if __name__=="__main__":
QuestionnaireApp().run()
Questionnaire.kv
<Questions>:
company:company
department:department
GridLayout:
cols:1
size:root.width, root.height
GridLayout:
cols:1
Label:
id:company
TextInput:
Label:
id:department
TextInput:
Button:
text:"process"
on_release: root.validate()
I am getting the following error:
File "C:/Users/pavan m sunder/virtual environments/android/Questionnaire.py", line 23, in validate
self.qvars[i].text = self.Data[i]
AttributeError: 'kivy.properties.ObjectProperty' object has no attribute 'text'
I referred to similar questions that had the same error but none matches specifically to my problem.

Using your qvars list messes things up because it's a list of the property objects, which doesn't have the right behaviour - Kivy Properties are descriptors, they only work at class level.
Instead, just access self.company or self.department in your methods.

Related

How do I add a row dynamically in KivyMD datatable?

Firstly, I KNOW there are similar questions like this out there but I really could not find any answer to this as almost all answers end with a dead end.
I am familiar with python and some with Kivy and now trying KivyMD specifically for the "Material Design" part.
Okay so it looks great and works great up to the point where I want to add or remove a row dynamically from a datatable. Well I haven't even tried removing yet because of the add problem.
Using the latest and greatest 3.10.4, Python Kivy 2.1.0, and kivymd 0.104.2, I followed the KivyMD 1.0.0.dev0 documentation from where the following code was sourced, but exits with the following error.
`AttributeError: 'MDDataTable' object has no attribute 'add_row'`
Here is the code:
from kivy.metrics import dp
from kivymd.app import MDApp
from kivymd.uix.datatables import MDDataTable
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.button import MDRaisedButton
class Example(MDApp):
data_tables = None
def build(self):
layout = MDFloatLayout() # root layout
# Creating control buttons.
button_box = MDBoxLayout(
pos_hint={"center_x": 0.5},
adaptive_size=True,
padding="24dp",
spacing="24dp",
)
for button_text in ["Add row", "Remove row"]:
button_box.add_widget(
MDRaisedButton(
text=button_text, on_release=self.on_button_press
)
)
# Create a table.
self.data_tables = MDDataTable(
pos_hint={"center_y": 0.5, "center_x": 0.5},
size_hint=(0.9, 0.6),
use_pagination=False,
column_data=[
("No.", dp(30)),
("Column 1", dp(40)),
("Column 2", dp(40)),
("Column 3", dp(40)),
],
row_data=[("1", "1", "2", "3")],
)
# Adding a table and buttons to the toot layout.
layout.add_widget(self.data_tables)
layout.add_widget(button_box)
return layout
def on_button_press(self, instance_button: MDRaisedButton) -> None:
'''Called when a control button is clicked.'''
try:
{
"Add row": self.add_row,
"Remove row": self.remove_row,
}[instance_button.text]()
except KeyError:
pass
def add_row(self) -> None:
last_num_row = int(self.data_tables.row_data[-1][0])
self.data_tables.add_row((str(last_num_row + 1), "1", "2", "3"))
def remove_row(self) -> None:
if len(self.data_tables.row_data) > 1:
self.data_tables.remove_row(self.data_tables.row_data[-1])
Example().run()
Some people suggested using self.data_tables.append witch also fails.
Please be assured that I have looked at a lot posts to this specific issue and I only found very half haerted or no answer.
Any solutions anybody?

Get the status of a checkbox from a Custom List in Kivy/KivyMD

I'm trying a custom List item based on the KivyMD's documentation examples, but I'm having a problem identifying which of my chechkboxes has been activated.
With * args I can access its state and which object it is but it prints like this:
{<__ main __. RightCheckbox object at 0x000001B1A62E6970>, False}.
The problem is that "0x000001B1A62E6970" is not a constant value (this value may change with the execution of the code) that represents the checkbox as its id does.
My minimal code KV:
KV = '''
MDCard:
orientation : 'vertical'
size_hint : (0.8,0.3)
pos_hint : {"center_x":.5,"center_y":.5}
elevation : 15
padding : 20
spacing : 30
id: box
MDList:
id: scroll
<ListItemWithCheckbox>:
IconLeftWidget:
icon: root.icon
RightCheckbox:
'''
And the MainApp and Custom Class definitions:
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivymd.app import MDApp
from kivymd.uix.list import IRightBodyTouch, OneLineAvatarIconListItem
from kivymd.uix.selectioncontrol import MDCheckbox
from kivymd.icon_definitions import md_icons
class ListItemWithCheckbox(OneLineAvatarIconListItem):
'''Custom list item.'''
icon = StringProperty("android")
def on_press(self):
print(self.text)
class RightCheckbox(IRightBodyTouch, MDCheckbox):
'''Custom right container.'''
def on_active(self, *args):
print(args)
class MainApp(MDApp):
def build(self):
return Builder.load_string(KV)
def on_start(self):
icons = list(md_icons.keys())
for i in range(5):
self.root.ids.scroll.add_widget(
ListItemWithCheckbox(text=f"Item {i}", icon=icons[i])
)
MainApp().run()
What I have tried:
I have tried to give a default id for the RightCheckbox to later change it to a unique one when putting the widgets in the list and thus access their states in something like "root.ids.checkboxid" but I don't know how to put it when doing self.root.ids.scroll.add_widget (ListItemWithCheckbox (text = f "Item {i}", icon = icons [i]))
Also in the on_active method (when any checkbox is selected) of the RightCheckbox class I have tried to print various attributes such as MDCheckBox.active .icon .ids. .text but none of them prints anything to help me identify which specific checkbox has been selected
I would really appreciate if anyone can help Thanks
One way to do this is to create a reference to the ListItemWithCheckbox within the RightCheckbox like this:
<ListItemWithCheckbox>:
IconLeftWidget:
icon: root.icon
RightCheckbox:
listItem: root
Then your on_active() method can use that reference:
class RightCheckbox(IRightBodyTouch, MDCheckbox):
'''Custom right container.'''
def on_active(self, rcb, value):
print(rcb.listItem.text, 'is', value)

kivyMD HotReloadViewer error: NoneType' object has no attribute 'fbind'

I am trying to run the kivyMD HotReloadViewer script and when I run this code I get an error message inside the HotReloadViewer widget that says: 'NoneType' object has no attribute 'fbind'. I have researched the error and it is explained that somewhere in the code a variable is not defined or a function is returning None. I am wondering if there is something I am missing here, or advice on debugging where undefined variable or function is located. I am running kivymd-0.104.2.dev0 and have watchdog installed.
from kivy.lang import Builder
from kivymd.app import MDApp
KV = '''
#:import KivyLexer kivy.extras.highlight.KivyLexer
#:import HotReloadViewer kivymd.utils.hot_reload_viewer.HotReloadViewer
BoxLayout:
CodeInput:
lexer: KivyLexer()
style_name: "native"
on_text: app.update_kv_file(self.text)
size_hint_x: .7
HotReloadViewer:
size_hint_x: .3
path: app.path_to_kv_file
errors: True
errors_text_color: 1, 1, 0, 1
errors_background_color: app.theme_cls.bg_dark
'''
class Example(MDApp):
path_to_kv_file = "kv_file.kv"
def build(self):
self.theme_cls.theme_style = "Dark"
return Builder.load_string(KV)
def update_kv_file(self, text):
with open(self.path_to_kv_file, "w") as kv_file:
kv_file.write(text)
Example().run()
class Example(MDApp):
path_to_kv_file = "kv_file.kv" # path to your KV file

Kivy .kv file passing argument

OK so what I want to do is have a FloorWidget with a option to add rooms dynamically with a press of a button while the app is running. I want to load specific graphic based on the room number and I don't know how to achive that in .kv file
<RoomWidget>:
id: room_widget
room_num: # would be great if you could pass this as an argument
Image:
source: os.path.join(GRAPHICS_DIR_PATH, "room" + str(room_widget.room_num))
Is there a way to do this? Could I somehow initilize room_num in python file by passing it into the RoomWidget constructor and access it in .kv file?
class RoomWidget(Screen, Widget):
def __init__(self, room_num, **kwargs):
super().__init__(**kwargs)
self.room_num = room_num
<RoomWidget>:
size_hint: (.2, .35)
source: os.path.join(GRAPHICS_DIR_PATH, "room" + str(self.room_num))
Image:
source: source
If I do sth like this I get an error: AttributeError: 'RoomWidget' object has no attribute 'room_num'.
You can do that by making a property of RoomWidget Like this:
class RoomWidget(Screen):
room_num = NumericProperty()
def __init__(self, room_num, **kwargs):
super().__init__(**kwargs)
self.room_num = room_num
Then, in your 'kv':
#:import os os
#:set GRAPHICS_DIR_PATH '.'
<RoomWidget>:
size_hint: (.2, .35)
source: os.path.join(GRAPHICS_DIR_PATH, "room" + str(self.room_num) + '.png')
Image:
source: root.source
Note that you do not need to extend both Screen and Widget, because Screen is a Widget.

Is it possible to schedule Kivy StringProperty set() method using Clock?

I would like to delay Kivy screen manager transition, which I can do for example like this:
#!/usr/bin/env python
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import StringProperty
from kivy.uix.button import Button
from kivy.clock import Clock
Builder.load_string('''
#: kivy 1.9.2
#: import sm kivy.uix.screenmanager
#: set base_font_size 25
<MainWidget>:
manager: screen_manager
ScreenManager:
id: screen_manager
transition: sm.NoTransition()
Screen:
name: 'screen_one'
Button:
text: 'Screen 1, press to switch to Screen 2'
font_size: base_font_size
on_press:
root.manager.current = 'screen_two'
root.statusmsg = 'Screen 2, press to launch Armageddon'
Screen:
name: 'screen_two'
Button:
text: root.statusmsg
font_size: base_font_size
disabled: True if root.statusmsg == 'Busy launching Armageddon' else False # prevent multiple activation
on_press:
root.deliver_payload()
''')
class MainWidget(FloatLayout):
statusmsg = StringProperty()
def __init__(self, **kwargs):
super(MainWidget, self).__init__(**kwargs)
def deliver_payload(self, dt=0):
if not dt: # called via on_press
self.statusmsg = 'Busy launching Armageddon'
# launch Armageddon here
Clock.schedule_once(self.deliver_payload, 2)
else: # scheduled via Clock
self.manager.current = 'screen_one'
class MyApp(App):
def build(self):
mw=MainWidget()
return mw
if __name__ == '__main__':
MyApp().run()
However, manager.current is a Kivy StringProperty and I should be able to assign a new value to it with the set() method and perhaps schedule this with Clock e.g. Clock.schedule_once(lambda dt: self.manager.property('current').set(??, 'screen_one'), 2).
I wasn't able to find documentation for Kivy StringProperty set() method. It seems the method takes two args and the first argument needs to be of type kivy._event.EventDispatcher - according to traceback info that I get when I try some random guesses. Can anyone point to some documentation, or examples of use for the set() method?
EDIT: I realise I did not make it clear what I want to do ultimately - I am asking if it is possible to schedule the Kivy property set() method using Kivy Clock. For example, I am able to toggle a Kivy BooleanProperty like this:
if self.property('myboolean').get(self):
self.property('myboolean').set(self, False)
else:
self.property('myboolean').set(self, True)
But I was not able to schedule the method using Clock. The following line generated no errors but did not have any effect either:
Clock.schedule_once(lambda dt: self.property('myboolean').set(self, False),0)
In the case of Kivy StringProperty set() method, I was not even able to figure out what the 1st argument needed to be, so did not get as far as trying to schedule it with Clock.
FURTHER EDIT:
Although screen manager 'current' is a StringProperty according to documentation, it appears to behave somewhat differently. With a StringProperty I am able to do the following:
self.property('statusmsg').set(self, 'Armageddon now')
However, attempting something like this with manager.current does not succeed:
self.manager.property('current').set(self.manager, 'aux_screen')
The error message is:
self.manager.property('current').set(self.manager, 'screen_one')
TypeError: Argument 'obj' has incorrect type (expected kivy._event.EventDispatcher, got kivy.weakproxy.WeakProxy)
Confusing.
I stumbled across this by chance Kivy: Changing screens in screen manager with an on_press event (thank you, Tshirtman) and I now have one answer. The following change gives me what I want - the ability to schedule screen change directly (i.e. without an intermediate method to call):
def deliver_payload(self):
self.statusmsg = 'Busy launching Armageddon'
Clock.schedule_once(lambda dt: self.manager.setter('current')(self.manager,'screen_one'), 2)
So it appears that for assigning a new value to screen manager's 'current' property, the correct method to use is the 'setter' method (not the 'set' method that I was attempting to use).
You can set a new value for a StringProperty by simply doing a self.manager.current = 'some string', as you do in your code.

Resources