I've wrote this code but I don't understand a part of it; the line that uses super(check_box, self).__init__(**kwargs) and when I remove check_box, self from it, the code still functions properly
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.checkbox import CheckBox
from kivy.uix.gridlayout import GridLayout
class check_box(GridLayout):
# for class name don't use keywords like CheckBox or if you use them you should use underscore in the name as well
def __init__(self, **kwargs):
# **kwargs imports keywords related to gridlayout class
super().__init__(**kwargs)
self.cols = 2
# number of columns
self.add_widget(Label(text="Male"))
self.active = CheckBox(active=True)
# making an object
self.add_widget(self.active)
# adding the object to the GUI
self.add_widget(Label(text="Female"))
self.active = CheckBox(active=True)
self.add_widget(self.active)
self.add_widget(Label(text="Other"))
self.active = CheckBox(active=True)
self.add_widget(self.active)
class CheckBoxApp(App):
def build(self):
return check_box()
if __name__ == "__main__":
CheckBoxApp().run()
check_box class inherits the GridLayout class. Calling super() inside check_box class allows us to make a proxy object (temporary object of the superclass) that allows us to access methods of the superclass class.
super().init(param1, param2, ...) simply means that we are calling the constructor method of that temporary superclass object to construct that object by feeding in parameter values.
Arguments inside the super are not used now, but they anyway work without error because they were used in old python versions.
Related
I am trying to get PyCharm to understand that the subclass of my base controller class only takes a specific type of widget.
Minimal example:
import tkinter as tk
class BaseWidgetController:
def __init__(self, parent: 'tk.Widget'): # Parent is always __some__ kind of widget
self._parent = parent
class EntryWidgetController(BaseWidgetController):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._parent: 'tk.Entry' # On this class, I want Pycharm to understand _parent is only ever an Entry (a subclass of tk.Widget), but even adding this line doesn't change its mind.
def say_type(self) -> None:
print(type(self._parent)) # PyCharm still thinks _parent is a tk.Widget
ew = EntryWidgetController(parent=tk.Frame())
ew.say_type() # Obviously this works fine at runtime.
If you want to constrain the EntryWidgetController so that it only accepts tk.Entry or subclasses, the fix is rather simple - just do
class EntryWidgetController(BaseWidgetController):
def __init__(self, parent: 'tk.Entry', **kwargs):
super().__init__(parent=parent, **kwargs)
That way
ew = EntryWidgetController(parent=tk.Frame())
will make PyCharm complain that Expected type 'Entry', got 'Frame' instead.
I am largely following the instructions here for using properties and I could just use the Person object given there as the backend, but that is not very useful. I am trying to figure out how to do the following two things:
Use multiple instances of multiple such classes in a backend and connect them in a way that PySide/QML doesn't complain
Allow customization of the backend by modules to be determined at runtime (i.e. I eventually want to componentize the application - have different components implementing an interface, the component contributes separately to both the GUI and the backend; but this question concerns only the backend)
This is in contrast to simply defining all of these Properties along with their setter and getter on the main backend class (which I was able to do), which is what I mean by modularize in the question.
I modify the Person example from the link to make it something the UI can change and give it an extra attribute for kicks...
person.py
from PySide2.QtCore import QObject, Signal, Property
class Person(QObject):
def __init__(self, name, age):
QObject.__init__(self)
self._name = name
self._age = age
def getName(self):
return self._name
def setName(self, name):
print(f"Setting name to {name}")
self._name = name
def getAge(self):
return self._age
def setAge(self, age):
print(f"Setting age to {age}")
self._age = age
#Signal
def name_changed(self):
pass
#Signal
def age_changed(self):
pass
name = Property(str, getName, setName, notify=name_changed)
age = Property(str, getAge, setAge, notify=age_changed)
Just as an example I'll create two instances of Person. The first instance I have created as a class member. This is not really what I want, but closer resembles the way properties were used in the link. The second instance is what I really want which is that the properties are instance members, so that I can add them from elsewhere in the application at runtime. Neither method currently works
main.py
import sys
from os.path import abspath, dirname, join
from PySide2.QtCore import QObject, Property, Signal
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
from person import Person
class Backend(QObject):
def __init__(self):
QObject.__init__(self)
def registerProperty(self, name : str, prop):
setattr(self, name, prop)
person1 = Person("Jill", 29)
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
context = engine.rootContext()
# Instance of the Python object
backend = Backend()
# simulate properties added by another module
backend.registerProperty("person2", Person("Jack", 30))
qmlFile = join(dirname(__file__), 'view3.qml')
engine.load(abspath(qmlFile))
# Expose the Python object to QML
context.setContextProperty("backend", backend)
# I tried this but it did nothing
# context.setContextProperty("backend.jack", backend.jack)
# context.setContextProperty("backend.jill", backend.jill)
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
finally the view3.qml file is simply
import QtQuick 2.0
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
import QtQuick.Window 2.12
ApplicationWindow {
visible: true
ColumnLayout {
TextField {
implicitWidth: 200
onAccepted: {
backend.person1.name = text
}
}
TextField {
implicitWidth: 200
onAccepted: {
backend.person1.age = text
}
}
TextField {
implicitWidth: 200
onAccepted: {
backend.person2.name = text
}
}
TextField {
implicitWidth: 200
onAccepted: {
backend.person2.age = text
}
}
}
}
When I try to set any of the values in the UI the error is always the same (the error appears against the QML file)
TypeError: Value is undefined and could not be converted to an object
Ultimately I would like to have such objects nested to any arbitrary depth. Is there a way to achieve what I am trying to do here? Or am I maybe completely off track with the way I'm setting about this?
I don't know that I'm qualified to advise you on overall architecture design for GUI apps, but I think I can explain what's going wrong, and suggest a way to do what you describe. Your registerProperty method adds a Python attribute, but as you've seen, that doesn't make it visible from QML.
The bad news: Qt properties cannot be added to an object after it's created.
The good news: You can create a Qt property that's a list (or a dictionary), and add to it.
One pitfall to be aware of is that to expose a list to QML, you specify its type as 'QVariantList'. (For dictionaries, use 'QVariantMap', and make sure your keys are strings.)
Here's an example of how your Backend class could look. (Using super() to access the parent class means you don't have to pass self to its initializer.)
from Pyside2.QtCore import QObject, Property, Signal
class Backend(QObject):
def __init__(self):
super().__init__()
self.people = []
people_changed = Signal('QVariantList')
#Property('QVariantList', notify=people_changed)
def people(self):
return self._people
#value.setter
def people(self, new_people):
self._people = new_people
self.people_changed.emit(new_people)
def add_person(self, name, age):
self.people.append(Person(name, age, self))
# Note: only ASSIGNING will automatically fire the changed
# signal. If you append, you have to fire it yourself.
self.people_changed.emit(self.people)
This will keep QML up to date as you add people; you could also create a similar method to remove people. Parenting each person to the Backend object makes sure Qt will keep references to them as long as your Backend still exists.
For a truly dynamic collection of properties, perhaps you could give your top-level Backend object a dictionary that your other components add to. So backend.people would become backend.properties['people'], and a specific module would be responsible for adding that key to the properties dictionary, then adding to and removing entries from it.
Specifying all those getters and setters is a hassle, isn't it? I spent so long feeling like there must be a better way, and only recently came across a solution here on Stack Overflow. Using this metaclass, your Person class and the Backend I posted above could be simplified to:
from PySide2.QtCore import QObject
# Assuming you save the linked code as properties.py:
from properties import PropertyMeta, Property
class Person(QObject, metaclass=PropertyMeta):
name = Property(str)
age = Property(int)
def __init__(self, name, age, parent=None):
super().__init__(parent)
self.name = name
self.age = age
class Backend(QObject, metaclass=PropertyMeta):
people = Property(list)
def __init__(self):
super().__init__()
self.people = []
def add_person(self, name, age):
self.people.append(Person(name, age, self))
# Automatically emits changed signal, even for in-place changes
(I also changed age to an int, but it could still be a str if you need it to be.)
I have a class with a set of variables that is initialized and accessed from my main.py script. I have another class that is imported from a folder one level deep from the main.py file.
I can access the already set variables anywhere in my main.py file by referencing appVars.some_variable. However, I am not able to access them in the --screen1.py class. It seems the --Variables.py class is being reinitialized in the nested class which results in a blank value.
How do I access the already initialized class variable from the main.py script inside of my --ScreenOne.py script?
File/Folder Structure
main.py
-classes
--ScreenOne.py
--Variables.py
variables.py
from kivy.properties import StringProperty
class Variables():
def __init__(self):
self.server_time = StringProperty('')
main.py
from classes.ScreenOne import ScreenOne
from classes.Variables import Variables
appVars = Variables()
class SomeApp(App):
def update_clock(self, *args):
appVars.server_time = datetime.now()
--ScreenOne.py
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import StringProperty
from classes.Variables import Variables
class ScreenOne(Screen):
def on_enter(self):
print(Variables.server_time)
I have also tried adding appVars = Variables() to the beginning of the --ScreenOne.py script and referencing appVars.server_time however, it did not work as well.
In main.py you initiate class Variables by appVars = Variables(). But in ScreenOne you import Variables but never initiate it, besides initiating it will give a new instance not the same as appVars in main (also print(Variables.server_time) will give an error).
To use appVars in ScreenOne you will need to pass appVars to this class, like:
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import StringProperty
class ScreenOne(Screen):
def __init__(self, appVars):
self.appVars = appVars
def on_enter(self):
print(self.appVars.server_time)
Then when you initiate ScreenOne you do something like:
screen_one = ScreenOne(appVars)
Note in ScreenOne, you do not need to import Variables as all attributes of this class will come with appVars.
By the way as per PEP8 variable names should not use capitals but underscore instead, appVars -> app_vars.
Hello fellow programmers, I'm developing a simple interface application in Python that will enable easy and intuitive entry of inventory forms in an access database.
I currently have a function as follows:
def spawnerror(self, errormsg):
self.running = False
content = Button(text=errormsg)
popup = Popup(title='ERROR!', content=content, auto_dismiss=False)
content.bind(on_press=popup.dismiss)
popup.open()
And I have appropriate error handling done, and the application uses this function as intended. For example, if someone doesn't enter in a required field, it calls this function and spawns an error page with an error and informs the user.
My issue that I run into is that, it needs to set the class variable running to False, because at the end of the main function "submit" it checks for that and if self.running == False, then it needs to skip the execution of data entry in the access database.
Why is this function not setting the class variable of running to false?
Solution - using App.get_running_app()
In the example, a class attribute, running is defined as BooleanProperty. In the spawnerror() function, it uses App.get_running_app() function to get an instance of App class and then access variable, running.
Note
If running, spawnerror() function and submit() function are in different classes then work out the relation of the classes and pass a direct reference between them.
Example
main.py
from kivy.app import App
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import BooleanProperty, ObjectProperty
class RootWidget(BoxLayout):
instance = ObjectProperty(None)
def __init__(self, **kwargs):
super(RootWidget, self).__init__(**kwargs)
self.instance = App.get_running_app()
self.spawnerror('Testing')
def spawnerror(self, errormsg):
self.instance.running = False
content = Button(text=errormsg)
popup = Popup(title='ERROR!', content=content, auto_dismiss=False)
content.bind(on_press=popup.dismiss)
popup.open()
class TestApp(App):
running = BooleanProperty(True)
def build(self):
print("\nbuild:")
self.display_attributes()
return RootWidget()
def on_stop(self):
print("\non_stop:")
self.display_attributes()
def display_attributes(self):
print("\tApp.running =", self.running)
if __name__ == "__main__":
TestApp().run()
Output
I'm relatively new to Python and OOP, and going through the Kivy documentation and found some syntax I was not used to seeing. I am not really sure how to search for the answer since I don't know what to call this type of syntax, so I'll illustrate with the code itself.
Here is the code from the kivy docs:
import kivy
kivy.require('1.0.5')
from kivy.uix.floatlayout import FloatLayout
from kivy.app import App
from kivy.properties import ObjectProperty, StringProperty
class Controller(FloatLayout):
'''Create a controller that receives a custom widget from the kv lang file.
Add an action to be called from the kv lang file.
'''
label_wid = ObjectProperty()
info = StringProperty()
def do_action(self):
self.label_wid.text = 'My label after button press'
self.info = 'New info text'
class ControllerApp(App):
def build(self):
return Controller(info='Hello world')
if __name__ == '__main__':
ControllerApp().run()
When the above code runs
return Controller(info='Hello world')
I understand that it is setting a value for Controller().info (or controller_object_instance.info within the kivy.app.build() code), but I don't understand how this string assignment to the variable 'info' works, since I would think you would need an init() function at the top of the class to accept this assignment. Since there is no init() within the Controller() class, why doesn't
Controller(info='Hello world')
throw an error? How does the above line of code know to properly assign the value 'Hello world' to its own attribute 'info'? Is there some init() function inherited from App and this can someone be used or does it have something to do with these two lines of code?
label_wid = ObjectProperty()
info = StringProperty()
Any help appreciated. Just looking for a way to search for this more on my own, and I'm stuck on what topic to search for.
When you created the class Controller, you wanted to inherit the class FloatLayout.
This means, whenever you create a Controller object, you create a FloatLayout which you extended. So the __init__ function from FloatLayout at executes.
Look at this example:
class Animal:
def __init__(self,**kwargs):
self.info = kwargs.get("info")
class Dog(Animal):
pass
obj = Dog(info="Hello world")
print(obj.info)
Now you want to create a Dog, but a dog is also an Animal, so you want to inherit all it's methods and attributes.
So if you pass info as an argument when creating an instance of Dog, it will be passed to Animal's __init__ function.