I want to center a single line of text in Kivy text input.
I'm going to use padding
widget.padding = [ (self.textinput.width - width of line) / 2, 20, 0, 0]
but i can't find the width of the line. How can I calculate or access the width of the line?
There is an internal TextInput._get_text_width method you can use to calculate proper padding:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string('''
<MyWidget>:
TextInput:
multiline: False
on_text: root.update_padding(args[0])
padding_x: self.width/2 # initial padding
''')
class MyWidget(FloatLayout):
def update_padding(self, text_input, *args):
text_width = text_input._get_text_width(
text_input.text,
text_input.tab_width,
text_input._label_cached
)
text_input.padding_x = (text_input.width - text_width)/2
class MyApp(App):
def build(self):
return MyWidget()
if __name__ == '__main__':
MyApp().run()
The solution above almost worked for me. Sometimes the padding wouldn't be updated correctly. Here is my slight change, setting text_width as a NumericProperty:
In Kivy:
<CenteredTextInput#TextInput>:
multiline: False
on_text: root.update_padding()
padding_x: (self.width - self.text_width) / 2
In Python:
class CenteredTextInput(TextInput):
'''
A centered TextInput.
'''
text_width = NumericProperty()
'''The text width
'''
def update_padding(self, *args):
'''
Update the padding so the text is centered
'''
self.text_width = self._get_text_width(
self.text,
self.tab_width,
self._label_cached
)
You could make a textinput behind a button, and make the button visualize as the text input.
When pushing the button, put the focus to the textinput, and update the buttons text.
I have made an example here.
from kivy.uix.textinput import TextInput
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.clock import Clock
from kivy.app import App
from kivy import require
require('1.9.1')
class MyWidget(BoxLayout):
def __init__(self,**kwargs):
super(MyWidget,self).__init__(**kwargs)
self.orientation = "vertical"
self.cur = False
self.textinput = TextInput(text='',halign="center",multiline=False)
self.textinput.bind(text=self.on_text)
self.button = Button(background_normal="",background_color=[0,0,0.1,1],font_size="40sp")
self.button.bind(on_release=self.button_click)
self.my_float_layout = FloatLayout()
self.my_float_layout.add_widget(self.textinput)
self.my_float_layout.add_widget(self.button)
self.add_widget(Label(text="type text below",font_size="40sp"))
self.add_widget(self.my_float_layout)
Clock.schedule_interval(self.cursor, 0.5)
def cursor(self,dt): # function to visualize a cursor
if self.textinput.focus:
cur_pos = self.textinput.cursor[0]
if not self.cur:
self.button.text = self.textinput.text[:cur_pos] + "|" + self.textinput.text[cur_pos:]
self.cur = True
else:
self.button.text = self.textinput.text[:cur_pos] + " " + self.textinput.text[cur_pos:]
self.cur = False
elif self.cur:
self.button.text = self.textinput.text + " "
self.cur = False
def on_text(self, *args): # function to set the button text
self.button.text = self.textinput.text
def button_click(self,*args): # function to focus the input
self.textinput.focus = True
class MyApp(App):
def build(self):
return MyWidget()
if __name__ == "__main__":
MyApp().run()
Related
I want to create a list of coloured labels. The thing is that I could do it with the kv file, but I need to do it through the build() method. So I tried replicate what I have done, but it does not work. And I can't understand why.
This is what I've coded
from kivy.app import App
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.graphics import *
class RL(RelativeLayout): # Creates the background colour for each label
def __init__(self, **kwargs):
super().__init__(**kwargs)
with self.canvas:
Color(.7, 0, .5, 1)
Rectangle(size_hint=self.size)
class MainMenu(BoxLayout):
N_LBLS = 8
labels_text = []
RL_list = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
button = Button(text='do something')
button.bind(on_release=self.change_text)
box = BoxLayout(orientation='vertical', padding= 10, spacing = 15)
for i in range(0, self.N_LBLS):
self.RL_list.append(RL())
self.labels_text.append(Label(text=f'{i}º label', size_hint=self.size))
self.RL_list[i].add_widget(self.labels_text[i])
box.add_widget(self.RL_list[i])
self.add_widget(button)
self.add_widget(box)
def change_text(self, instance):
for lbl in self.labels_text:
if lbl.text[0] == '5':
lbl.text = 'Text changed'
class MainApp(App):
def build(self):
return MainMenu()
if __name__ == '__main__':
MainApp().run()
It's supposed to make a button to the left, and a list of 8 coloured labels to the right.
The problem is that you are setting size_hint=self.size in each Label. The self.size is the size of the MainMenu, which is [100,100] when that code is executed. Note that size_hint is a multiplier that is applied to the parents size to calculate the widgets size. So a size_hint of [100,100] makes each Label 100 times bigger than the MainMenu. So your code is working, but the Labels are so large that the text is off the screen. Start by just removing size_hint=self.size.
And, to set a background color on a Label, you can just use the canvas of that Label, rather than some container. Here is a version of your code that does that:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
class ColorLabel(Label):
pass
Builder.load_string('''
<ColorLabel>:
bg_color: [.7, 0, .5, 1]
canvas.before:
Color:
rgba: self.bg_color
Rectangle:
pos: self.pos
size: self.size
''')
class MainMenu(BoxLayout):
N_LBLS = 8
labels_text = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
button = Button(text='do something')
button.bind(on_release=self.change_text)
box = BoxLayout(orientation='vertical', padding=10, spacing=15)
for i in range(0, self.N_LBLS):
self.labels_text.append(ColorLabel(text=f'{i}º label'))
box.add_widget(self.labels_text[i])
self.add_widget(button)
self.add_widget(box)
def change_text(self, instance):
for lbl in self.labels_text:
if lbl.text[0] == '5':
lbl.text = 'Text changed'
lbl.bg_color = [0, 1, 0, 1]
class MainApp(App):
def build(self):
return MainMenu()
if __name__ == '__main__':
MainApp().run()
I'm writing a data scraper for my school that displays information live in an app form. I'm trying to create buttons in a for loop, use lambda to bind each button to a function and a value to use when calling (in this case, to print their value for testing), add each button (as well as a couple other widgets) to a layout and add this layout to the screen.
For some reason, however, although I should be saving the value of "a" in the lambda function: btn.bind(on_press=lambda btn, a=a: self.getlink(a)), lambda cannot seem to "remember" more than around 3 iterations back (this loop goes for about 20 cycles). The last three buttons (most recently generated) on the screen return correct values, but the further you go up on the screen (a kivy RecycleView layout), the less accurate the value. When I press the first few buttons on the screen, literally nothing happens. As I go down the screen pressing buttons, I occasionally get a random number spit out, sometimes a significantly different number (spits out 2 when it should be 11 for example).
There are no errors being thrown back when I run it, and everything else works just fine. I just cannot seem to get these buttons to bind to the proper value. I even tried using a counter in the loop that adds one for every iteration and using that instead of a, but it did the same thing.
Any help would be much appreciated, thank you!
Here is the relevant code:
from kivy.app import App
from kivy.base import runTouchApp
from kivy.core.window import Window
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.recycleview import RecycleView
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.widget import Widget
from kivy.uix.image import AsyncImage
from kivy.uix.button import Button
from kivy.lang import Builder
class ScreenThree(RecycleView, Screen):
def __init__(self, **kwargs):
super(ScreenThree, self).__init__(**kwargs)
self.data = [{'padding': '7sp',
'size_hint': (6, None),
'allow_stretch': True,
'height': ((Window.height / 4) * (22))}]
def screenlength(self):
return Window.height * (20 / 4)
def screenSize(self, modifier=1):
window_s = Window.size
if window_s[1] >= window_s[0]:
return str((window_s[0] * 0.007) * modifier) + 'sp'
else:
return str((window_s[1] * 0.007) * modifier) + 'sp'
class ScreenFour(TabbedPanel, Screen):
def __init__(self, **kwargs):
super(ScreenFour, self).__init__(**kwargs)
self.default_tab_text = 'Events'
self.tab_width = Window.width / 2.7
self.tab_height = Window.height / 25
self.default_tab_content = ScreenThree()
self.background_color = (0, 0, 0, 1)
def screenSize(self, modifier=1):
window_s = Window.size
if window_s[1] >= window_s[0]:
return str((window_s[0] * 0.007) * modifier) + 'sp'
else:
return str((window_s[1] * 0.007) * modifier) + 'sp'
class AddWidget(GridLayout, Widget):
def __init__(self, **kwargs):
super(AddWidget, self).__init__(**kwargs)
for a in range(20):
asyncimage1 = AsyncImage(source='https://images-na.ssl-images-amazon.com/images/I/51zLZbEVSTL._AC_SX679_.jpg',
allow_stretch=True,
size_hint_y=None,
size_hint_x=5,
height=Window.height / 4)
self.add_widget(asyncimage1)
label = Label(text='title goes here',
bold=True,
text_size=self.size,
size_hint=(1, 1),
valign='top',
padding=(0, '11sp'),
font_size=self.screenSize(1.2))
label.bind(size=label.setter('text_size'))
layout2 = GridLayout(rows=3)
label2 = Label(text='date goes here',
text_size=self.size,
size_hint=(1, 1),
valign='top',
padding=(0, '1sp'),
font_size=self.screenSize(0.9))
label2.bind(size=label2.setter('text_size'))
layout2.add_widget(label)
layout2.add_widget(label2)
btn = Button(text='Visit Website',
font_size=self.screenSize(1.4),
size_hint=(1, 1),
id = str(a),
padding=("10sp", "10sp"))
btn.bind(on_press=lambda btn, a=a: self.getlink(a))
layout2.add_widget(btn)
self.add_widget(layout2)
def getlink(self, position):
print(position)
def screenSize(self, modifier=1):
window_s = Window.size
if window_s[1] >= window_s[0]:
return str((window_s[0] * 0.007) * modifier) + 'sp'
else:
return str((window_s[1] * 0.007) * modifier) + 'sp'
class ScreenManagement(ScreenManager):
pass
pres1 = runTouchApp(Builder.load_file('screenskv2.kv'))
class MyApp(App):
def build(self):
return ScreenManagement
MyApp().run()
And the relevant code in the kv file:
#:import Window kivy.core.window.Window
ScreenManagement:
ScreenThree:
<AddWidget>:
orientation: 'horizontal'
cols:2
spacing: '10sp'
col_default_width: (Window.width/2)-50
<ScreenThree>:
name: 'screen_three'
rv_layout: 'layout'
viewclass: 'AddWidget'
RecycleBoxLayout:
id: 'layout'
pos_hint_x: 0
size_hint: (None, None)
height: self.minimum_height
orientation: 'vertical'
I need to add a series of labels to my screen layout - the number of the labels are defined at runtime - and update the label texts with runtime data.
Hence, I need to add the labels programatically.
I have created a little test programme to investigate the behaviour:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.properties import StringProperty
import random
Builder.load_string('''
<YourWidget>:
BoxLayout:
size: root.size
Button:
id: button1
text: "Change text"
on_release: root.change_text()
# Label:
# id: label1
# text: root.random_number
''')
class YourWidget(Widget):
random_number = StringProperty()
def __init__(self, **kwargs):
super(YourWidget, self).__init__(**kwargs)
label = Label(
id= 'label1',
text = root.random_number
)
self.add_widget(label)
self.random_number = str(random.randint(1, 100))
def change_text(self):
self.random_number = str(random.randint(1, 100))
class updatetest(App):
def build(self):
return YourWidget()
#if __name__ == '__main__':
if (True):
updatetest().run()
If I uncomment the three lines relating to Label in the Builder.load_string, and remove the label = & self.add_widget lines from the python code, then the label gets updated as expected.
However, I have been unsuccessful in adding the label with the code as it stands.
What am I missing?
All assistance gratefully received!
I have a following problem: I need to write an app, where I will show proper answers for every question. I wrote with kivy some code and I'm struggling with one thing. I created a page. There is a button for showing answers, but after one press I only see a part of my answers and I can't scroll. But, when I press a button second time, everything is good. Could you tell me why is that? How to repair it? I would like to see all answers after pressing a button once and be able to scroll.
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.scrollview import ScrollView
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window
from kivy.config import Config
Config.set('graphics', 'resizable', True)
import os
import sys
class MyApp(App):
def build(self):
self.screen_manager = ScreenManager()
self.answers = Answers()
screen = Screen(name = "Answers")
screen.add_widget(self.answers)
self.screen_manager.add_widget(screen)
return self.screen_manager
class Answers(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.rows = 3
self.label = Label(text = "Answers: ", font_size = 40)
self.add_widget(self.label)
self.button = Button(text="Show answers")
self.button.bind(on_press=self.showanswers)
self.add_widget(self.button)
self.scroll = ScrollableLabel(height = Window.size[1]*0.75, size_hint_y = None)
self.add_widget(self.scroll)
def showanswers(self, instance):
f = open("text.txt", "r")
lines = f.readlines()
ScrollableLabel.update(self.scroll, lines)
myapp.screen_manager.current = "Answers"
class ScrollableLabel(ScrollView):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.layout = GridLayout(cols = 1, size_hint_y = None)
self.add_widget(self.layout)
self.lines = Label(size_hint_x = 1, size_hint_y = None, text_size = (self.width, None))
self.scroll_to_point = Label()
self.scroll_to_point.bind(texture_size=self.scroll_to_point.setter('size'))
self.layout.add_widget(self.lines)
self.layout.add_widget(self.scroll_to_point)
def update(self, lines):
self.lines.text = '\n'
for i in range(len(lines)):
self.lines.text += '\n ' +str(i+1) + ". " + lines[i]
self.layout.height = self.lines.texture_size[1]
self.lines.height = self.lines.texture_size[1]
self.lines.text_size = (self.lines.width*0.75, None)
self.scroll_to(self.scroll_to_point)
f = open("text.txt", 'a+')
for i in range(30):
f.write("Important text \n")
f.close()
myapp = MyApp()
myapp.run()
I think your widget heights are not updating correctly, and to correct that requires doing some binding. Since the 'kv' language automatically does bindings, I have provided an answer that uses 'kv':
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.scrollview import ScrollView
from kivy.core.window import Window
from kivy.config import Config
Config.set('graphics', 'resizable', True)
class MyApp(App):
def build(self):
self.screen_manager = ScreenManager()
self.answers = Answers()
screen = Screen(name = "Answers")
screen.add_widget(self.answers)
self.screen_manager.add_widget(screen)
return self.screen_manager
class Answers(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.rows = 3
self.label = Label(text = "Answers: ", font_size = 40)
self.add_widget(self.label)
self.button = Button(text="Show answers")
self.button.bind(on_press=self.showanswers)
self.add_widget(self.button)
self.scroll = ScrollableLabel(height = Window.size[1]*0.75, size_hint_y = None)
self.add_widget(self.scroll)
def showanswers(self, instance):
f = open("text.txt", "r")
lines = f.readlines()
self.scroll.update(lines)
myapp.screen_manager.current = "Answers"
class ScrollableLabel(ScrollView):
def update(self, lines):
self.ids.lines.text = '\n'
for i in range(len(lines)):
self.ids.lines.text += '\n ' +str(i+1) + ". " + lines[i]
Builder.load_string('''
<ScrollableLabel>:
size_hint_y: None
GridLayout:
cols: 1
size_hint_y: None
height: self.minimum_height # adjust height to handle the Label
Label:
id: lines
size_hint_y: None
height: self.texture_size[1] # set height based on text
''')
f = open("text.txt", 'a+')
for i in range(30):
f.write("Important text \n")
f.close()
myapp = MyApp()
myapp.run()
I believe the two instances of size_hint_y: None and the corresponding height rules are the key.
Note that I also changed:
ScrollableLabel.update(self.scroll, lines)
to:
self.scroll.update(lines)
New to kivy, and OOP.
I'm trying to update a label in kivy with data I pull from a temp sensor. The code that pulls in the sensor data is in labeltempmod. I created a function getTheTemp() that is called every second. In the function I try to assign the text of the label via Label(text=(format(thetemp)), font_size=80). The program ignores this. What am I doing wrong here?
#This is a test to see if I can write the temp to label
import labeltempmod
import kivy
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.label import Label
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.textinput import TextInput
from kivy.uix.boxlayout import BoxLayout
def getTheTemp(dt):
thetemp = labeltempmod.readtemp()
Label(text=(format(thetemp)), font_size=80)
print thetemp
class LabelWidget(BoxLayout):
pass
class labeltestApp(App):
def build(self):
# call get_temp 0.5 seconds
Clock.schedule_interval(getTheTemp, 1)
return LabelWidget()
if __name__ == "__main__":
labeltestApp().run()
Here is the kivy language file:
<LabelWidget>:
orientation: 'vertical'
TextInput:
id: my_textinput
font_size: 80
size_hint_y: None
height: 100
text: 'default'
FloatLayout:
Label:
id: TempLabel
font_size: 150
text: 'Temp Test'
Thanks.
Sorry but you never update something You are just creating another label
Try this:
class LabelWidget(BoxLayout):
def __init__(self, **kwargs):
super(LabelWidget, self).__init__(**kwargs)
Clock.schedule_interval(self.getTheTemp, 1)
def getTheTemp(self, dt):
thetemp = labeltempmod.readtemp()
self.ids.TempLabel.text = thetemp
print thetemp
class labeltestApp(App):
def build(self):
return LabelWidget()
if __name__ == "__main__":
labeltestApp().run()
Update : for your last request, I think the best way to do that is:
...
class LabelWidget(BoxLayout):
def __init__(self, **kwargs):
super(LabelWidget, self).__init__(**kwargs)
self.Thetemp = None
Clock.schedule_interval(self.getTheTemp, 1)
def getTheTemp(self, dt):
if self.Thetemp is None:
self.thetemp = labeltempmod.readtemp()
else:
self.thetemp = labeltempmod.readtemp(self.theTemp)
self.ids.TempLabel.text = str(self.thetemp)