Im new to Kivy, Im sorry for asking 2 questions in a post..
First, why the remove_widget() does not work? It said AttributeError: 'MyCard' object has no attribute 'remove_card' but I tried to put them in other classes and it still does not work.
Second, why my widgets are still have "focus behavior" and my buttons are still clickable even when I put a card whose color has little transparent on it
This is my main.py file
class MyCard(Screen):
pass
class HomeScreen(Screen):
def add_card(self):
self.add_widget(MyCard())
def remove_card(self):
self.remove_widget(MyCard(name='cardd'))
class AVCard(Screen):
pass
class ScreenApp(MDApp):
def build(self):
def build(self):
sm = ScreenManager(transition=FadeTransition(duration=0.2))
sm.add_widget(HomeScreen(name='home'))
sm.add_widget(AVCard(name='av'))
return sm
and this is my home.kv file (AVCard class has its own .kv file)
<HomeScreen>:
name: 'home'
MDIconButton:
on_release: root.add_card()
...
<MyCard>:
name: 'cardd'
MDCard: #-> I put this card is to not allow user click on widgets behind it but it does not work
md_bg_color: 0, 0, 0, .3
...
MDCard: #-> Thís card is like a window which includes small widgets in it
...
Screen:
MDIconButton: #-> The close button
icon: "close-circle"
...
on_release:
root.remove_card()
Thank you very much.
In your kv, the root.remove_card() is trying to reference the remove_card() method of root. In this instance root refers to the root of the rule in which it appears, which is the MyCard. That is why you see the error message, the remove_card() is not in the MyCard object. The fix is to use a reference to the correct object that does contain the remove_card() method, like this:
<MyCard>:
MDCard: #-> I put this card is to not allow user click on widgets behind it but it does not work
md_bg_color: 0, 0, 0, .3
MDCard: #-> Thís card is like a window which includes small widgets in it
Screen:
MDIconButton: #-> The close button
icon: "close-circle"
on_release:
app.root.get_screen('home').remove_card(root)
Note the use of app.root.get_screen('home').remove_card(root), this gets a reference to the HomeScreen object (assuming the name used is home), and calls its remove_card() method with root (the MyCard instance) as an arg.
Then , in the HomeScreen class, the remove_card() method can be:
def remove_card(self, card):
self.remove_widget(card)
Related
Please help me, my competition is around the corner
I tried to use OOP in Kivy. This is my simple Python code for testing:
class location:
def __init__(self, total_house, total_land):
self.total_house = total_house
self.total_land = total_land
class test(BoxLayout):
def addNum(self):
App.get_running_app().x.total_house += 1
class testApp(App):
x = location(NumericProperty(10),NumericProperty(5))
testApp().run()
this is my kv file:
<test>:
orientation: 'vertical'
Label:
text: str(app.x.total_house)
Button:
text: 'add'
on_press: root.addNum()
This is the output
I want the output to be 10 and when the button is pressed the number is added by one.
Please help me, I am new to KIVY
One way of getting pure value from Kivy Property is to use the built-in .get(EventDispatcher obj) method from the kivy.properties.Property class:
class test(BoxLayout):
def addNum(self):
App.get_running_app().x.get(EventDispatcher()) += 1
But before that, you need to import the EventDispatcher class first:
from kivy._event import EventDispatcher
Also please note that while this works in theory and it will indeed change the value of the x variable, I would recommend directly changing the label's own text, something like this:
.py
def numberify(*args):
# This functions is for universally changing str to either int or float
# so that it doesn't happen to return something like 8.0 which isn't that great
a = []
for w in range(0, len(args)):
try:
a.append(int(args[w]))
except ValueError:
a.append(float(args[w]))
return a if len(a) > 1 else a[0]
class test(BoxLayout):
def addNum(self):
self.ids.label1.text = str(numberify(self.ids.label1.text) + 1)
.kv
<test>:
orientation: 'vertical'
Label:
id: label1
text: str(app.x.total_house)
Button:
id: button1
text: 'add'
on_press: root.addNum()
Learn more about Kivy Property here and understand how it's not always necessary to use them :)
I am currently developing a GUI with Python/ Kivy and have some issues when it comes to call a function from another class. I set up a screen, which includes a TextInput widget, that can be used to insert an E-Mail address. By clicking the Submit-Button, the function 'check_mail' is called, which checks the E-Mail using regular expressions and then either prints a text ('E-Mail not valid') or changes the screen (The E-Mail Address will later be transferred to a database, for now its fine to just change the screen after submitting). However, the function does print me the text, if the E-Mail format is not valid, but when it comes to call the change_screen function from the InsertData class, it is not working (AttributeError: 'NoneType' object has no attribute 'ids') If I call the change_screen function within the .kv file {on_release: app.change_screen('home_screen')}, it works fine. How can I access the change_screen function from my InsertData class?
main.py
class HomeScreen(Screen):
pass
class InsertData(Screen):
def check_mail(self):
addressToVerify = self.ids.email_main.text
match = re.match('^[_a-z0-9-]+(\.[_a-z0-9-]+)*#[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$', addressToVerify)
if match == None:
print('Email not valid!')
else:
MainApp().change_screen('home_screen')
GUI = Builder.load_file("main.kv")
class MainApp(App):
def build(self):
return GUI
def change_screen(self, screen_name):
screen_manager = self.root.ids[
'screen_manager']
screen_manager.transition = CardTransition()
screen_manager.transition.direction = 'up'
screen_manager.transition.duration = .3
screen_manager.current = screen_name
MainApp().run()
insert_data.kv
<InsertData>:
FloatLayout:
canvas:
Rectangle:
size: self.size
pos: self.pos
source: "background/background_main.png"
GridLayout:
rows: 1
pos_hint: {"top": 1, "right": 1}
size_hint: 1, .8
TextInput:
id: email_main
hint_text: "E-Mail Address"
LabelButton:
text: "Submit"
on_release:
root.check_mail()
This is how you can call function x for class B
class A:
def __init__(self):
pass
def x(self):
print('x')
class B:
def __init__(self):
A().x()
Update Managed to solve the problem.
Looking at the error I noticed that every time, the function change_screen is executed (due to a not-valid E-Mail format), it will run the following line in change_screen:
screen_manager = self.root.ids['screen_manager']
The problem was, that self.root was referring to the InsertData class, which does not have the attributes. Since self.roots refers to the GUI in the MainApp class, I changed the line in the change_screen function as follows:
screen_manager = GUI.ids['screen_manager']
Now the function is referring to the fixed GUI instead of the self.root and is running without any problems.
I have some solution just to show how does it will work.
class A (self):
def some_method(self):
print("Print Here")
class B (self):
def some_more_method(self):
print("Let see here")
Details:
Let's say you want to use method/function from class B in class A.
'Add this line'
B.some_more_method(self)
It works for me.
I am using carousel in FlashDisplayPage of my app. and I want to use label inside the carousel. I searched relevant sites and made the below code. I am new to kivy so I don't know if this is the right way. If its not how can I make it right?
And an error is also occurring here, even though I have defined label_name in FlashDisplayPage I am getting this error-AttributeError: 'FlashDisplayPage' object has no attribute 'label_name'. Why this error is showing up? how can I remove it?
here the part of the code where the error is occurring-
class FlashDisplayPage(Screen):
def on_enter(self):
Num=self.index
card=['flash_card1.json','flash_card2.json','flash_card3.json','flash_card4.json','flash_card5.json','flash_card6.json','flash_card7.json','flash_card8.json','flash_card9.json','flash_card10.json']
label_name='Flash Card '+ Num #here I have defined label_name
with open(card[Num+1]) as frfile:
flash_data=json.load(frfile)
for i in flash_data:
self.ids.CarDisplay.add_widget(Label(text=i['word']+' : '+i['meaning']))
# here I am adding label to carousel
def next_one(self):
self.ids.CarDisplay.direction='right' # next label in carousel
def previous_one(self):
self.ids.CarDisplay.direction='left' # previous label in carousel
here the part of kv code related to this-
<FlashDisplayPage>:
BoxLayout:
orientation: 'horizontal'
spacing:15
padding: 20
Label:
id: l
text: root.label_name
sixe_hint_y: None
height: 100
Carousel:
id: CarDisplay
loop: True
Button:
text:'next'
size_hint: None,.20
width: 30
on_press:root.next_one()
Button:
text:'next'
size_hint: None,.20
width: 130
on_press:root.previous_one()
label_name is a local variable within the on_enter function, and local variables can only be accessed within the function or scope where they were created. If you want to be accessible a possible solution is to make it a property of the widget:
from kivy.properties import StringProperty
class FlashDisplayPage(Screen):
label_name = StringProperty("")
def on_enter(self):
[...]
self.label_name='Flash Card {}'.format(Num)
[...]
Try using self.label_name to create an instance variable instead of a local variable in on_enter() which has local scope only.
This seems to be a silly question. But I have a widget that I want to add to a screen called GameScreen.
This is my Python code:
class WelcomeScreen(Screen):
pass
class BasicScreen(Screen):
pass
class GameScreen(Screen):
parent = Widget()
game = ShootingGame()
parent.add_widget(game)
Clock.schedule_interval(game.update, 1.0 / 60.0)
# return parent
sm = ScreenManager()
sm.add_widget(WelcomeScreen(name='welcome'))
sm.add_widget(BasicScreen(name='basic'))
sm.add_widget(GameScreen(name='game'))
class ShootingApp(App):
def build(self):
print(sm.current)
return sm
if __name__ == '__main__':
ShootingApp().run()
And this is my kivy code:
<WelcomeScreen>:
Button:
text: "Learn about haptic illusions"
size_hint: None, None
size: 500, 70
pos: 100, 200
font_size: 30
on_release: app.root.current = "basic"
Button:
text: "Play our game"
size_hint: None, None
size: 500, 70
pos: 100, 100
font_size: 30
on_release: app.root.current = "game"
<BasicScreen>:
name: "basic"
<GameScreen>:
name: "game"
The error I am getting is this. And I think this is because I already defined a parent for the widget game. However, I need that parent because the game widget uses width and height values of its parent (e.g., self.parent.width). Is there any workaround for this so that the game widget can be nested in a parent and add the parent to the screen?
kivy.uix.widget.WidgetException: Cannot add <Screen name='game'>, it already has a parent <kivy.uix.widget.Widget object at 0x1093dc8d8>
Thanks guys!!
you can do something like this
class GamesScreen(Screen):
def __init__(self, **kwargs):
super(GameScreen, self).__init__(**kwargs)
self.game = ShootingGame()
self.add_widget(self.game)
clock.schedule_interval(self.game.update, 1.0 / 60.0)
It looks to me like what's happening is that you attempt to give GameScreen a parent TWICE. Once when telling it it's parent is Widget(), and again when you add it to the ScreenManager(which would make sm it's parent). Whichever of them is executed first(I think the parent = Widget() line from looking at the Exception) is causing the error when you try it the second time.
How do I dynamically resize the a label or button, in particular, the text_size and height, depending on the amount of text, at run-time?
I am aware that this question has already been answered in one way with this question:
Dynamically resizing a Label within a Scrollview?
And I reflect that example in part of my code.
The problem is dynamically resizing the labels and buttons at run-time. Using, for example:
btn = Button(text_size=(self.width, self.height), text='blah blah')
...and so on, only makes the program think (and logically so) that the "self" is referring to the class which is containing the button.
So, how do I dynamically resize these attributes in the python language, not kivy?
My example code:
import kivy
kivy.require('1.7.2') # replace with your current kivy version !
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
i = range(20)
long_text = 'sometimes the search result could be rather long \
sometimes the search result could be rather long \
sometimes the search result could be rather long '
class ButtonILike(Button):
def get_text(self):
return long_text
class HomeScreen(Screen):
scroll_view = ObjectProperty(None)
def __init__(self, *args, **kwargs):
super(HomeScreen, self).__init__(*args, **kwargs)
layout1 = GridLayout(cols=1, spacing=0, size_hint=(1, None), \
row_force_default=False, row_default_height=40)
layout1.bind(minimum_height=layout1.setter('height'),
minimum_width=layout1.setter('width'))
layout1.add_widget(ButtonILike())
for result in i:
btn1 = Button(font_name="data/fonts/DejaVuSans.ttf", \
size_hint=(1, None), valign='middle',)#, \
#height=self.texture_size[1], text_size=(self.width-10, None))
btn1.height = btn1.texture_size[1]
btn1.text_size = (btn1.width-20, layout1.row_default_height)
btn1.text = long_text
btn2 = Button(font_name="data/fonts/DejaVuSans.ttf", \
size_hint=(1, None), valign='middle')
btn2.bind(text_size=(btn2.width-20, None))
btn2.text = 'or short'
layout1.add_widget(btn1)
layout1.add_widget(btn2)
scrollview1 = self.scroll_view
scrollview1.clear_widgets()
scrollview1.add_widget(layout1)
class mybuttonsApp(App):
def build(self):
return HomeScreen()
if __name__ == '__main__':
mybuttonsApp().run()
And the kv file:
#:kivy 1.7.2
<ButtonILike>:
text_size: self.width-10, None
size_hint: (1, None)
height: self.texture_size[1]
text: root.get_text()
#on_release: root.RunSearchButton_pressed()
<HomeScreen>:
scroll_view: scrollviewID
AnchorLayout:
size_hint: 1, .1
pos_hint: {'x': 0, 'y': .9}
anchor_x: 'center'
anchor_y: 'center'
Label:
text: 'Button Tester'
ScrollView:
id: scrollviewID
orientation: 'vertical'
pos_hint: {'x': 0, 'y': 0}
size_hint: 1, .9
bar_width: '8dp'
You can see that I added the button from the kv file which displays all the behavior that I want at the top of the list. Resize your window while running it, and you can see the magic. And, of course, changing the text_size also makes it possible for me to align text.
I simply have not been able to achieve the same behavior on the python side. My app requires that the buttons be created at run-time. I think the answer might lie with "bind()", though admittedly, I'm not sure I used it correctly in my attempts or that I understand it fully. You can see that I tried with "btn2", which I thought would've thrown the text to the left (since halign defaults to left), but didn't seem to do anything.
I appreciate the help.
I think the best way is to set Label's/Button's size to texture_size:
Label:
text: "test"
size_hint: None, None
size: self.texture_size
canvas.before: # for testing purposes
Color:
rgb: 0, 1, 0
Rectangle:
pos: self.pos
size: self.size
My answer is slightly different from #martin's - I only want to modify the height.
def my_height_callback(obj, texture: Texture):
if texture:
obj.height = max(texture.size[1], 100)
class MyButton(Button):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.size_hint = (1, None)
self.bind(texture=my_height_callback)
When the text is rendered the texture property of the button gets set. That texture's height is then pushed to the button's height via the callback. Calling max() allows for a minimum height to be set. This works fine with labels as well.
btn2.bind(text_size=(btn2.width-20, None))
As with your other question, the problem is that you have the syntax of bind wrong. You must pass a function, but you just wrote a tuple, and bind can't do anything useful with that - it certainly doesn't know you happened to write btn2.width there.
Also, the syntax is that bind calls the function when the given property changes. That's the opposite of what you want - you need to change the text_size when btn2.width changes, not call a function when text_size changes
I think something like the following would work. instance and value are the default arguments we ignored in the other question.
def setting_function(instance, value):
btn2.text_size = (value-20, None)
btn1.bind(width=setting_function)
I was looking to resize both the text_size width and height, the latter specifically with regard to the documented behaviour of kivy.Label that vertical alignment of text in a label cannot be achieved without doing this first. Further, I needed to do it in python, not .kv.
class WrappedVAlignedLabel(Label):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.bind(height=lambda *x:self.setter('text_size')(self, (self.width, self.height)))
strangely, binding on width instead of height would only set text_size[0], I guess due to some order of rendering self.height wasn't yet computed, so the setting of text_size[1] wasn't happening. Whereas binding on height gets them both.