Issues with accessing information declared in different modules in kivy - python

I've got a python file(root.py) and another python file(button.py). When I define a button with an attribute (such as size_hint:0.1,1) in button.py using the kv language, root.py doesnt seem to be able to access that information.
When I define the same information using python in button.py, root.py seems to be able to access it.
ROOT.PY
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang.builder import Builder
from buttons import *
Builder.load_string("""
<Root>:
ButtonBar:
""")
class BtnBar(ButtonBar):
print(self.size_hint) # prints [1,1] instead of [0.1,1]
class Root(FloatLayout):
pass
class AppDev(App):
def build(self):
return Root()
BUTTON.PY
from kivy.uix.floatlayout import FloatLayout
from kivy.lang.builder import Builder
Builder.load_string("""
<ButtonBar>:
size_hint: 0.1,1
""")
class ButtonBar(FloatLayout):
pass
Root.py should be able to access any information I declare in button.py's Builder.load_string

After the object is instantiated, the right data will be accessable. You can use clock to make sure you get it after instantiation.
Also in your kv string, you probably want BtnBar instead of ButtonBar
Here is your root.py rewritten to do this.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang.builder import Builder
from kivy.clock import Clock
from buttons import *
Builder.load_string("""
<Root>:
BtnBar: # corrected to BtnBar
""")
class BtnBar(ButtonBar):
def __init__(self, **kwargs):
super(BtnBar, self).__init__(**kwargs)
Clock.schedule_once(self.get_data)
def get_data(self, dt):
print(self.size_hint) # prints on second frame
class Root(FloatLayout):
pass
class AppDev(App):
def build(self):
return Root()
AppDev().run()

Related

KivyMD without using kv file

