In trying to create a widget to mimic the functions of a multiple choice quiz, I have run into an error that I can't find any solution for. I read this post and didn't find anything that applied to my specific situation.
I am trying to load a group of settings from a .JSON file and use them as the main properties for my KV objects. For example, loading the "canvas_color" setting from the file and assigning that value to the canvas color of my widget.
When I try and do this I get the error:
AttributeError: 'MultipleChoiceT' object has no attribute 'settings'
My .py file containing the "missing" attribute:
import kivy
import json
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.app import App
kivy.require("1.11.1")
Builder.load_file("MultipleChoice_text.kv")
class MultipleChoiceT(BoxLayout):
# collects info from json file and assigns it to settings var
with open('widget_settings.json', 'r') as file:
settings = json.load(file)["MultipleChoice_settings"][0]
print(settings)
test = 'hello'
def __init__(self, **kwargs):
super(MultipleChoiceT, self).__init__(**kwargs)
self.app = App.get_running_app()
print(self.app)
self.w_width = 0.5
self.w_height = 1
self.data = {'pressed_btn': 0, 'btn_text': None}
# the function called for when the toggle button is on_state
def pressed(self, instance):
self.data['pressed_btn'] = self.get_id(instance)
self.data['btn_text'] = instance.text
print(self.data)
# changes the bottom label when submit button is pressed
def submit(self):
self.bottom_label.text = f"ID: {self.data['pressed_btn']} TEXT: {self.data['btn_text']}"
# returns the id of the given instance
@staticmethod
def get_id(instance):
for id, widget in instance.parent.ids.items():
if widget.__self__ == instance:
return id
# null method for now, not implemented
def change_question(self, new_text):
self.question_label.text = str(new_text)
# prints text when the next question button is pressed
def next_question(self):
print('next question pressed')
# returns the dimensions for the toggle buttons based on their text length and font size defined in settings json
def get_dimension(self, instance):
if len(instance.text) < 25:
width = ((len(instance.text) / 2) * instance.font_size) * 1.1
else:
width = (len(instance.text) / 2) * instance.font_size
height = instance.font_size * 1.35
return width * self.settings["btn_scalar"], height * self.settings["btn_scalar"]
class Test(BoxLayout):
pass
class MainApp(App):
def build(self):
return Test()
if __name__ == "__main__":
MainApp().run()
And my .kv file (yes, I am aware the indentation is off, that is only in this post):
<MultipleChoiceT@BoxLayout>:
orientation: 'vertical'
padding: 10
id: main_layout
bottom_label: bottom_label
canvas:
Color:
rgba: root.settings["canvas_color"]
Rectangle:
pos: self.x + 5, self.y + 5
size: self.width - 10, self.height - 10
Label:
id: question_label
font_size: root.settings["font_size"]
text: 'Sample question'
text_size: self.size[0] + 5, self.size[1]
valign: 'middle'
height: self.font_size * 1.33
size_hint: 1, None
color: root.settings["label_text_color"]
Label:
size_hint_y: None
height: 10
ToggleButton:
id: btn1
background_color: root.settings["button_bg_color"]
color: root.settings["button_text_color"]
font_size: root.settings["font_size"]
text: 'Option1'
group: 'MultChoice'
on_state: root.pressed(self)
size: root.get_dimension(self)
size_hint: None, None
halign: 'center'
valign: 'center'
Label:
size_hint_y: 0.1
ToggleButton:
id: btn2
background_color: root.settings["button_bg_color"]
color: root.settings["button_text_color"]
font_size: root.settings["font_size"]
text: '4785839409fjvojrvn eijofgj4389'
group: 'MultChoice'
on_state: root.pressed(self)
size: root.get_dimension(self)
size_hint: None, None
halign: 'center'
valign: 'center'
Label:
size_hint_y: 0.1
ToggleButton:
id: btn3
background_color: root.settings["button_bg_color"]
color: root.settings["button_text_color"]
font_size: root.settings["font_size"]
text: 'Option3'
group: 'MultChoice'
on_state: root.pressed(self)
size: root.get_dimension(self)
size_hint: None, None
halign: 'center'
valign: 'center'
Label:
size_hint_y: 0.1
ToggleButton:
id: btn4
background_color: root.settings["button_bg_color"]
font_size: root.settings["font_size"]
color: root.settings["button_text_color"]
text: 'A sample question type'
group: 'MultChoice'
on_state: root.pressed(self)
size: root.get_dimension(self)
size_hint: None, None
halign: 'center'
valign: 'center'
Label:
size_hint: 1, 0.5
GridLayout:
id: bottom_layout
cols: 4
size_hint_y: None
canvas:
Color:
rgba: root.settings["second_canvas_color"]
Rectangle:
pos: self.x, self.y
size: self.width, self.height
Button:
id: submit_btn
text: 'Press to submit'
on_press: root.submit()
font_size: root.settings["font_size"]
background_color: root.settings["second_btn_bg_color"]
color: root.settings["second_btn_txt_color"]
# for text wrapping
size: root.get_dimension(self)
text_size: self.size
halign: 'center'
valign: 'center'
Label:
id: bottom_label
text: 'answer'
font_size: root.settings["font_size"]
color: root.settings["second_lbl_txt_color"]
size_hint_x: main_layout.size_hint_x * 2
# for text wrapping
size: root.get_dimension(self)
text_size: self.size
halign: 'center'
valign: 'center'
Button:
id: next_btn
text: 'Next question'
font_size: root.settings["font_size"]
background_color: root.settings["second_btn_bg_color"]
color: root.settings["second_btn_txt_color"]
on_press: root.next_question()
# for text wrapping
size: self.texture_size
text_size: self.size
halign: 'center'
valign: 'center'
<Test@BoxLayout>:
orientation: 'horizontal'
MultipleChoiceT:
MultipleChoiceT:
Whenever the MultipleChoice_text.py file is run, I get an error saying on line 11 of MultipleChoice_text.kv that the MultipleChoiceT object has no attribute 'settings' Maybe it's just my understanding of how/what attributes are. But when I ran the file on its own, without trying to have multiple the MultipleChoiceT widgets in one layout, it ran perfectly.
My current idea is that the settings attribute is made on the instance level and is only created when the instance is created. And the instance is not created until after root.settings[whatever] is called. I'm not sure how I can fix this issue.
I have tried to move the creation of the settings attribute around and into different classes to see if it can be initialized before it is called, but I had no luck with that. I also tried importing the settings attribute into the kv file but that didn't do anything either. My closest attempt has been running the MultipleChoiceT widget on its own and having it be the only thing in the app. That works when the code is as follows:
class MainApp(App):
def build(self):
return MultipleChoiceT()
MainApp().run()
Any help or pointers would be appreciated as this is for a school project due in 2 weeks :) I can post the full error message if needed but the only thing I found useful in it was the line stating the missing attribute.
As a side note, Is PyQt5 a better option for GUI development in python? I looked into it and chose not to go that route because I did not find a way for it to support scalable applications. And, the forum posts were sparse