Alignment problems with custom widget using RecycleView in Kivy - python

So I have this chatting app like structure I have made using Kivy's RecycleView to instantiate a custom widget (named 'Row') and I pass it values however I wish.
It works fine if the Row custom widget only contains a Label child, the moment I add an Image and a Button (I'll explain why in a sec), there are weird spacing and positioning problems which shouldn't exist because I am using a BoxLayout and have tried proper size_hint: and height: techniques to get them to look right.
Relevant KV code:
<Row#BoxLayout>:
orientation: 'vertical'
text: ''
source: 'res/icon.png'
buttontext: ''
Label:
markup: True
text_size: self.size
text: root.text
Image:
size: self.size
source: root.source
allow_stretch: True
Button:
text: root.buttontext #contains a link
on_press: app.root.stuff(self.text) #opens that link
# Relevant Screen where I am using the RecycleView
<ChatBotScreen>
BoxLayout:
ti_userinput: ti_userinput
orientation: 'vertical'
ScrollView:
size_hint_y: 85
RecycleView:
id: chatbase_list
viewclass: 'Row'
data: []
RecycleBoxLayout:
padding: "15dp", "45dp", "15dp", "15dp"
default_size: None, dp(25)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
TextInput:
size_hint_y: None
height: "40dp"
id: ti_userinput
multiline: False
on_text_validate: root.on_user_enter_text()
Since my custom widget Row is extending BoxLayout and I am using a vertical orientation, why aren't the child widgets (Label, Image and Button) overlapping?
This is the class & function via which I am calling to pass the data inside the RecycleView
class ChatBotScreen(Screen):
nickname = StringProperty()
rvList = []
def display_bot_output(self, botOutput):
# Initialize an empty dictionary
tempDict = {}
textToDisplay = '' + botOutput
# Update the dictionary with properties:values and then pass it as a list to RecycleView's data
tempDict.update({'text':textToDisplay, 'buttontext':'https://google.com'})
self.rvList.append(tempDict)
self.ids.chatbase_list.data = self.rvList
What I want to be visible on screen is:
The text I send through the textToDisplay variable in the Label
Underneath the Label, Image should be displayed the source of which I can pass, if no source passed, no image displayed
Underneath the Image, a button with a link should be displayed which I pass through buttontext, if not passed, no button should be displayed.
I am able to render all that but the spacing and all is all messed up.
Screenshot below:
Here I first sent data with only Label text info and Image, thus the Button text is empty (it still shows up), then I send another data (2nd Row widget) with Label text info, Image as well as a Button text (which is google link).
Any help is greatly appreciated. Thanks!

<ChatBotScreen>
BoxLayout:
ti_userinput: ti_userinput
orientation: 'vertical'
ScrollView:
# to have the screen get bigger not lliek what you have
size: root.size
RecycleView:
id: chatbase_list
viewclass: 'Row'
data: []
RecycleBoxLayout:
padding: "15dp", "45dp", "15dp", "15dp"
default_size: None, dp(25)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
# add this so you can scroll
row_default_height: 60
orientation: 'vertical'
TextInput:
size_hint_y: None
height: "40dp"
id: ti_userinput
multiline: False
on_text_validate: root.on_user_enter_text()

Related

Problem with Kivy - Scrollview overlapping with Box Layout above it

I'm clicking the button in the fixed menu at the top even though there is a Button in the vertical Scrollview, it doesn't effect the fixed menu.
I'm trying to click at the button in the fixed menu at the top but the actions are being sent to the horizontal Scrollview below.
I'm new with kivy and even programming and I'm creating an app with Python 3.7, and Kivy(1.11.1) that kinda looks like Netflix's Page layout, in which there is a menu fixed at the top and below it there is a vertical Scrollview with some horizontal scrollviews inside (just like the Netlix's catalogues, eg.: My List, Continue Watching, Series, etc). The problem is that when I scroll down the vertical ScrollView and one of the horizontal scrollview gets behind the fixed menu at the top, the events are sent to this horizontal scrollview and not to the fixed menu that is be above the Scrollview content. If I scroll the vertical Scrollview down and one of the horizontal scrollviews gets "behind" (in quotes because I imagine they aren't supposed to collide with each other) the menu at the top, if I click in the menu the event is being sent to the horizontal scrollview.
from kivy.app import App
from kivy.lang.builder import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.scrollview import ScrollView
Builder.load_string(
'''
<ScrollApp>:
orientation: 'vertical'
BoxLayout:
size_hint: 1, 0.12
Button:
text: 'Fixed Menu'
on_press: print('This button stops working if there is a horizontal scrollview "behind"')
ScrollView:
bar_width: 10
scroll_type: ['bars', 'content']
BoxLayout:
orientation: 'vertical'
size_hint: 1, None
height: self.minimum_height
padding: 22, 0, 22, 50
spacing: 50
canvas:
Color:
rgba: .15, .15, .15, .9
Rectangle:
size: self.size
pos: self.pos
Button:
size_hint: None, None
width: 100
height: 100
on_press: print('This button does not overlap the menu above')
# "ScrollViews containers"
Custom
Custom
Custom
Custom
Custom
BoxLayout:
size_hint: 1, 0.12
Button:
on_press: print("This menu at the bottom is not affected by the problem that occurs with the top one")
<Custom#BoxLayout>:
orientation: 'vertical'
size_hint: 1, None
height: self.minimum_height
Label:
size_hint: None, None
size: self.texture_size
id: label
font_size: 20
text: 'Teste'
ScrollView:
do_scroll: True, True
size_hint: 1, None
height: 150
GridLayout:
id: grid
size_hint: None, 1.01
width: self.minimum_width
spacing: 5
cols: 3
Button:
size_hint: None, None
size: 400, 150
on_press: print('ScrollView button pressed')
Button:
size_hint: None, None
size: 400, 150
on_press: print('ScrollView button pressed')
Button:
size_hint: None, None
size: 400, 150
on_press: print('ScrollView button pressed')
''' )
class ScrollApp(BoxLayout):
pass
class Test(App):
def build(self):
return ScrollApp()
Test().run()
You do manage to find obscure situations in kivy :-).
I believe the problem here is the order of touch event processing. The touch events are normally processed by the top level widgets, and they dispatch the event to their children, all the way down the Widget tree. In any Widget with multiple children, the touch event is dispatched to children in the reverse order that they have been add to their parent. And in kv, child Widgets are added in order as they appear (top to bottom). So, in your case, the ScrollApp has three children, and the touch events will be dispatched to its children starting with the BoxLayout that contains the bottom menu, then to the ScrollView, and lastly, to the BoxLayout that contains the top menu. So the ScrollView gets the touch event before the top menu, and dispatches the touch to its children (including the Custom instances). So the Buttons in the Custom see the touch event before the top menu sees it, and they claim it as their own. The obvious way to fix it would be to change the order of Widgets that appear in the ScrollApp, but since ScrollApp is a BoxLayout, that would dramatically change how your App looks. A different fix is to use a Layout that doesn't depend on the order of its children. Something like a FloatLayout has that characteristic. So, here is a modified version of your code, that makes ScrollApp extend FloatLayout instead of BoxLayout:
from kivy.app import App
from kivy.lang.builder import Builder
from kivy.uix.floatlayout import FloatLayout
Builder.load_string(
'''
<ScrollApp>:
ScrollView:
size_hint: 1.0, 0.76
pos_hint: {'center_y':0.5}
bar_width: 10
scroll_type: ['bars', 'content']
BoxLayout:
orientation: 'vertical'
size_hint: 1, None
height: self.minimum_height
padding: 22, 0, 22, 50
spacing: 50
canvas:
Color:
rgba: .15, .15, .15, .9
Rectangle:
size: self.size
pos: self.pos
Button:
size_hint: None, None
width: 100
height: 100
on_press: print('This button does not overlap the menu above')
# "ScrollViews containers"
Custom
Custom
Custom
Custom
Custom
BoxLayout:
size_hint: 1, 0.12
pos_hint: {'y':0}
Button:
on_press: print("This menu at the bottom is not affected by the problem that occurs with the top one")
BoxLayout:
size_hint: 1, 0.12
pos_hint: {'top':1.0}
Button:
text: 'Fixed Menu'
on_press: print('This button stops working if there is a horizontal scrollview "behind"')
<Custom#BoxLayout>:
orientation: 'vertical'
size_hint: 1, None
height: self.minimum_height
Label:
size_hint: None, None
size: self.texture_size
id: label
font_size: 20
text: 'Teste'
ScrollView:
do_scroll: True, True
size_hint: 1, None
height: 150
GridLayout:
id: grid
size_hint: None, 1.01
width: self.minimum_width
spacing: 5
cols: 3
Button:
text: 'button 1'
size_hint: None, None
size: 400, 150
on_press: print('ScrollView button pressed')
Button:
text: 'button 2'
size_hint: None, None
size: 400, 150
on_press: print('ScrollView button pressed')
Button:
text: 'button 3'
size_hint: None, None
size: 400, 150
on_press: print('ScrollView button pressed')
''' )
class ScrollApp(FloatLayout):
pass
class Test(App):
def build(self):
return ScrollApp()
Test().run()
So, with this child order in ScrollApp, the top and bottom menus get their shot at the touch events before the ScrollView.
I actually think your code should work without this change, so there may be something else happening that I couldn't find.

