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())
Related
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 :)
I'm trying to port this IronPython WPF example to CPython with python.NET.
I'm able to get the window running after fixing some known issues (adding __namespace__ to the ViewModel class, using Single Thread Apartment and updating the syntax of pyevent.py to python3), but bindings are ignored: I can write in the textbox, but no events gets triggered, and the button doesn't fire the OnClick method.
Here's the full code:
import clr
clr.AddReference(r'wpf\PresentationFramework')
clr.AddReference('PresentationCore')
from System.Windows.Markup import XamlReader
from System.Windows import Application, Window
from System.ComponentModel import INotifyPropertyChanged, PropertyChangedEventArgs
import pyevent
from System.Threading import Thread, ThreadStart, ApartmentState
XAML_str = """<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Sync Paramanager" Height="180" Width="400">
<StackPanel x:Name="DataPanel" Orientation="Horizontal">
<Label Content="Size"/>
<Label Content="{Binding size}"/>
<TextBox x:Name="tbSize" Text="{Binding size, UpdateSourceTrigger=PropertyChanged}" />
<Button x:Name="Button" Content="Set Initial Value"></Button>
</StackPanel>
</Window>"""
class notify_property(property):
def __init__(self, getter):
def newgetter(slf):
#return None when the property does not exist yet
try:
return getter(slf)
except AttributeError:
return None
super().__init__(newgetter)
def setter(self, setter):
def newsetter(slf, newvalue):
# do not change value if the new value is the same
# trigger PropertyChanged event when value changes
oldvalue = self.fget(slf)
if oldvalue != newvalue:
setter(slf, newvalue)
slf.OnPropertyChanged(setter.__name__)
return property(
fget=self.fget,
fset=newsetter,
fdel=self.fdel,
doc=self.__doc__)
class NotifyPropertyChangedBase(INotifyPropertyChanged):
__namespace__ = "NotifyPropertyChangedBase"
PropertyChanged = None
def __init__(self):
self.PropertyChanged, self._propertyChangedCaller = pyevent.make_event()
def add_PropertyChanged(self, value):
self.PropertyChanged += value
def remove_PropertyChanged(self, value):
self.PropertyChanged -= value
def OnPropertyChanged(self, propertyName):
if self.PropertyChanged is not None:
self._propertyChangedCaller(self, PropertyChangedEventArgs(propertyName))
class ViewModel(NotifyPropertyChangedBase):
__namespace__ = "WpfViewModel"
def __init__(self):
super(ViewModel, self).__init__()
# must be string to two-way binding work correctly
self.size = '10'
#notify_property
def size(self):
return self._size
#size.setter
def size(self, value):
self._size = value
print(f'Size changed to {self.size}')
class TestWPF(object):
def __init__(self):
self._vm = ViewModel()
self.root = XamlReader.Parse(XAML_str)
self.DataPanel.DataContext = self._vm
self.Button.Click += self.OnClick
def OnClick(self, sender, event):
# must be string to two-way binding work correctly
self._vm.size = '10'
def __getattr__(self, name):
# provides easy access to XAML elements (e.g. self.Button)
return self.root.FindName(name)
def main():
tw = TestWPF()
app = Application()
app.Run(tw.root)
if __name__ == '__main__':
thread = Thread(ThreadStart(main))
thread.SetApartmentState(ApartmentState.STA)
thread.Start()
thread.Join()
It seems that assigning the ModelView to DataPanel's DataContext doesn't trigger any binding registration, but I have no clue on how to fix that.
Am I missing something obvious?
Unfortunately pythonnet does not support defining binding in the xaml file.
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
I'm pretty sure this has been answered, but I can't seem to locate it.
What I want is a python script for Blender that creates a custom tab that contains a button. When that button is pressed, it prints the value of an integer and increments it, so that when you press the button again, it shows an incremented value. Everything seems to work, except for the incremental part.
Here is the code I am using at the moment:
===
import bpy
from bpy.props import (IntProperty,)
from bpy.types import (Panel, Operator, AddonPreferences, PropertyGroup,)
def main(context):
my_number += 1
print(str(my_number))
class MySettings(PropertyGroup):
my_number = IntProperty(
name="Int property",
description="This is an integer.",
default = 1
)
class AddOne(bpy.types.Operator):
"""This is an operator"""
bl_idname = "op.add_one"
bl_label = "Increment by 1"
def execute(self, context):
main(context)
return {'FINISHED'}
class CreatePanel(bpy.types.Panel):
bl_label = "Render Setup Panel"
bl_idname = "OBJECT_PT_hello"
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'TOOLS'
bl_category = "Increment by 1 Tab"
def draw(self, context):
layout = self.layout
obj = context.object
row = layout.row()
row.operator("op.add_one")
def register():
bpy.utils.register_class(AddOne)
bpy.utils.register_class(MySettings)
bpy.utils.register_class(CreatePanel)
def unregister():
bpy.utils.unregister_class(AddOne)
bpy.utils.unregister_class(MySettings)
bpy.utils.unregister_class(CreatePanel)
if __name__ == "__main__":
register()
===
However, when I press the button 'Increment by 1', I get the following error:
"local variable 'my_number' referenced before assignment"
The point of this exercise is just to create an integer variable, store it, then increment it's value and print it out.
EDIT: I added the actual code, rather than an image of it.
The variable my_number is defined in the class MySettings - it can only be accessed through that class, whether that is inside a method that is also part of the class (self.my_number) or directly as a property that is part of an instance of the class (settings_instance.my_number).
You need to find a place outside of the operator and panel to store persistent variables. Adding a custom property to the object or scene types are common options. As you are showing your panel in the node editor, maybe you will want to add it to the material to keep it specific to a material, instead of global to the scene. You define these properties in the addons register() and remove them in unregister().
def register():
bpy.types.Scene.my_settings = bpy.props.PointerProperty(type=MySettings)
def unregister():
del bpy.types.Scene.my_settings
Then in your operator (or main() function) and your panel you can access the variable through the context paramater.
context.scene.my_settings.my_number += 1
Putting that together into your example, with a label to show the value -
import bpy
from bpy.props import (IntProperty,)
from bpy.types import (Panel, Operator, AddonPreferences, PropertyGroup,)
def main(context):
context.scene.my_settings.my_number += 1
print(str(context.scene.my_settings.my_number))
class MySettings(PropertyGroup):
my_number: IntProperty(
name="Int property",
description="This is an integer.",
default = 1
)
class AddOne(Operator):
"""This is an operator"""
bl_idname = "op.add_one"
bl_label = "Increment by 1"
def execute(self, context):
main(context)
return {'FINISHED'}
class CreatePanel(Panel):
bl_label = "Render Setup Panel"
bl_idname = "OBJECT_PT_hello"
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'UI'
bl_category = "Increment by 1 Tab"
def draw(self, context):
layout = self.layout
obj = context.object
row = layout.row()
row.operator("op.add_one")
row = layout.row()
row.label(text='Value is: '+str(context.scene.my_settings.my_number))
def register():
bpy.utils.register_class(AddOne)
bpy.utils.register_class(MySettings)
bpy.utils.register_class(CreatePanel)
bpy.types.Scene.my_settings = bpy.props.PointerProperty(type=MySettings)
def unregister():
bpy.utils.unregister_class(AddOne)
bpy.utils.unregister_class(MySettings)
bpy.utils.unregister_class(CreatePanel)
del bpy.types.Scene.my_settings
if __name__ == "__main__":
register()
You will find blender.stackexchange a better place to ask for blender specific python help.
Generally this problem "local variable 'my_number' referenced before assignment" comes when you have 'my_number' variable in code and you had not initialized that variable at top of your code or before using that variable do one thing .
Declare my_number=0 and then do your calculation on my_number variable .
The problem I am having now is the user inputs are being called twice. Is there any better way to share self between python scripts more easily and avoid calling input twice. I read about multiprocessing module in python, but couldn't use it.
This is a short example:
script1.py:
class importparam(object):
def dataset1(self):
self.var1=1
self.var2=2
self.var3=3
self.var4=input('enter var4 : ')
def dataset2(self):
self.const1=11
self.const2=22
self.const3=33
self.const4 = input('enter const4 : ')
script2.py:
from main import main_
class reader_(main_):
def __init__(self):
super(reader_, self).__init__()
def addition(self):
self.add1 = self.const1+self.const2
self.add2 = self.const3+self.const4
def multiply(self):
self.mult1 = self.var1+self.var2
self.mult2 = self.var3+self.var4
main.py
from script1 import importparam
class main_(importparam):
def __init__(self):
super(main_, self).dataset1()
super(main_, self).dataset2()
def fun1(self):
from script2 import reader_
r=reader_()
val=1
if val==1:
r.addition()
else:
r.multiply()
if __name__=='__main__':
main_().fun1()