self.minimum_height not working for BoxLayout containing other layouts - python

I'm working on a simple task list with a checkbox in the left and a text input in the right. I tried putting 20 checkboxes and text input inside a box layout, which I put inside a ScrollView. However, it seems that the minimum_height of the boxlayout does not involve the height of the two gridlayouts inside it. Any idea aside from setting the height of the scrollview to "xdp" so that in the future when the user adds another tasks, the height will adjust accordingly?
Python File:
class Task_List(GridLayout):
pass
class CheckBox_Area(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.padding = (dp(10), dp(10), dp(20), dp(20))
for i in range(0, 20):
size = dp(40)
check_box = CheckBox(size_hint=(1, None), size=(0, size))
self.add_widget(check_box)
class List_Area(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation = "lr-tb"
self.padding = (dp(10), dp(10), dp(20), dp(20))
for i in range(0, 20):
text = TextInput(text=str(f"Task {i}"), size_hint=(1, None), size=(0, dp(40)))
self.add_widget(text)
kv file:
Task_List:
<Task_List>:
rows: 3
Label:
text: "Tasks for Today"
size_hint: 1, None
height: "50dp"
Label:
text: "Area for Quotes"
size_hint: 1, None
height: "50dp"
Scroll_Body:
<CheckBox_Area>:
cols: 1
<List_Area>:
cols: 1
<Main_Body#BoxLayout>:
CheckBox_Area:
size_hint: .1, 1
List_Area:
<Scroll_Body#ScrollView>:
Main_Body:
size_hint: 1, None
height: self.minimum_height

Related

kivy Can't scroll using scrollview even after setting height: minimum_height

Almost all of the similar problems I've read had the same solution, which I've already done from the start. So I don't know the problem I am encountering, but it may be from the stack layouts I have used.
Python File
class Task_List(BoxLayout):
pass
class CheckBox_Area(StackLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation = "lr-tb"
self.padding = (dp(20), dp(10), dp(20), dp(0))
for i in range(0, 20):
size = dp(40)
b = CheckBox(size_hint=(None, None), size=(size, size))
self.add_widget(b)
class List_Area(StackLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation = "lr-tb"
self.padding = (dp(10), dp(10), dp(20), dp(20))
for i in range(0, 20):
b = TextInput(text=str(f"Task {i}"), size_hint=(1, None), size=(0, dp(40)))
self.add_widget(b)
kv file
Scroll:
<Task_List>:
max: False
orientation: "vertical"
Label:
text: "Tasks for Today"
size_hint: 1, None
height: "50dp"
Label:
text: "Area for Quotes"
size_hint: 1, None
height: "50dp"
BoxLayout:
orientation: "horizontal"
CheckBox_Area:
List_Area:
size_hint: 5, 1
<CheckBox_Area>:
<List_Area>:
<Scroll#ScrollView>:
Task_List:
size_hint: 1, None
height: self.minimum_height
Every children of ScrollView should have at least one explicit size value(s) (depending on the scrolling direction).
Thus the changes you need,
BoxLayout:
orientation: "horizontal"
size_hint_y: None
height: self.minimum_height
CheckBox_Area:
List_Area:
size_hint: 5, None # Note that `size_hint_x` value should be between 0 and 1 otherwise you may get unexpected result.
height: self.minimum_height
Edit:
Since ScrollView accepts a single widget, the statement 'Every children of ScrollView should have at least one explicit...' is invalid. What I wanted to mean is that this is applicable to the widget's (that single widget) children so that that widget's at least one size value becomes explicit.

how to set size to texture_size in python

So this is the .kv code:
Button:
id:last_message_2
text:''
font_size:18
background_normal:'textures/message.png'
size_hint_x:None
size: self.texture_size
pos_hint:{'right': 1}
padding:(9, 0)
opacity:0
on_release: root.onClick("send_rcnt2")
and what i want to do is make a custom button widget like this:
class MessageBubble(Button):
def __init__(self, **kwargs):
super(MessageBubble, self).__init__(**kwargs)
self.text = "Hello World"
self.font_size = 18
self.background_normal = "textures/message.png"
self.size_hint_x = None
self.size = self.texture_size
self.pos_hint = {'right': 1}
self.padding = (9, 0)
self.opacity = 0
and have only this left in the .kv file:
MessageBubble:
id:last_message_1
on_release: root.onClick("send_rcnt1")
but when i run the code i get a very small button with text out off bound
and when i debugged i got to know that texture_size was not updating dynamically
What can i do now?
You need to set the size in the kv rule for MessageBubble, because it needs to be updated later when self.texture_size changes. You can also do this manually by creating bindings in python.

Kivy add widgets on different sides of the Layout

I have a chat app that uses a widget to print out messages. I want to print these on different sides, so the user input goes on the right and the answer goes on the left. Furthermore, I want the chat box to scroll to the new message. Here is my code, where I tried to use a StackLayout, only to realise it doesn't work:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
class Message(Widget):
pass
class KatApp(App):
def post(self):
msg = self.root.ids.usrinp.text
if len(msg) > 0:
self.root.ids.chatbox.orientation = 'tb-rl'
msgbox = Message()
msgbox.ids.mlab.text = msg
self.root.ids.chatbox.add_widget(msgbox)
self.root.ids.scrlv.scroll_to(msgbox)
self.root.ids.usrinp.text = ''
def resp(self,msg):
if len(msg) > 0:
ansr = msg
self.root.ids.chatbox.orientation = 'tb-lr'
ansrbox = Message()
ansrbox.ids.mlab.text = str(ansr)
self.root.ids.chatbox.add_widget(ansrbox)
self.root.ids.scrlv.scroll_to(ansrbox)
self.root.ids.usrinp.text = ''
def build(self):
return Builder.load_string('''
<Message#Widget>:
size_hint: None, None
height: mlab.height
width: mlab.width
canvas:
Color:
rgba: 0, 1, 0, 0.7
Rectangle:
pos: self.pos
size: self.size
Label:
id: mlab
center: root.center
padding: 10, 10
markup: True
text_size: (None, None)
text: ''
haligh: 'left'
valign: 'top'
size_hint: (1, None)
size: self.texture_size
color: 0, 0, 0
ScreenManager:
Screen:
BoxLayout:
orientation: 'vertical'
ScrollView:
canvas.before:
Color:
rgb: 1, 1, 1
Rectangle:
pos: self.pos
size: self.size
StackLayout:
id: chatbox
padding: 10, 10
orientation: 'tb-rl'
BoxLayout:
orientation: 'horizontal'
padding: 10, 10, 10, 10
size_hint: 1, None
height: 50
BoxLayout:
id: inpbox
height: max(40, scrlv.height)
size_hint: 0.9, None
ScrollView:
id: scrlv
width: inpbox.width - 15
x: inpbox.x + 10
y: inpbox.y
height:
(len(usrinp._lines)+1) * usrinp.line_height - 5 \
if (len(usrinp._lines)+1 <= 5) \
else 5 * usrinp.line_height - 5
TextInput:
id: usrinp
valign: 'middle'
halign: 'left'
font_size: 16
multiline: True
size_hint: scrlv.size_hint_x, None
padding: 10, 0, 10, 0
Button:
id: post
border: 0, 0, 0, 0
size: 40, 40
size_hint: None, None
on_press:
root.inp = usrinp.text
app.post()
on_release:
app.resp(root.inp)
''')
if __name__ == "__main__":
KatApp().run()
For the sake of this example, the button on the bottom right sends the user input on_press and answers with the same input on_release.
Also, is it possible to make a maximum width for the message widget, like, if it reaches the middle of the page, it should go on the next line?
One more thing that I'm having a hard time figuring out would be the TextInput. It seems that, with the multiline option, when a word is long enough to go on the next line and I try to delete it, some space remains there and it keeps the box from resizing. To reproduce this, just type "aaaaaaaaaa" until it is on line 3 and try to remove it.
To wrap the width of the widget try this:
Function to read text and create label
def Sender_chat_bubble():
Measure the length of the unwrapped text
Measure = Label(text=self.chatinputfield.text)
Measure.texture_update()
if Measure.texture_size[0]== 0 or self.chatinputfield.text=='':
return 0
if the text is not long, keep same width + 10 for the padding I added
elif Measure.texture_size[0]<250:
xsize = Measure.texture_size[0]
chatbubble = ChatBubble_Send(text=self.chatinputfield.text, width=xsize+10, padding_y=10, padding_x=10)
If the unwrapped text is longer than your limit(250 here) , lets wrap it to 260 (250 + 10 padding)
else:
chatbubble = ChatBubble_Send(text=self.chatinputfield.text, width=250+10, padding_y=10, padding_x=10)
Class to make a color behing the label
class ColorLabelSEND(Label):
bgcolor = ListProperty([0.7, 0.7, 0.7, 0.7])
def __init__(self, **kwargs):
super(ColorLabelSEND, self).__init__(**kwargs)
with self.canvas.before:
r, g, b, a = self.bgcolor
Color(r, g, b, a)
self.rect = Rectangle(
size=self.size,
pos=self.pos)
self.bind(size=self._update_rect,
pos=self._update_rect)
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
Class to wrap the text and label
class ChatBubble_Send(ColorLabelSEND):
def __init__(self, **kwargs):
super(ChatBubble_Send, self).__init__(**kwargs)
self.size_hint = (None, None)
#Constrain horizontally to size of label and free vertically
self.text_size = (self.width, None)
self.width = self.width + 15
def on_texture_size(self,*args):
self.texture_update()
self.height = self.texture_size[1]
Writing on each side can be done using a BoxLayout.
def post(self):
msg = self.root.ids.usrinp.text
if len(msg) > 0:
msgbox = Message()
msgbox.ids.mlab.text = msg
msgbox.pos_hint = {'right': 1}
self.root.ids.chatbox.add_widget(msgbox)
self.root.ids.scrlv.scroll_to(msgbox)
self.root.ids.usrinp.text = ''
def resp(self, msg):
if len(msg) > 0:
ansr = msg
ansrbox = Message()
ansrbox.ids.mlab.text = str(ansr)
ansrbox.pos_hint = {'x': 0}
self.root.ids.chatbox.add_widget(ansrbox)
self.root.ids.scrlv.scroll_to(ansrbox)
self.root.ids.usrinp.text = ''
And in the builder:
ScrollView:
canvas.before:
Color:
rgb: 1, 1, 1
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
orientation: 'vertical'
id: chatbox
padding: 10, 10
spacing: 5
After adding pos_hint: {'right': 1} to the input widget, the text now goes on the right side, like this:
The problem now remains with the width of the widget:
I tried setting the width of the widget to max(root.width, mlab.width), but it doesn't work. Also, now, scrolling up doesn't work.

Python : How to remove widget in kivy

I have two file demo.py and demo.kv.
when i run demo.py after click on menu then shows +Add more button.When i click on +Add more button then three row add dynamic because i am using loop there.Every time add three row dynamic
But can anyone tell me when i add new row then how to remove previous three row?
Every time should be show only 3 new row and previous row should be delete.I am using code
def add_more(self):
self.remove_widget(Row())
for x in range(0, 3):
self.row_count += 1
self.add_widget(Row(button_text=str(self.row_count)))
demo.py
from kivy.app import App
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import BooleanProperty, ListProperty, StringProperty, ObjectProperty, NumericProperty
from kivy.uix.popup import Popup
Window.clearcolor = (0.5, 0.5, 0.5, 1)
Window.size = (500, 400)
class User(Popup):
total_value = ObjectProperty(None)
def __init__(self, **kwargs):
super(User, self).__init__(**kwargs)
def add_more(self):
self.ids.rows.add_more()
class Row(BoxLayout):
col_data = ListProperty(["?", "?", "?", "?", "?"])
name = ObjectProperty(None)
button_text = StringProperty("")
col_data3 = StringProperty("")
col_data4 = StringProperty("")
def __init__(self, **kwargs):
super(Row, self).__init__(**kwargs)
class Rows(BoxLayout):
row_count = 0
def __init__(self, **kwargs):
super(Rows, self).__init__(**kwargs)
self.add_more()
def add_more(self):
self.remove_widget(Row())
for x in range(0, 3):
self.row_count += 1
self.add_widget(Row(button_text=str(self.row_count)))
class rv(BoxLayout):
data_items = ListProperty([])
mode = StringProperty("")
def __init__(self, **kwargs):
super(rv, self).__init__(**kwargs)
def add(self):
self.mode = "Add"
popup = User()
popup.open()
class MainMenu(BoxLayout):
content_area = ObjectProperty()
def display(self):
self.rv = rv()
self.content_area.add_widget(self.rv)
class demo(App):
def build(self):
return MainMenu()
if __name__ == '__main__':
demo().run()
demo.kv
<Row>:
size_hint_y: None
height: self.minimum_height
height: 40
Button:
text: root.button_text
size_hint_x: None
top: 200
TextInput:
id : name
text: root.col_data3
width: 300
TextInput:
id: number_input
text: root.col_data4
width: 300
input_filter: 'int'
<Rows>:
size_hint_y: None
height: self.minimum_height
orientation: "vertical"
<User>:
id: user
BoxLayout:
orientation: "vertical"
padding : 20, 5
BoxLayout:
orientation: "horizontal"
#padding : 10, 10
spacing: 10, 10
size: 450, 40
size_hint: None, None
Label:
size_hint_x: .2
text: "Number"
text_size: self.size
valign: 'bottom'
halign: 'center'
Label:
size_hint_x: .4
text: "name"
text_size: self.size
valign: 'bottom'
halign: 'center'
Label:
size_hint_x: .4
text: "Value"
text_size: self.size
valign: 'bottom'
halign: 'center'
ScrollView:
Rows:
id: rows
BoxLayout:
orientation: "horizontal"
size_hint_x: .2
size_hint_y: .2
Button:
text: "+Add More"
on_press: root.add_more()
<rv>:
BoxLayout:
orientation: "vertical"
Button:
size_hint: .25, .03
text: "+Add"
on_press: root.add()
GridLayout:
size_hint: 1, None
size_hint_y: None
height: 25
cols: 3
BoxLayout:
orientation: "vertical"
<MenuButton#Button>:
text_size: self.size
valign: "middle"
padding_x: 5
size : (100, 40)
size_hint : (None, None)
background_color: 90 , 90, 90, 90
background_normal: ''
color: 0, 0.517, 0.705, 1
border: (0, 10, 0, 0)
<MainMenu>:
content_area: content_area
BoxLayout:
orientation: 'vertical'
spacing : 10
BoxLayout:
canvas.before:
Rectangle:
pos: self.pos
size: self.size
size_hint_y: 2
MenuButton:
text: 'Menu'
size : (50, 12)
on_release: root.display()
BoxLayout:
id: content_area
size_hint_y: 30
remove_widget must receive as argument the instance of the child widget to be removed. Since you can get widget's children using its children attribute, to remove the previous three rows you can do the following:
def add_more(self):
if self.children:
for child in self.children[:3]:
self.remove_widget(child)
for x in range(0, 3):
self.row_count += 1
self.add_widget(Row(button_text=str(self.row_count)))
However, it's simpler to use clear_widgets method:
def add_more(self):
self.clear_widgets(self.children[:3])
for x in range(0, 3):
self.row_count += 1
self.add_widget(Row(button_text=str(self.row_count)))
Since you actually delete all the rows in the BoxLayout, you can do:
def add_more(self):
self.clear_widgets()
for x in range(0, 3):
self.row_count += 1
self.add_widget(Row(button_text=str(self.row_count)))
Edit:
To reset the index simply disregard self.row_count and use the value returned by range:
def add_more(self):
self.clear_widgets()
for x in range(1, 4):
self.add_widget(Row(button_text=str(x)))

Call def from one class to another class in python

I have two file demo.py and demo.kv.
Can anyone tell me how to call function from one class to another class?I want to call def calculate(self): from def on_text(self, text_input, value):.Now i am using code
def on_text(self, text_input, value):
App.get_running_app().User.calculate()
But it gives error AttributeError: 'Test' object has no attribute 'User'
demo.py
from kivy.app import App
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import BooleanProperty, ListProperty, StringProperty, ObjectProperty, NumericProperty
from kivy.uix.popup import Popup
Window.clearcolor = (0.5, 0.5, 0.5, 1)
Window.size = (500, 400)
class User(Popup):
total_value = ObjectProperty(None)
def add_more(self):
self.ids.rows.add_row()
def calculate(self):
rows = self.ids.rows
total = 0
for row in rows.children:
text = row.ids.number_input.text
total += int(text) if text != "" else 0 # validate if the entry is not empty
self.total_value.text = str(total)
class Row(BoxLayout):
col_data = ListProperty(["?", "?", "?", "?", "?"])
button_text = StringProperty("")
col_data3 = StringProperty("")
col_data4 = StringProperty("")
def __init__(self, **kwargs):
super(Row, self).__init__(**kwargs)
self.ids.number_input.bind(text=self.on_text)
def on_text(self, text_input, value):
print('Calling')
App.get_running_app().User.calculate()
class Rows(BoxLayout):
row_count = 0
def __init__(self, **kwargs):
super(Rows, self).__init__(**kwargs)
self.add_row()
def add_row(self):
self.row_count += 1
self.add_widget(Row(button_text=str(self.row_count)))
class rv(BoxLayout):
data_items = ListProperty([])
mode = StringProperty("")
def __init__(self, **kwargs):
super(rv, self).__init__(**kwargs)
def add(self):
self.mode = "Add"
popup = User()
popup.open()
class MainMenu(BoxLayout):
content_area = ObjectProperty()
def display(self):
self.rv = rv()
self.content_area.add_widget(self.rv)
class Test(App):
def build(self):
self.root = Builder.load_file('demo.kv')
return MainMenu()
if __name__ == '__main__':
Test().run()
demo.kv
<Row>:
size_hint_y: None
height: self.minimum_height
height: 40
Button:
text: root.button_text
size_hint_x: None
top: 200
TextInput:
text: root.col_data3
width: 300
TextInput:
id: number_input
text: root.col_data4
width: 300
input_filter: 'int'
<Rows>:
size_hint_y: None
height: self.minimum_height
orientation: "vertical"
<User>:
id: user
total_value:total_value
BoxLayout:
orientation: "vertical"
padding : 20, 5
BoxLayout:
orientation: "horizontal"
#padding : 10, 10
spacing: 10, 10
size: 450, 40
size_hint: None, None
Label:
size_hint_x: .2
text: "Number"
text_size: self.size
valign: 'bottom'
halign: 'center'
Label:
size_hint_x: .4
text: "name"
text_size: self.size
valign: 'bottom'
halign: 'center'
Label:
size_hint_x: .4
text: "Value"
text_size: self.size
valign: 'bottom'
halign: 'center'
ScrollView:
Rows:
id: rows
BoxLayout:
orientation: "horizontal"
padding : 10, 5
spacing: 10, 10
size: 200, 40
size_hint: None, None
Label:
size_hint_x: .7
text: "Total value"
TextInput:
id: total_value
on_focus:root.test()
BoxLayout:
orientation: "horizontal"
size_hint_x: .2
size_hint_y: .2
Button:
text: "+Add More"
on_press: root.add_more()
<rv>:
BoxLayout:
orientation: "vertical"
Button:
size_hint: .25, .03
text: "+Add"
on_press: root.add()
GridLayout:
size_hint: 1, None
size_hint_y: None
height: 25
cols: 3
BoxLayout:
orientation: "vertical"
<MenuButton#Button>:
text_size: self.size
valign: "middle"
padding_x: 5
size : (100, 40)
size_hint : (None, None)
background_color: 90 , 90, 90, 90
background_normal: ''
color: 0, 0.517, 0.705, 1
border: (0, 10, 0, 0)
<MainMenu>:
content_area: content_area
BoxLayout:
orientation: 'vertical'
spacing : 10
BoxLayout:
canvas.before:
Rectangle:
pos: self.pos
size: self.size
size_hint_y: 2
MenuButton:
text: 'Menu'
size : (50, 12)
on_release: root.display()
BoxLayout:
id: content_area
size_hint_y: 30
One way to solve this problem without accessing the tree of kivy hierarchies is using global variables, but it is advisable not to abuse this type of variables since the errors generated by its misuse are difficult to trace.
[...]
class Row(BoxLayout):
[...]
def on_text(self, text_input, value):
print('Calling')
popup.calculate()
class rv(BoxLayout):
[...]
def add(self):
self.mode = "Add"
global popup
popup = User()
popup.open()
[...]
This may be a duplicate of this question: Im New on kivy Python classes
Firstly:
class Test(App):
def build(self):
self.root = Builder.load_file('demo.kv')
return MainMenu()
This is a bit unnecessary, you can instead do:
class demo(App):
def build(self):
return MainMenu()
App's search for a kv file with a lowercase name equal to the App's name.
Secondly:
<User>:
id: user
total_value:total_value
This is not how you use ids in the kivy Widgets. When you define a Kivy Widget, ie:
class KivyWidgetName(KivyLayout):
pass
<KivyWidgetName>:
You're creating a class. When you add your custom widget to a parent Widget, you are creating an Object of that KivyWidget Class. Sometimes however if you're making more than one, you need to give them ids so their parent can separate them from each other.
ie:
<RootWidget>:
KivyWidgetName:
id: first_instance
KivyWidgetName:
id: second_instance
So now RootWidget can access two different versions of the class, if it wants to access the first one, it can do:
self.ids.first_instance
You can also access them by the index number in which they appear, so this
can also be done by:
self.ids[1]
However explicit is typically better than implicit
Thirdly, you had the right idea to this:
App.get_running_app().User.calculate()
but it should actually be something like this:
App.get_running_app().root.ids.[INSERTID]
So in this case, you're getting the root widget of app (which is ), then you need to use the ids to get to the proper address.
For example, take this:
:
User:
id: 'user_one'
User:
id: 'user_two'
To access the user calculate function, you can do this:
App.get_running_app().root.ids.user_one.calculate
If you however have a number of children, you'll have to seek all of their ids until you find user:
For example:
<TestWidget>:
User:
id: 'user_one'
User:
id: 'user_two'
<RootWidget>:
TestWidget:
id: 'test_one'
TestWidget:
id: 'test_two'
So to get to user one of test two, you can do this:
App.get_running_app().root.ids.test_two.ids.user_one.calculate
You might need to re-arrange your rootwidget to be something else that just contains your main menu to help you with this but that decision is up to you ultimately.

Categories

Resources