Python kivy - how to reduce height of TextInput

I am using kivy to make a very simple gui for an application. Nothing complex, very simple layout.
Nevertheless I am having a hard time with TextInputs...They always display with full height and I can't manage to make them adjust to a "reasonable" text-height like height.
I am using the kv files style since I find it cleaner and easier to integrate it in an already existing app...I would like to reduce as much as possible the gui-python code of the app.
Here is what I got for the TextInput (useless to add other parts of the gui).
Python code
# textInput.py
from kivy import require
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang.builder import Builder
Builder.load_file('path/to/kv/file/textInput.kv')
require('1.10.0')
class MainScreen(BoxLayout):
pass
class Test(App):
def build(self):
self.title = 'Testing textInput'
return MainScreen()
if __name__ == '__main__':
Test().run()
KV code
# textInput.kv
<MainScreen>
orientation: 'vertical'
# Third section title
Label:
size_hint: (1, .1)
text: 'Setup Connection'
font_size: 25
# Third section Box
BoxLayout:
size_hint: (1, .2)
padding: [100, 0, 100, 0]
BoxLayout:
Label:
size_hint: (.2, 1)
text: 'Host'
TextInput:
height: self.minimum_height
multiline: False
text: 'localhost'
Label:
size_hint: (.2, 1)
text: ''
Label:
size_hint: (.2, 1)
text: 'Port'
TextInput:
size_hint: (.2, 1)
multiline: False
text: '502'
I have tried lot of stuff, in the code here I am trying both to use size_hint and height...but none works..
To set a height of a widget, first set the size_hint_y to None and then you can set the height to whatever you want.
TextInput:
size_hint: (.2, None)
height: 30
multiline: False
text: '502'
in addition to the answer size_hint_x and size_hint_y must be set to None respectively before the height and width attribute can be used i.e size_hint: (None, None) for less typing. If you want to set the width attribute, size_hint_x is set to None and vice-versa.

