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

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.

Related

Kivy change slider design

Is it possible to make a kivy slider look like this one?
This is my python code:
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.core.window import Window
class MyWidget(GridLayout):
def __init__(self, **kwargs):
super(MyWidget, self).__init__(**kwargs)
Window.clearcolor = (1, 1, 1, 1)
class PhoneApp(App):
def build(self):
return MyWidget()
if __name__ == "__main__":
PhoneApp().run()
This is my kv code:
#:kivy 2.1.0
<Label>
size_hint_y: None
height: self.texture_size[1]
color: 0, 0, 0, 1
<Image>
size_hint_y: None
height: self.texture_size[1]
<Button>
size_hint_x: None
size_hint_y: None
width: self.texture_size[0]
height: self.texture_size[1]
<Slider>
size_hint_y: None
<GridLayout>
cols: 1
size_hint: (0.5, 0.9)
pos_hint: {"center_x": 0.5, "center_y": 0.5}
<MyWidget>:
Image:
source: "captus.png"
Label:
text: "Test Message"
Button:
text: "Second test"
Slider:
id: slider
min: 0
max: 100
For now the slider looks like this:
Is it possible to make it look like the first one?
Also.. I'm wrong or it's like bugged, not aligned with the bar?
First of all, you generally should not create dynamic classes with their default class names as it may affect its overall design for all of its instances which you may not want. Rather create an instance of that class and apply your design or logic therein. So you should change the lines like <Button> in kvlang with <MyButton#Button>.
Now since here you're using the GridLayout as a container you have fewer control over its children's attributes like pos etc. The Slider is also a widget, so you can adjust its size. To change its appearance you can use its properties like cursor_image etc. With all these changes your code in kvlang should now look something like the following,
#:kivy 2.1.0
<MyLabel#Label>
size_hint_y: None
height: self.texture_size[1]
color: 0, 0, 0, 1
<MyImage#Image>
size_hint_y: None
height: self.texture_size[1]
<MyButton#Button>
# size_hint_x: None
size_hint_y: None
# width: self.texture_size[0]
height: self.texture_size[1]
<MySlider#Slider>
size_hint_y: None
height: dp(64) # Set specific height.
cursor_image: "path to image"
background_horizontal: "some path to image"
#<MyGridLayout#GridLayout>
# cols: 1
# size_hint: (0.5, 0.9)
# pos_hint: {"center_x": 0.5, "center_y": 0.5}
<MyWidget>:
cols: 1
size_hint: (0.5, 0.9)
pos_hint: {"center_x": 0.5, "center_y": 0.5}
MyImage:
keep_ratio: True
source: "captus.png"
MyLabel:
text: "Test Message"
MyButton:
text: "Second test"
MySlider:
id: slider
min: 0
max: 100

self.minimum_height not working for BoxLayout containing other layouts

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

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.

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.

kivy - custom form in a scrollview

Say I have a custom slider form like in this template here:
<SliderForm#BoxLayout>:
orientation: 'vertical'
padding: [0, '5 dp']
size_hint_y: None
form_title: "Slider"
slider_value: slider_widget.value
slider_max: slider_widget.max
Label:
text: root.form_title
bold: True
text_size: (self.width - 20, self.height)
halign: 'left'
BoxLayout:
Slider:
id: slider_widget
min: 1
max: 5
value: 1
step: 1
padding: '15 dp'
Label:
text: str(int(root.slider_value))
size_hint_x: None
width: self.height
Now I need to have multiple of these and was thinking of placing them inside a scrollview, but I can't figure how to.
My attempt is:
<MyScreen>:
BoxLayout:
orientation: "vertical"
ScrollView:
BoxLayout:
orientation: "vertical"
size_hint_y: None
minimum_height: self.setter('height')
SliderForm:
form_title: "Amount"
SliderForm:
form_title: "Other"
This way, the forms are never well positioned... Either they are missing or stacked on top of each other.
BoxLayout:
minimum_height: self.setter('height')
This isn't right. First, BoxLayout doesn't have a minimum_height property, so you're just creating a new one that doesn't do anything. Second, self.setter('height') returns a function, but you don't call it or anything so that also does nothing.
It seems like maybe you really mean:
GridLayout:
size_hint_y: None
height: self.minimum_height
GridLayout does have a minimum_height property that sums the height of its children, so this will make its size adjust dynamically. This is a normal way to work with a ScrollView.
However, this isn't the only thing wrong. Your SliderForm includes size_hint_y: None but doesn't actually set its height anywhere, so it will have the default of 100 pixels. Maybe you meant to set some fixed value, e.g. height: 600?
Here's two more options on making layouts ScrollView friendly.
class Scrollable_GridLayout(GridLayout):
def __init__(self, **kwargs):
super(Scrollable_GridLayout, self).__init__(**kwargs)
self.size_hint_y: None
self.bind(minimum_height = self.setter('height'))
Option one (above), set a class that inherits from the desired layout and set self.bind(minimum_height = self.setter('height')), option two (bellow), set the height based off summed children heights.
<Scrolling_GridLayout#GridLayout>:
size_hint_y: None
height: sum([c.height for c in self.children])

Categories

Resources