Accessing Kivy Screenmanagaer inside a screen's __init__ method - python

I am trying to build an application that loads data from a JSON file and adds it to an MDList object. I would like the loaded items to take the user to a specific page when clicked. My implementation of the __init__ finction is shown bellow:
def __init__(self, **kw):
super().__init__(**kw)
json_data_object = JsonData("data.json")
# adds list item from JSON file to MDlist object
for i in json_data_object.data["lists"]:
loaded_object = ListItemWithoutCheckbox(text="[b]" + i["list_name"] + "[/b]")
self.ids["Container"].add_widget(loaded_object)
self.manager.add_widget(ToDoListPage(name=str(loaded_object.text)))
loaded_object.bind(on_release= lambda x : self.change_screen(loaded_object.text))
The first half of the for loop works as intended, adding the loaded objects to the MDList object. However the second half returns an AttributeError:
AttributeError: 'NoneType' object has no attribute 'add_widget'
I have a theory that this is due to the __init__ function running before the screen is added to the ScreenManager() object in the MainApp() class, shown below, but have no concrete proof of this nor any ideas for how to get around the issues.
class MainApp(MDApp):
def build(self):
# Setting theme to my favorite theme
self.theme_cls.theme_style = "Dark"
Builder.load_file("styling.kv")
sm = ScreenManager()
sm.add_widget(ToDoListView(name="ListView"))
return sm
I will keep trying to work on the issue but I am struggling to come up with new ideas, so any help is greatly appreciated.
If I manage to crack this I will post my solution to this issue!
Thanks for any help :)
EDIT:
I have added a method to attempt to add the on_release functionality after the __init__ method has run. On printing self.parent I still receive a None value. I have also attempted to use the Clock module of kivy as suggested in the comments by John Anderson but I am still receiving the same AttributeError shown earlier in this question. My edited code now looks like this:
Class initialisation:
def __init__(self, **kw):
super().__init__(**kw)
json_data_object = JsonData("data.json")
self.loaded_items = []
# adds list item from JSON file to MDlist object
for i in json_data_object.data["lists"]:
loaded_object = ListItemWithoutCheckbox(text="[b]" + i["list_name"] + "[/b]")
self.ids["Container"].add_widget(loaded_object)
self.loaded_items.append(loaded_object)
def load_tasks(self, loaded_objects):
for i in loaded_objects:
# To test if i can access the screen manager
print(self.parent)
self.manager.add_widget(ToDoListPage(name=str(i.text)))
object.bind(on_release= lambda x : self.change_screen(i.text))
Main app class:
class MainApp(MDApp):
def build(self):
# Setting theme to my favorite theme
self.theme_cls.theme_style = "Dark"
Builder.load_file("styling.kv")
list_view_screen = ToDoListView(name = "ListView")
list_view_screen.load_tasks(list_view_screen.loaded_items)
sm = ScreenManager()
sm.add_widget(list_view_screen)
return sm
Thank you so much for even checking out this question!
any help is greatly appreciated :)

happy to say i finally found a solution to the issue. By abstracting the functionality of the __init__ method into another method and making a partial call in kivy's Clock object, the function could utilise the screen manager object.
def __init__(self, sm,**kw):
super().__init__(**kw)
Clock.schedule_once(partial(self.load_tasks, sm))
def load_tasks(self, sm, *largs):
json_data_object = JsonData("data.json")
self.loaded_items = []
for i in json_data_object.data["lists"]:
self.add_loaded_item_to_list(i)
for i in self.loaded_items:
self.bind_on_release_to_loaded_item(sm, i)
def bind_on_release_to_loaded_item(self, sm, loaded_item):
self.manager.add_widget(ToDoListPage(name = loaded_item.text))
loaded_item.bind(on_release= lambda x: self.change_screen(loaded_item.text))
def add_loaded_item_to_list(self, loaded_item):
loaded_object = ListItemWithoutCheckbox(text="[b]" + loaded_item["list_name"] + "[/b]")
self.ids["Container"].add_widget(loaded_object)
self.loaded_items.append(loaded_object)
The MainApp class reverted to its original:
class MainApp(MDApp):
def build(self):
# Setting theme to my favorite theme
self.theme_cls.theme_style = "Dark"
Builder.load_file("styling.kv")
sm = ScreenManager()
sm.add_widget(ToDoListView(sm,name = "ListView"))
return sm
Thanks again to John Anderson for helping me get to this solution :)