Field content not always visible in kivy Textinput

I am encountering some strange/unexpected behaviour when displaying content in a Textinput field (Initially used for new record input - subsequently for show record data). Data is available in a dictionary and is assigned to Textinput fields. For short data the characters will be hidden sometimes:
It seems that the cursor is at the end of the string and all characters are at the left side and 'hidden'(?) behind the label. After mouseclick in the field and arrow left, the characters appear.
What is wrong in my kv definitions? :
BoxLayout:
orientation: "horizontal"
height: 25
size_hint_y: None
Label:
id: _socialsource_label
size_hint: 0.35,1
text: "Social access token:"
size: self.texture_size
halign: 'left'
valign: 'middle'
font_size: 14
color: .3,.3,.3,1
TextInput:
id: socialsource
padding: 4,2,4,0
size_hint: 0.65,1
font_size: 14
multiline: False
readonly: False
text_size: self.width, None
halign: 'left'
foreground_color: .3,.3,.3,1
disabled_foreground_color: .3,.3,.3,1
background_normal: './images/tinputBGnormal.png'
background_active: './images/tinputBGactive.png'
background_disabled_normal: './images/tinputBGdisnormal.png'
background_disabled_active: './images/tinputBGdisactive.png'
In the python code the data is assigned by:
self.socialchnl.text = projdict[0]['PRJSocchnl:']
self.socialsource.text = projdict[0]['PRJSocsrc:']
self.socialprovdr.text = projdict[0]['PRJSocprv:']
You hint_text instead of text for your TextInputs. Something like
MyTextInput:
id: social
hint_text: some_social_name
After gaining more experience with kivy, I came up with the following solution:
Just set the cursor position at the same time you assign new data to the textinput:
self.socialsource.text = projdict[0]['PRJSocsrc:']
self.socialsource.cursor = (0, 0)

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])

Adding Scrollbar to Boxlayout in Kivy

I am trying to a Boxlayout with ScrollBar in Kivy, but I am not able to do it. Below excerpt of .kv file. I am dynamically adding controls to Boxlayout once the Boxlayout overflows controls are hidden and there is not Scrollbar. Please advise.
<ProcessorUI>: #GridLayout
cols: 1
rows: 3
Label:
text: 'Output'
size_hint_x: None
width: 100
size_hint_y: None
height: 20
ScrollView:
size_hint: (None, None)
size: (400, 400)
BoxLayout:
id: output
orientation: 'vertical'
GridLayout
cols: 2
TextInput:
id: input
multiline: True
size_hint_y: None
height: 40
Button:
id: btn_process
text: 'Process'
size_hint_x: None
width: 100
size_hint_y: None
height: 40
on_press: root.on_event()
ScrollView:
size_hint: (None, None)
size: (400, 400)
BoxLayout:
id: output
orientation: 'vertical'
The BoxLayout has no manually set height, so it always precisely fills the Scrollview, and never needs a scrollbar.
You probably actually want something like the following
ScrollView:
size_hint: (None, None)
size: (400, 400)
GridLayout:
id: output
cols: 1
size_hint_y: None
height: self.minimum_height
These last two lines set the gridlayout height to track the sum of heights of its children. You could also set the height to anything else.

Categories

Resources