Get the status of a checkbox from a Custom List in Kivy/KivyMD - python-3.x

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)

Related

Text of label in UI file can't be changed

I tried it.
from PySide2 import QtWidgets
from PySide2 import QtGui
from PySide2 import QtCore
from PySide2.QtUiTools import QUiLoader
from maya.app.general.mayaMixin import MayaQWidgetBaseMixin
import shiboken2 as shiboken
import os
UIFILEPATH = 'D:/MAYA/pyside_pick/ui/PicsTest5.ui'
class MainWindow(MayaQWidgetBaseMixin,QtWidgets.QMainWindow):
def __init__(self,parent=None):
super(MainWindow,self).__init__(parent)
self.UI = QUiLoader().load(UIFILEPATH)
self.setWindowTitle(self.UI.windowTitle())
self.setCentralWidget(self.UI)
#image
img = QtGui.QPixmap('D:/MAYA/pyside_pick/images/imgKohaku.png')
self.scene = QtWidgets.QGraphicsScene(self)
item = QtWidgets.QGraphicsPixmapItem(img)
self.scene.addItem(item)
self.UI.graphicsView_char_1.setScene(self.scene)
#filter
self._filter = Filter()
self.installEventFilter(self._filter)
self.UI.pSphere1.installEventFilter(self._filter)
#primary
self.UI.label.setStyleSheet("QLabel {color : white;}")
self.UI.label.setText("A")
def labelTest(self):
self.UI.label.setStyleSheet("QLabel {color : red;}")
self.UI.label.setText("B")
print('D')
return False
class Filter(QtCore.QObject):
def eventFilter(self, widget, event):
win = MainWindow()
if event.type() == QtCore.QEvent.MouseButtonPress:
print(widget.objectName())
cmds.select(widget.objectName())
win.labelTest()
return False
def main():
win = MainWindow()
win.show()
if __name__ == '__main__':
main()
I clicked the button that 'pSphere1', but
self.UI.label.setStyleSheet("QLabel {color : red;}") self.UI.label.setText("B")
were look like it's not working.
I can change it inside define with UI loaded, but can't I do setText from outside?
How can I change the label of an imported UI file?
I find this, but I really do not understand. I couldn't find any mention of them beyond this page.
Change comboBox values in Qt .ui file with PySide2
If you know, I also want you to tell me where to put them.
Your issue is within the eventFilter(), and specifically the first line:
win = MainWindow()
This will create a new main window instance, which clearly doesn't make sense, since you obviously want to interact with the existing one.
While you could add the instance as an argument in the filter constructor in order to get a reference to the instance and directly call the function, that wouldn't be very good from the OOP point of view, as objects should never directly access attributes of their "parents".
A better and more correct approach would be to use a custom signal instead, and connect it from the main window instance:
class Filter(QtCore.QObject):
testSignal = QtCore.Signal()
def eventFilter(self, widget, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
print(widget.objectName())
cmds.select(widget.objectName())
self.testSignal.emit()
return False
class MainWindow(MayaQWidgetBaseMixin, QtWidgets.QMainWindow):
def __init__(self, parent=None):
# ...
self._filter.testSignal.connect(self.labelTest)
Note that widgets could accept events and prevent propagation (for instance, buttons or graphics views that have selectable or movable items), so you might not receive the event in the filter in those cases.

How to dynamically update label texts in kivy that is imported from excel file?

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.

Kivy progressbar value won't update

I'm trying to program a progress bar which counts down from 60 to 0 seconds as soon as the screen it is on opens, however I can't work out why the value of the bar isn't updating.
I believed self.ids.pb.value was a valid way to do this, which leads me to think I'm making a mistake elsewhere.
Thanks in advance.
Error
KeyError: 'pb'
AttributeError: 'super' object has no attribute '__getattr__'
.py file
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen, CardTransition
from kivy.uix.progressbar import ProgressBar
class CountDown(ProgressBar):
def count(self):
self.ids.pb.value = 60
seconds = 60
def count_it(seconds):
if seconds == 0:
return
seconds -= 1
self.ids.pb.value = seconds
Clock.schedule_once( lambda dt: count_it(seconds), 1)
Clock.schedule_once( lambda dt: count_it(60), 1)
class EasyMode(Screen):
pass
class TutorialEasy(Screen):
pass
class GIFapp(App):
countdown = CountDown()
def build(self):
return Builder.load_file("testkivy.kv")
Kv file
<TutorialEasy>:
Button:
on_release: app.countdown.count()
on_release: app.root.current = "EasyMode"
<EasyMode>:
CountDown:
id: pb
max:60
Update
Value wasn't updating once errors were fixed because I missed declaring the value in Kv lang ie.
<EasyMode>:
CountDown:
id: pb
max:60
value: app.countdown.value
The value is already in the CountDown class.
So you can set the value with self.value = seconds
If you are in the EasyMode class, it would work with self.ids.pb.value = seconds

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.

How to read a Kivy.properties.NumericProperty value in my code at a specific point

So basically I have a kivy project in which,I have a layout that I use in many classes,as such..I have made in custom and put it in a separate file so I can just reference it from different parts of my code.
Snippet:
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
Builder.load_string('''
<CustLayout>:
#code here
''')
class CustLayout(BoxLayout):
t_length = NumericProperty(0)
my_len = 0
print(my_len)
def __init__(self, **kwargs):
super(Silvertable,self).__init__(**kwargs)
self.bind(t_length=self.on_t_length)
#This statement is executed after all other prints
print(self.t_length)
def on_t_length(self,instance,length):
#I'd like to get kv file value before the next line
self.my_len = length
print(my_len)
My kiv file:
#:import Silvertable silvertables.Silvertable
#chunk of code
BoxLayout:
Silvertable:
t_length: 5
Here,I DO get the value but unfortunately too late.That is,I get the value after the program has finished.my_len Doesn't change it's still 0
Bind Kivy Properties
The value of my_len is 0 (zero) because the print() functions were executed first before the run() method.
The value of my_len did change from 0 to 5 as per binding on property of t_length.
Please refer to the example and trace for details.
Example
main.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty
from kivy.lang import Builder
Builder.load_string('''
<CustLayout>:
t_length: 5
''')
class CustLayout(BoxLayout):
t_length = NumericProperty(0)
my_len = 0
print("\tCustLayout.class level-start: my_len=", my_len)
def __init__(self, **kwargs):
super(CustLayout,self).__init__(**kwargs)
print("\nCustLayout.__init__:")
self.bind(t_length=self.on_t_length)
#This statement is executed after all other prints
print("\tself.t_length=", self.t_length)
print("\tself.my_len=", self.my_len)
def on_t_length(self, instance, length):
print("\nCustLayout.on_t_length:")
#I'd like to get kv file value before the next line
self.my_len = length
print("\tself.my_len=", self.my_len)
print("\tCustLayout.class level-end: my_len=", my_len)
class TestBindProperty(App):
def build(self):
print("\nTestBindProperty.build:")
return CustLayout()
def on_stop(self):
print("\tTestBindProperty.on_stop: my_len=", self.root.my_len)
if __name__ == "__main__":
TestBindProperty().run()
Output

Resources