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])
Related
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.
I am trying to figure out a way to keep the three widgets I have in my main.kv file header setup and just move the two outside ones further to the edges of the screen. This is like a top banner that is displayed on every screen and houses the time, an 'home button' and a 'settings button'. I have an absolute dog's breakfast of a main.kv layout and I don't really know how to change it so that its fully functional and keeps the widgets visible. I will include the framework for the section I am talking about.
main.kv
GridLayout:
cols: 1
FloatLayout:
GridLayout:
canvas:
Color:
rgb: utils.get_color_from_hex("#000523")
Rectangle:
size: self.size
pos: self.pos
rows: 1
pos_hint: {"top": 1, "left": 1}
size_hint: 1, .07
GridLayout:
rows: 1
canvas:
Color:
rgb: utils.get_color_from_hex("#000523")
Rectangle:
size: self.size
pos: self.pos
pos_hint: {"top": .93, "center_x": .5}
size_hint: 1, .03
ImageButton:
id: home_button
source: "icons/home.png"
opacity: 1 if self.state == 'normal' else .5
on_release:
app.change_screen("home_screen")
app.clear_results()
Label:
id: utc_time
markup: True
font_size: '20sp'
text: ""
ImageButton:
id: settings_button
source: "icons/cog.png"
opacity: 1 if self.state == 'normal' else .5
on_release:
app.change_screen("settings_screen")
I have found that bringing my three widgets back one tab (not under GridLayout but as a child of FloatLayout) allows me to reposition them out more to where I'd like. This doesn't work though because I am referencing them in my main.py and adding or removing these widgets with reference to current screen. The problem I keep getting is that it keeps wanting to add widgets on top of widgets. I will include that function as well.
main.py
def home_setting_widgets(self, screen_name): # this is going to display the home and setting widgets once the app has already been opened
home_button = self.root.ids["home_button"]
utc_time = self.root.ids["utc_time"]
settings_button = self.root.ids["settings_button"]
grid_layout = self.root.children[0].children[2]
if screen_name == "login_screen" or screen_name == "signup_screen" or screen_name == "welcome_screen":
grid_layout.remove_widget(home_button) # remove widgets if on the 3 screens listed
grid_layout.remove_widget(utc_time)
grid_layout.remove_widget(settings_button)
self.home_and_setting = False
else:
if self.home_and_setting == False:
grid_layout.add_widget(home_button) # add widgets
grid_layout.add_widget(utc_time)
grid_layout.add_widget(settings_button)
self.home_and_setting = True
I think the problem if I move them out of the one GridLayout is that they won't be referenced as a group but as individual widgets. I currently have them bunched and referenced in the grid_layout = self.root.children[0].children[2] line.
I have tried removing GridLayouts and their contents from main.kv, I have tried 'tabbing' the widgets to different spots, I have tried referencing the widgets in many different ways.
All I want to do is move the 'home button' and 'settings button' further apart slightly and it has me this stuck. If there is a way to move them without breaking everything, please tell me. Your help is much appreciated, thank you.
Try adding some spacing to the GridLayout that has the "Home", "Time", and "Settings"
GridLayout:
rows: 1
spacing: 1000
canvas:
I can't get the image from kivy docs page to upload, but the GridLayout kivy docs has the "spacing" info you are for.
I created a box layout in kivy with a few buttons and text input boxes. The boxes that I sized all get pushed to the bottom of the window with a large amount of extra padding on the top of them. How do I get rid of this padding at the top OR how do I get the window to fit to the layout exactly, without a large margin at the top?
BalanceGridLayout:
id: Balance
orientation: "vertical"
display: ChekingsEntry, SavingsEntry
padding: 10
spacing: 10
BoxLayout:
size_hint_y: None
height: "40dp"
Label:
text:"Chekcings"
TextInput:
id: ChekingsEntry
font_size: 18
multiline: False
BoxLayout:
size_hint_y: None
height: "40dp"
Button:
text: "Deposite"
size_hint_x: 15
on_press: Balance.Deposite(0, ChekingsEntry.text)
Button:
text: "Withdraw"
size_hint_x: 15
on_press: Balance.withdraw(SavingsEntry.text)
Button:
text: "Transfer"
size_hint_x: 15
on_press: Balance.TransferIn(ChekingsEntry.text)
For reference, I was following the tutorials by Derek Banas: https://www.youtube.com/watch?v=dxXsZKmD3Kk&t=698s
The problem happened when I added:
size_hint_y: None
height: "40dp"
to each of the BoxLayouts. Previously, the elements I had would fill the entire window, but they were extremely large.
The elements he uses (buttons, text input boxes, lists etc..) stay at the top of the screen, with the sizing method, unlike mine. However, there is still a large amount of padding at the bottom of his window (example around 11:35 in the video). Is it possible to resize the window or get rid of the padding at the top?
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()
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.