I'm trying to make an expense tracker app using KivyMD. I have built it already using kivy but it's design is awful, then i found out KivyMD and now i want to tweak the app using KivyMD but i want to do it without using a kv file because my app has a lot of nested if statements which are too complex to write in the kv file. Anyway, i'm trying to test KivyMD but running into this nasty ValueError
ValueError: KivyMD: App object must be initialized before loading root widget. See https://github.com/kivymd/KivyMD/wiki/Modules-Material-App#exceptions and idk how to fix it without using a kv file. This question is asked plenty times but every answer uses a kv file. Can someone please help me understand this error and tackle it without kv. Thank you... Here is some code
from kivymd.app import MDApp
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.app import App
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.stacklayout import MDStackLayout
from kivymd.uix.button import MDRaisedButton, MDRectangleFlatButton
from kivy.metrics import dp,sp
from kivymd.uix.screen import MDScreen
from kivy.uix.textinput import TextInput
from kivymd.uix.textfield import MDTextField
from kivy.uix.screenmanager import ScreenManager
import re
#ALL SCREENS
class MainScreen(MDScreen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
box = MDBoxLayout(orientation="vertical")
b = MDRaisedButton(text="Content",size_hint = (1,0.5))
box.add_widget(b)
t = MDTextField(size_hint=(1,0.5))
box.add_widget(t)
self.add_widget(box)
#ScreenManager
sm = ScreenManager()
sm.add_widget(MainScreen(name="main_screen"))
class MyApp(MDApp):
def build(self):
self.theme_cls.primary_palette = "DeepOrange"
self.theme_cls.accent_palette = "Lime"
return MainScreen()
if __name__ == "__main__":
MyApp().run()
works perfectly fine when i remove the screenmanager and just return the MainScreen.
Any help or guidance is highly appreciated.

Why is text not showing on my kivy label?

as far as I know, my codes are good because there is no error from the IDE, and I used the same code on another project and it works. I read from SO posts that say, maybe the object don't have the .text() method, that would give me an error, but I don't have an error. Another post that say could come from not set the table to an object. As far as I know, I referenced everything correctly.
does it have to do with the data type? thanks again for some insight.
main.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.lang.builder import Builder
from kivy.properties import ObjectProperty, StringProperty
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.graphics import Rectangle, Color
import socket
class Screen_Manager(ScreenManager):
screen_1 = ObjectProperty()
class Screen_1(Screen):
main_display = ObjectProperty()
def __init__(self, **kwargs):
super(Screen_1, self).__init__(**kwargs)
def get_host(self):
self.s = socket.gethostbyname(socket.gethostname())
return self.s
class MainApp(App):
def build(self):
self.title = "Number Display App"
self.sm = Screen_Manager()
return self.sm
if __name__=="__main__":
MainApp().run()
main.ky
<Screen_Manager>:
Screen_1:
<Screen_1>:
id: screen_1
name: "first"
main_display: display_1
Label:
id: display_1
text: root.get_host()
font_size: "30sp"
It looks like you don't set the text of your label to anything but " ".

Is it possible to place all the Kivy code in a python user function, called from Main?

I'm using Kivy for a user interface to a control application. As such, the UI is subsidiary to the essential functionality. For modularity/tidyness, I've been trying to place all the Kivy code in a separate source file and to call it as the last step in Main.
However, if I do this, I find that some things don't work properly (for example, creating labels programatically and updating their text by scheduled events).
Is this something which should be possible?
Is there a 'trick' of which I should be aware?
If the detail of my question is not clear, I can put up some test code to illustrate.
Monolithic code, which works as expected:
cat test.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, NoTransition
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.properties import StringProperty
from kivy.clock import Clock
import random
Builder.load_string('''
<YourWidget>:
BoxLayout:
id: Box1
size: root.size
Button:
id: button1
text: "Change text"
on_release: root.change_text()
''')
class YourWidget(Screen):
random_number = StringProperty()
def __init__(self, **kwargs):
super(YourWidget, self).__init__(**kwargs)
label = Label(
id= 'label1',
text = self.random_number
)
self.ids.Box1.add_widget(label)
self.random_number = 'ini'
Clock.schedule_interval(self.change_text,1)
def change_text(self, *largs):
self.random_number = str(random.randint(1, 100))
class updatetestapp(App):
def build(self):
sm = ScreenManager(transition=NoTransition())
sm.add_widget(YourWidget(name='d_analogs'))
return YourWidget()
if (True):
updatetestapp().run()
If I then attempt to modularise my code, placing all the Kivy processing in a user function,everything works except the display of the ransom number variable. The variable persists through successive callbacks, as shown by calls to print, but it simply doesn't display. I'm guessing that this is something to do with the scope or context of the variable - Kivy isn't displaying the same variable which is being updated in the callback.
cat Main.py
#from mytest import mytest
import threading
import time
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, NoTransition
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.properties import StringProperty
from kivy.clock import Clock
from display import *
def main():
Display()
if __name__ == "__main__":
main()
cat display.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, NoTransition
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.properties import StringProperty
from kivy.clock import Clock
import random
Builder.load_string('''
<YourWidget>:
BoxLayout:
id: Box1
size: root.size
Button:
id: button1
text: "Change text"
on_release: root.change_text()
''')
class YourWidget(Screen):
random_number = StringProperty()
def __init__(self, **kwargs):
super(YourWidget, self).__init__(**kwargs)
label = Label(
id= 'label1',
text = self.random_number
)
self.ids.Box1.add_widget(label)
self.random_number = 'ini'
Clock.schedule_interval(self.change_text,1)
def change_text(self, *largs):
self.random_number = str(random.randint(1, 100))
class test(App):
def build(self):
sm = ScreenManager(transition=NoTransition())
sm.add_widget(YourWidget(name='d_analogs'))
return YourWidget()
def Display():
test().run()
You don't have any code that would change the text of your Label. Although you update self.random_number, you don't update the Label's text property, therefore you don't see any change.

Trouble connecting objects in .kv file to python class

I am learning Kivy and having trouble connecting my objects declared in the .kv file to the python class in order to update their properties. No matter which way I try I get this error:
self.kbCompressionLabel.text = 'Hello World'
AttributeError: 'NoneType' object has no attribute 'text'
The app loads all the kivy files fine, and only breaks when I try to update from the Class.
I've dumbed down the current code to the minimum to illustrate how it is setup. Any help is much appreciated.
Main app entry
import kivy
kivy.require('1.10.0')
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager
Builder.load_file('appscreenmanager.kv')
Builder.load_file('compressorscreen.kv')
Builder.load_file('slidersview.kv')
class AppScreenManager(ScreenManager):
pass
class AppManager(App):
def build(self):
return AppScreenManager()
if __name__ == "__main__":
AppManager().run()
Dumed down appscreenmanager.kv
#:kivy 1.10.0
<AppScreenManager>:
CompressorScreen:
...
compressorscreen.kv
<CompressorScreen>:
name: 'compressor'
GridLayout:
rows: 4
cols: 1
SlidersView:
...
This is where the issue is happening: simplified slidersview.kv
#:kivy 1.10.0
#:import slidersview slidersview
<slidersView>:
cols: 4
rows: 2
id: sliders
kbCompressionLabel: kbCompressionLabel
Label:
id: kbCompressionLabel
text: 'test'
slidersview.py
import kivy
kivy.require('1.10.0')
from kivy.uix.gridlayout import GridLayout
from kivy.properties import ObjectProperty
class SlidersView(GridLayout):
# properties
sliders = ObjectProperty(None)
kbCompressionLabel = ObjectProperty(None)
def __init__(self, **kwargs):
self.kbCompressionLabel.text = 'Hello World'
super(SlidersView, self).__init__(**kwargs)
UPDATE
I had to add a delay into the init function then things worked. However, this feels pretty wonky to me. Is this the expected behavior?
Updated slidersview.py
import kivy
kivy.require('1.10.0')
from kivy.clock import mainthread
from kivy.uix.gridlayout import GridLayout
from kivy.properties import ObjectProperty
class SlidersView(GridLayout):
# properties
kbCompressionLabel = ObjectProperty(None)
def __init__(self, **kwargs):
super(SlidersView, self).__init__(**kwargs)
#mainthread
def delayed():
self.kbCompressionLabel.text = 'Hello World'
delayed()
The KV definitions are applied only when you call __init__ of the base classes...
reverse the order and you'll be fine...
class SlidersView(GridLayout):
# properties
sliders = ObjectProperty(None)
kbCompressionLabel = ObjectProperty(None)
def __init__(self, **kwargs):
super(SlidersView, self).__init__(**kwargs) #first!
self.kbCompressionLabel.text = 'Hello World' #2nd!

Kivy on_copy event?

I have a basic text input box with some text in it. When the user attempts to copy the text, I want to execute a function that modifies the text first then returns it. An on_copy event would be perfect. However, I do not see an obvious way to do this in Kivy.
You can create TextInput subclass that overrides the copy() method:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.clock import Clock
Builder.load_string("""
<MyWidget>:
MyTextInput:
MyTextInput:
""")
class MyTextInput(TextInput):
def copy(self, data=''):
# wrap copied text with ---
if data:
data = "--- {} ---".format(data)
else:
data = "--- {} ---".format(self.selection_text)
return super(MyTextInput, self).copy(data)
class MyWidget(BoxLayout):
pass
class ClientApp(App):
def build(self):
return MyWidget()
if __name__ == '__main__':
ClientApp().run()
You should probably override cut() method too.

Categories