Related

Create a graph at the start of an application in kivy (NoneType object has no Atribute "Get_Screen")

I have created a small kivy app for summarizing my Grades and everything works just fine until the point where i want to display a graph at the start of the application so i am able to display the graph correctly on a press of a button(click Mathe and then back it calls the function) however trying to call the same function at the start results in: "'NoneType' object has no attribute 'get_screen'" although there are some answers for this problem sadly none of them worked for me :( maybe i have implemented them wrongly i would be grateful if someone could help.
here the important parts of the code:
class MainWindow(Screen,object):
def Allgemeinepunkte(self, *args):
print("nice")
self.manager.get_screen("main").ids.Graph.clear_widgets()
global Alist
with open ('Allgemein.txt', 'r') as m:
Alist = m.read().splitlines()
Alist = [int(item) for item in Alist]
AAnzahlNoten = []
x = 0
for i in Alist:
AAnzahlNoten.append(x+1)
x = x+1
plt.clf()
plt.gcf()
plt.plot(AAnzahlNoten,Alist)
plt.tight_layout()
self.manager.get_screen('main').ids.Graph.add_widget(FigureCanvasKivyAgg(plt.gcf()))
and the App itself:
class MyMainApp(App):
def build(self):
Window.clearcolor = (0.08,0.12,0.17,1)
kv = Builder.load_file("my.kv")
return kv
def on_start(self, **kwargs):
mw = MainWindow()
mw.Allgemeinepunkte()
if __name__ == "__main__":
app = MyMainApp()
app.run()
And the error:
File "c:\Users\denis\Documents\VSCode\main.py", line 42, in Allgemeinepunkte
self.manager.get_screen("main").ids.Graph.clear_widgets()
AttributeError: 'NoneType' object has no attribute 'get_screen'
In your App:
def on_start(self, **kwargs):
mw = MainWindow()
mw.Allgemeinepunkte()
This method creates a new instance of MainWindow, and calls its Allgemeinepunkte() method. But since the new instance of MainWindow is not added to a ScreenManager, its manager property is None. You need to access the actual instance of MainWindow that is in your GUI. Probably, need to access the MainWindow instance as:
def on_start(self, **kwargs):
mw = self.root.get_screen('main')
mw.Allgemeinepunkte()

Text-Based adventure in Kivy, Python

I'm very new to programming, just have an introductory seminar in university, and I'm supposed to program a little app in Python. For that, I want to use Kivy, but I got stuck.
I have a text file which should include the question, the possible answers and where it's supposed to go considering which answer the user chose:
0|"Will you rather do A or B?"|"A"|"B"|1|2
1|"Congrats, you chose A. Now go to B."|"Go to B"|"Go to B"|2|2
2|"That's B. Incredible. Want to discover C?"|"Yes."|"Stay here."|3|6
3|Wow, C is so cool, isn't it? There's also a D.|D? Crazy!|Boring. Go back.|4|0
4|Welcome to the depths of D. You are curious, aren't you?|Yep.|Nope.|5|0
5|Cool. There's nothing else here.|There must be.|Knew it.|4|0
6|Surprise! You should really discover C.|Alright.|Is there a D?|3|4
Now I want the game to go to the according line, replace the displayed text and go on. In theory, this is kind of working with my Code (I'm sorry if it's messed up, as I said, I'm new to this topic):
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
with open('try.txt') as t:
Lines = t.readlines()
first_line = Lines[0].strip()
x = first_line.split("|")
answer_b = int(x[5])
class MyGrid(GridLayout):
def __init__(self, **kwargs):
super(MyGrid, self).__init__(**kwargs)
self.cols = 1
self.inside = GridLayout()
self.inside.cols = 2
self.btna = Button(text=x[2])
self.btna.bind(on_press=self.a)
self.inside.add_widget(self.btna)
self.btnb = Button(text=x[3])
self.btnb.bind(on_press=self.b)
self.inside.add_widget(self.btnb)
self.main_text = Label(text=x[1])
self.add_widget(self.main_text)
self.add_widget(self.inside)
def a(self, instance):
answer_a = int(x[4])
next_line_a = Lines[answer_a].strip()
print(next_line_a)
print(answer_a)
x = next_line_a.split("|")
self.main_text.text = x[1]
self.btna.text = x[2]
self.btnb.text = x[3]
self.btna.bind(on_press=self.a)
def b(self, instance):
next_line_b = Lines[answer_b].strip()
print(next_line_b)
print(answer_b)
x = next_line_b.split("|")
self.main_text.text = x[1]
self.btna.text = x[2]
self.btnb.text = x[3]
class Game(App):
def build(self):
return MyGrid()
if __name__ == '__main__':
Game().run()
The problem is that it stays with the first line I defined and I don't really know how to go around that problem. I would imagine that I first define x with the first line, and after that x gets redefined with the according new line. But the next_line and x variable are both dependent on each other - I tried two different ways with answer a and b, but both don't really work. B will just continuously take the first_line-x, A tells me that x is referenced before assignment.
It would be great if someone could help me out of my confusion, because everything I tried just didn't work out...
Thanks!
I changed it so you pass items into the object that you create. It's challenging to get the inheritance correct.
I also added an initializer to the Games object. I think this works but to be honest I am not expert in the workings of Kivy and have gotten this pattern to work but I don't know for sure if it is best practice.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
with open('try.txt') as t:
Lines = t.readlines()
class MyGrid(GridLayout):
def __init__(self, Lines: list):
super(MyGrid, self).__init__()
self.first_line = Lines[0].strip()
self.xx = self.first_line.split("|")
self.answer_b = int(self.xx[5])
self.cols = 1
self.inside = GridLayout()
self.inside.cols = 2
self.btna = Button(text=self.xx[2])
self.btna.bind(on_press=self.a)
self.inside.add_widget(self.btna)
self.btnb = Button(text=self.xx[3])
self.btnb.bind(on_press=self.b)
self.inside.add_widget(self.btnb)
self.main_text = Label(text=self.xx[1])
self.add_widget(self.main_text)
self.add_widget(self.inside)
def a(self, instance):
answer_a = int(self.xx[4])
next_line_a = Lines[answer_a].strip()
print(next_line_a)
print(answer_a)
self.xx = next_line_a.split("|")
self.main_text.text = self.xx[1]
self.btna.text = self.xx[2]
self.btnb.text = self.xx[3]
self.btna.bind(on_press=self.a)
def b(self, instance):
next_line_b = Lines[self.answer_b].strip()
print(next_line_b)
print(self.answer_b)
self.xx = next_line_b.split("|")
self.main_text.text = self.xx[1]
self.btna.text = self.xx[2]
self.btnb.text = self.xx[3]
class Game(App):
def __init__(self, **kwargs):
self._arguments_to_pass_through = kwargs
super().__init__()
def build(self):
return MyGrid(**self._arguments_to_pass_through)
if __name__ == '__main__':
Game(Lines=Lines).run()

How to access a Class value outside of it

Sorry for such a noobish question, but, now i am writing a code that has some features i didn't know how to write by myself, so i copied them from stack overflow. I was a class i didnt study, yet i understood it mostly. The question is, how do i acess any of values created in it. Ex
class SimpleApp(object):
def __init__(self, master, filename, **kwargs):
self.master = master
self.filename = filename
self.canvrt = tk.Canvas(master, width=200, height=200, bg="#FF5733")
self.canvrt.pack()
self.update = self.draw().__next__
master.after(100, self.update)
def draw(self):
image = Image.open(self.filename)
angle = 0
while True :
tkimage = ImageTk.PhotoImage(image.rotate(angle))
canvas_obj = self.canvrt.create_image(
100, 100, image=tkimage)
self.master.after_idle(self.update)
yield
self.canvrt.delete(canvas_obj)
angle += 1
angle %= 360
how can i access the canvrt from the code? I need to acces this canvrt outisde of the class, so i can input it for example in a fucntion
You create an instance of the class SinpleApp:
myapp = SimpleApp(master, filename)
And then you can access any of its variables like this:
myapp.canvrt
However, notice that it is confusing to call filename the argument of your function clean if it expects a tkinter widget...
Depends on where you want to access it.
class NewClass():
def __init__(self,msg):
self.msg ="Hi"
def print_msg(self):
print("Message is : ", self.msg)
Inside any of the class functions/methods, you should make use of self, i.e,
self.msg
Outside of the class, use an object to access it, i.e,
obj = NewClass("hi")
obj.msg
//will print Hi

NameError: name "winroot" is not defined

I am building a calculator app, and I solved a lot of problems except this one. It is bigger than me to solve it, I tried nearly everything except 1 way that I am thinking of it right now is: making the program in one (.py) file.
My program is designed in Kivy and I made the app in two python files, so here is the problem: the main screen has several choices to open a new page, and that page must have a button that make the app gets back to the main screen, and this button did not work.
A variable has just disappeared magically .. called 'winroot'!
Here is the code (main.py):
class Base(App):
def build(self):
global winroot
winroot = TheMainScreen()
mnsc = MainScreen()
winroot.add_widget(mnsc)
return winroot
class TheMainScreen(FloatLayout):
def back(self, obj=1):
print('pressed')
winroot.clear_widgets()
winroot.add_widget(MainScreen())
class MainScreen(FloatLayout):
def __init__(self, **kwargs):
self.B1 = Button(text='Base Calculator\n\n\n', on_press=self.basecalc)
def basecalc(self, obj):
winroot.clear_widgets()
from calculator.basecalculator import BaseCalculator
winroot.add_widget(BaseCalculator())
and this is for the second python file (basecalculator.py):
class BaseCalculator(FloatLayout):
def __init__(self, **kwargs):
super(BaseCalculator, self).__init__(**kwargs)
self.B11 = Button(size_hint=(.08, .13), on_release=self.prev)
def prev(self, obj=1):
from calculator.main import TheMainScreen
a = TheMainScreen()
a.back()
and here is the Error that is showing up :
File "C:\Users\work\PycharmProjects\Kivy\calculator\basecalculator.py", line 95, in prev
a.back()
File "C:\Users\work\PycharmProjects\Kivy\calculator\main.py", line 26, in back winroot.clear_widgets()
NameError: name 'winroot' is not defined
since winroot is a global variable created inside a function, you have to declare it as global in every function you use it.
Or, you know, instead of global, set is as instance attribute and pass it around, then you don't need globals:
class Base(App):
def build(self):
self.winroot = TheMainScreen()
self.mnsc = MainScreen(self.winroot)
self.winroot.add_widget(self.mnsc)
return winroot
class TheMainScreen(FloatLayout):
def back(self, obj=1):
print('pressed')
self.clear_widgets()
self.add_widget(MainScreen())
class MainScreen(FloatLayout):
def __init__(self, winroot, **kwargs):
self.B1 = Button(text='Base Calculator\n\n\n', on_press=self.basecalc)
self.winroot = winroot
def basecalc(self, obj):
self.winroot.clear_widgets()
from calculator.basecalculator import BaseCalculator
self.winroot.add_widget(BaseCalculator())

Refer Names of a Button created from for loops Iteration variables and use it in python kivy

I am facing a very basic issue here . I want to create 100s of Buttons and to make them I want to use loops preferably for loop and create Button Names in the loop itself so i can use it later in the program logic i.e. example for i=1 i want to create Button1 and for i=100 i want button Button100 , however i am not sure how to do that in Python Kivy .
This is same thing which we can do in Linux using &variable name and sometime eval .Please see the code below for better . Comments will describe the issue here :
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.graphics import Color
from kivy.clock import Clock
class RootWidget(GridLayout):
pass
class MainApp(App):
def build(self):
self.parent = GridLayout(cols=6)
for i in (1,2,3,4,5,6):
for j in (1,2,3,4,5,6):
#Want to make self.BName s value as ButtonName%s%s
self.BName= 'ButtonName%s%s'%(i,j)
print "Button Name should be ",self.BName
self.BName = Button(text='%s%s'%(i,j))
self.parent.add_widget(self.BName)
Clock.schedule_once(lambda a:self.update(),1)
return self.parent
def update(self):
print "I am update function"
for child in self.parent.children:
print child
#Want to make use ButtonName11 and not BName
self.BName.text = "hello"
#self.ButtonName11.text= "hello"
if __name__ == '__main__':
MainApp().run()
Have you tried using this concept?
class Foo:
def __init__(self):
for i in range(100):
self.__dict__['Button{}'.format(i)] = Button(text='Button no. {}'.format(i))
>>> foo = Foo()
>>> foo.Button5.text
Button no. 5
>>> foo.Button98.text
Button no. 98
Scorpion_God way of doing it should work, however, a clearer way is using setattr.
Here's some example code:
class Foo(object):
def create_buttons(self):
for i in range(10):
setattr(self, 'my_name{}'.format(i), i)
foo = Foo()
foo.my_name3
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'my_name3'
foo.create_buttons()
foo.my_name3
3

Categories