I'm trying to put a textfield inside a toolbar with KivyMD, but the textfield is always directly against the right side of the window. I tried to adjust it with pos_hint, but none of the values I put for the x-coordinates moved it.
Screen:
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: 'assets/bg.png'
BoxLayout:
orientation: 'vertical'
MDToolbar:
id: toolbar
title: 'Placeholder'
elevation: 10
pos_hint: {"top": 1}
left_action_items: [['menu', lambda x: app.menu.open()]]
MDTextFieldRound:
id: textinput
icon_left: 'magnify'
pos_hint: {'x': 0.1 ,'center_y': 0.5, 'right': 0.3}
size_hint: {.6, .4}
text_color: 'white'
hint_text: 'Search'**strong text**
MDLabel:
text: 'Placeholder'
color: 'white'
halign: 'center'
Screenshot of toolbar
Any help is appreciated, thanks.
You can insert your widgets wherever you want, for this you can specify a position in the widget tree - add_widget(widget, index). I wrote a special get_widgets function for your solution, which goes through all the widgets in the class. In the example below, you can place the text field on the left, right, and center. Also examine the MDToolbar source code
from kivy.lang.builder import Builder
from kivy.metrics import dp
from kivy.clock import Clock
from kivy.weakproxy import WeakProxy
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.textfield import MDTextField
KV = """
MDScreen:
MDBoxLayout:
orientation: 'vertical'
MDToolbar:
id: toolbar
title: 'Placeholder'
pos_hint: {"top": 1}
MDLabel:
text: 'Placeholder'
color: 'white'
halign: 'center'
"""
class TestApp(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.search_content = None
def build(self):
return Builder.load_string(KV)
def on_start(self):
Clock.schedule_once(lambda dt: self.add_search('left'))
#staticmethod
def get_widgets(root: WeakProxy, class_: str):
"""
:param root: root widget
:param class_: which subclass are we looking for
:return:
"""
widgets = []
for widget in root.walk():
if widget.__class__.__name__ == class_:
widgets.append(widget)
print(widget)
return widgets
def add_search(self, side: str):
"""
:param side: left/right/center
:return:
"""
box = self.get_widgets(self.root.ids.toolbar, 'MDBoxLayout')[0] # get root `MDBoxLayout`
if side == 'left':
index = 2
elif side == 'right':
index = 1
else:
index = 0
self.root.ids.toolbar.ids.label_title.size_hint = None, None
self.root.ids.toolbar.ids.label_title.opacity = 0
# NOTE: If you remove this `MDLabel`, you cant change `self.theme_cls.material_style`
# box.remove_widget(self.root.ids.toolbar.ids.label_title)
boxlayout = MDBoxLayout(padding=[dp(18), dp(0), dp(18), dp(18)])
self.search_content = MDTextField(icon_left='magnify',
mode='round',
color_mode="custom",
line_color_normal=(1, 0, 1, 1),
line_color_focus=(0, 0, 1, 1),
text_color_focus=self.theme_cls.text_color,
text_color_normal=self.theme_cls.text_color[0:3] + [0.7],
hint_text='Empty field',
)
boxlayout.add_widget(self.search_content)
box.add_widget(boxlayout, index)
TestApp().run()
Related
I am trying to just put a piece of text centred in the top on the window but it ends up truncated in the bottom left.
I have the following python code:
# anchortest.py
from kivy.app import App
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.widget import Widget
class Root(Widget):
pass
class TextTitle(AnchorLayout):
pass
class AnchorTestApp(App):
def build(self):
return Root()
AnchorTestApp().run()
and it's associated kv file.
# anchortest.kv
<Root>
TextTitle:
<TextTitle>
anchor_x: 'center'
anchor_y: 'top'
Label:
id: score
text: 'Hello World'
font_name: 'Courier New'
font_size: 40
pad_x: 10
pad_y: 10
When I run the app I get this:
Don't use AnchorLayout for this case, just use BoxLayout with
pos_hint: {'top': 1}
size_hint_y: None
from kivy.app import App
from kivy.lang.builder import Builder
KV = """
Screen:
BoxLayout:
orientation: 'vertical'
BoxLayout:
pos_hint: {'top': 1}
size_hint_y: None
canvas.before:
Color:
rgba: [0.5, 0, 1, 1]
Rectangle:
pos: self.pos
size: self.size
Label:
text: 'Hello World'
font_size: sp(60)
BoxLayout:
Label:
text: 'Screen text'
"""
class TestApp(App):
def build(self):
return Builder.load_string(KV)
TestApp().run()
You also can use Label parametrs valign and halign:
KV = """
Screen:
Label:
text: 'Hello World'
font_size: sp(60)
text_size: self.size
halign: 'center'
valign: 'top'
"""
In order to control sizing, you must specify text_size to constrain
the text and/or bind size to texture_size to grow with the text.
If you really want to use AnchorLayout for some reason, it's done this way. Following the kivy documentation, in order for the AnchorLayout rules to apply, the Label must be set with
size_hint_y: None
height: self.height
The size_hint is a tuple of values used by layouts to manage the sizes
of their children. It indicates the size relative to the layout’s size
instead of an absolute size (in pixels/points/cm/etc)
KV = """
AnchorLayout:
anchor_x: 'center'
anchor_y: 'top'
Label:
text: 'Hello World'
font_size: sp(60)
size_hint_y: None
height: self.height
"""
i want to add items to NavigationDrawer but i get this "Unknown class " errors instead.
i tried defining a Class "DrawerClickableItem" but it wouldn't work instead i get errors. i have tried several options that seem to work but "DrawerClickableItem" always gives an error. the codes are below.
python code
from kivymd.app import MDApp
from kivy.core.window import Window
from kivy.lang.builder import Builder
from kivymd.uix.screenmanager import ScreenManager
from kivy.uix.boxlayout import BoxLayout
Window.size = [320, 600]
class ShopApp(MDApp):
def build(self):
global screen_manager
screen_manager = ScreenManager()
screen_manager.add_widget(Builder.load_file('home.kv'))
screen_manager.add_widget(Builder.load_file('Sign_up.kv'))
screen_manager.add_widget(Builder.load_file('login.kv'))
return screen_manager
ShopApp().run()
kv file
MDScreen:
name: 'home'
md_bg_color: 1, 1, 1, 1
background_normal: ''
MDNavigationLayout:
ScreenManager:
Screen:
BoxLayout:
orientation: 'vertical'
MDTopAppBar:
md_bg_color: [0, 0, 0, 1]
elevation: 2.5
title: "Home"
left_action_items: [['menu', lambda x: nav_drawer.set_state("open")]]
Widget:
MDNavigationDrawer:
id: nav_drawer
size_hint: .3, .9
radius: (0, 30, 30, 0)
md_bg_color: 0, 0, 0, 1
pos_hint: {'y': .1}
scrim_color: 0, 0, 0, 0
MDNavigationDrawerMenu:
MDNavigationDrawerHeader:
title: "Header title"
title_color: "#4a4939"
text: "Header text"
spacing: "4dp"
padding: "12dp", 0, 0, "56dp"
MDNavigationDrawerLabel:
text: "Mail"
DrawerClickableItem:
icon: "gmail"
right_text: "+99"
text_right_color: "#4a4939"
text: "Inbox"
DrawerClickableItem:
icon: "send"
text: "Outbox"
MDNavigationDrawerDivider:
MDNavigationDrawerLabel:
text: "Labels"
DrawerLabelItem:
icon: "information-outline"
the new class needs to be defined and inherit one that exists.
this can be done in a .kv file that you load with Builder.loadfile("filename.kv") or put this code in a .kv file that you already have. properties can be defined such as the size hint that I placed as an example.
<DrawerClickableItem#MDNavigationDrawer>:
size_hint_x: 5
I have two screens in my Kivy-based app, each with a RecycleView to display lists. Both RVs should update when I press a button (add_button_clicked()) on one screen. Currently, the first RV (AddRecipe screen) works mostly as intended. However, the RV on the ViewList screen does not update with new data.
I am new to Python and even newer to Kivy - what am I missing here?
.py:
#! python3
# GroceryList.py
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.spinner import Spinner
from kivy.properties import ListProperty
from kivy.properties import ObjectProperty
from kivy.uix.recycleview import RecycleView
selectedMeals = []
ingredients = []
class ViewList(Screen):
def updateList(self, portions, recipe):
ingredients.append((portions, recipe))
print(ingredients) # This proves updateList is getting called
##THE FOLLOW RV THINGS DON'T WORK:
self.ids.shoplist.data = [{'text': '%s (%s)' %(ingredients[i][0], ingredients[i][1])}
for i in range(len(ingredients))]
self.ids.shoplist.refresh_from_data()
##
class AddRecipe(Screen):
recipes = ListProperty()
recipes = {'Guacarole':5, 'Salsa':3, 'Chips':1} # Sample dict for demo
def add_one(self):
if self.addportions.text != '':
value = int(self.addportions.text)
self.addportions.text = str(value+1)
def subtract_one(self):
if self.addportions.text != '':
value = int(self.addportions.text)
self.addportions.text = str(value-1)
def add_button_clicked(self, recipe, portions):
if recipe != '':
selectedMeals.append((recipe, portions))
self.ids.mealslist.data = [{'text': '%s (%s)' %(selectedMeals[i][0], selectedMeals[i][1])}
for i in range(len(selectedMeals))]
self.ids.mealslist.refresh_from_data()
ViewList().updateList(portions, recipe)
def spinner_clicked(self, val):
self.addportions.text = str(self.recipes[val])
class WindowManager(ScreenManager):
pass
class GroceryList(App):
mealsRVdata = ListProperty()
shoppingRVdata = ListProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.sm = ScreenManager()
def build(self):
Builder.load_file("grocerylist.kv")
screens = [ViewList(name='viewlist'), AddRecipe(name='addrecipe')]
for screen in screens:
self.sm.add_widget(screen)
self.sm.current = "addrecipe"
return self.sm
if __name__ == '__main__':
GroceryList().run()
And .kv:
#:kivy 1.11.1
# GroceryList.kv
# Used by GroceryList.py
WindowManager:
AddRecipe:
ViewList:
<ViewList>:
name: "viewlist"
shoplist: shoplist
BoxLayout:
orientation: 'vertical'
BoxLayout:
size_hint: (1, 0.8)
RecycleView:
id: shoplist
data: app.shoppingRVdata
viewclass: 'RVLabel'
RecycleGridLayout:
cols: 1
size_hint: None, None
default_size: sp(200), sp(25)
height: self.minimum_height
width: self.minimum_width
BoxLayout:
size_hint: (1, 0.2)
Button:
text: "View shopping list"
on_release:
app.root.current = "viewlist"
Button:
text: "Add recipes"
on_release:
app.root.current = "addrecipe"
root.manager.transition.direction = "left"
<AddRecipe>:
name: "addrecipe"
addportions: addportions
mealslist: mealslist
BoxLayout:
orientation: 'vertical'
BoxLayout:
size_hint: (1, 0.08)
Label:
size_hint: (0.64, 1)
font_size: 20
text: "Select a meal to add"
Label:
size_hint: (0.36, 1)
font_size: 20
text: "Select portions"
BoxLayout:
size_hint: (1, 0.08)
Spinner:
id: add_spinner
size_hint: (0.64, 1)
text: ""
values: root.recipes.keys()
on_text:
root.spinner_clicked(add_spinner.text)
Button:
size_hint: (0.12, 1)
font_size: 36
text: "-"
on_release: root.subtract_one()
Label:
id: addportions
size_hint: (0.12, 1)
font_size: 24
text: ''
Button:
size_hint: (0.12, 1)
font_size: 36
text: "+"
on_release: root.add_one()
FloatLayout:
size_hint: (1, 0.08)
Button:
size_hint: (0.4, 1)
pos_hint: {"x": 0.3, "top": 1}
text: "Add to shopping list"
on_release:
root.add_button_clicked(add_spinner.text, addportions.text)
BoxLayout:
size_hint: (1, 0.68)
RecycleView:
id: mealslist
data: app.mealsRVdata
viewclass: 'RVLabel'
RecycleGridLayout:
cols: 1
size_hint: None, None
default_size: sp(200), sp(25)
height: self.minimum_height
width: self.minimum_width
BoxLayout:
size_hint: (1, 0.08)
Button:
text: "View shopping list"
on_release:
app.root.current = "viewlist"
root.manager.transition.direction = "right"
Button:
text: "Add recipes"
on_release:
app.root.current = "addrecipe"
<RVLabel#Label>:
text_size: self.size
I stumbled across the fix while browsing other questions - specify the RV more fully:
App.get_running_app().root.get_screen('viewlist').ids.shoplist.data = ....
I'm going to leave the question up, though, because now I want to know why this RV needs to be specified so completely, while the one in the AddRecipe screen doesn't.
I added the Kivy scatter example to a kivy screen. But it didn´t work properly. I have to reconfigure the center on the window. It's done in the kv-file. But I don´t know how to do it on a screen. See the code below.
python
class Picture(Scatter):
source = StringProperty(None)
class ScreenThree(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
root = BoxLayout() # instantiate BoxLayout
self.add_widget(root) # add BoxLayout to screen
curdir = dirname(__file__)
for filename in glob(join(curdir, 'images', '*')):
print(filename)
try:
picture = Picture(source=filename, rotation=randint(-30, 25))
root.add_widget(picture)
except Exception as e:
Logger.exception('Pictures: Unable to load <%s>' % filename)
def on_pause(self):
return True
class TestApp(App):
def build(self):
sm = ScreenManager()
sc1 = ScreenOne(name='screen1')
sc2 = ScreenTwo(name='screen2')
sc3 = ScreenThree(name='screen3')
sm.add_widget(sc1)
sm.add_widget(sc2)
sm.add_widget(sc3)
print (sm.screen_names)
return sm
if __name__ == '__main__':
TestApp().run()
kivy
#:kivy 1.0
#:import kivy kivy
#:import win kivy.core.window
FloatLayout:
canvas:
Color:
rgb: 1, 1, 1
Rectangle:
source: 'data/images/background.jpg'
size: root.size
BoxLayout:
padding: 10
spacing: 10
size_hint: 1, None
pos_hint: {'top': 1}
height: 44
Image:
size_hint: None, None
size: 24, 24
source: 'data/logo/kivy-icon-24.png'
Label:
height: 24
text_size: self.width, None
color: (1, 1, 1, .8)
text: 'Kivy %s - Pictures' % kivy.__version__
<Picture>:
on_size: root.center = win.Window.center <-- this is the question i guess
size: image.size
size_hint: None, None
Image:
id: image
source: root.source
# create initial image to be 400 pixels width
size: 400, 400 / self.image_ratio
# add shadow background
canvas.before:
Color:
rgba: 1,1,1,1
BorderImage:
source: 'shadow32.png'
border: (36,36,36,36)
size:(self.width+72, self.height+72)
pos: (-36,-36)
See the example here, Kivy Gallery of Examples » Basic Picture Viewer
In the kv file, on_size: root.center = win.Window.center will work fine, when you make the instantiated object, FloatLayout: as child of class rule, <ScreenThree>: plus some enhancements in Python script.
kv file
Replace #:kivy 1.0 with the Kivy version installed e.g. #:kivy 1.11.1
Add class rule, <ScreenThree>: and make FloatLayout: object as child of <ScreenThree>:
Snippets - kv file
<ScreenThree>:
FloatLayout:
canvas:
Color:
rgb: 1, 1, 1
Rectangle:
source: 'data/images/background.jpg'
size: root.size
BoxLayout:
padding: 10
spacing: 10
size_hint: 1, None
pos_hint: {'top': 1}
height: 44
Image:
size_hint: None, None
size: 24, 24
source: 'data/logo/kivy-icon-24.png'
Label:
height: 24
text_size: self.width, None
color: (1, 1, 1, .8)
text: 'Kivy %s - Pictures' % kivy.__version__
py file
Remove root = BoxLayout() and self.add_widget(root)
Replace root.add_widget(picture) with self.add_widget(picture)
Snippets - py file
class ScreenThree(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
curdir = dirname(__file__)
for filename in glob(join(curdir, 'images', '*')):
print(filename)
try:
picture = Picture(source=filename, rotation=randint(-30, 25))
self.add_widget(picture)
except Exception as e:
Logger.exception('Pictures: Unable to load <%s>' % filename)
Output
I use a button to retrieve the paths of some folders selected with the filechooser. When the button is clicked I would like to update the text of the label so that it dispays the selected paths.
In my Kv:
Button:
text:'OK'
on_press: root.selected(filechooser.path, filechooser.selection)
Label:
id: Lb_ListViewFolder
text: root.Lb_ListViewFolder_text
color: 0, 0, 0, 1
size_hint_x: .75
In .py:
class MyWidget(BoxLayout):
Lb_ListViewFolder_text = ObjectProperty("Text")
def selected(self, a, b):
global Lb_ListViewFolder_text
Lb_ListViewFolder_text = b
print(a,b)
This doesn't give me any error but the label text isn't changed.
I also tried self.ListViewFolder.text = b like recommended here but I get this error: MyWidget' object has no attribute 'Lb_ListViewFolder'.
I have seen this answer, but I have trouble applying in my code
I use python 3.6 and Kivy 1.9.2.dev0
In case, this is my entire code:
from kivy.properties import ObjectProperty
from kivy.core.window import Window
from kivy.event import EventDispatcher
from kivy.lang import Builder
root = Builder.load_string('''
<MyWidget>
id: BL_Main
orientation: "horizontal"
padding: 10
spacing: 10
BoxLayout:
id: BL_folder
orientation: "vertical"
Button:
id:ok
text:'OK'
background_color: 0,0,1,1
height: 5
size_hint: 0.1, 0.1
on_press: root.selected(filechooser.path, filechooser.selection)
BoxLayout:
orientation:"horizontal"
size_hint: None, 0.9
width:150
canvas.before:
Color:
rgb: .4,.5,.5
Rectangle:
pos: self.pos
size: self.size
## multiple select folder not possible with FileChooserListView
FileChooserIconView:
id: filechooser
pos:self.pos
multiselect: True
dirselect: True
Label:
id: Lb_ListViewFolder
text: root.Lb_ListViewFolder_text
color: 0, 0, 0, 1
size_hint_x: .75
''')
class MyWidget(BoxLayout):
Lb_ListViewFolder_text = ObjectProperty("Text")
def selected(self, a, b):
global Lb_ListViewFolder_text
Lb_ListViewFolder_text = b
print(a,b)
class MyApp(App):
def build(self):
Window.clearcolor = (1, 1, 1, 1)
return MyWidget()
MyApp().run()
You can use StringProperty here:
from kivy.app import App
from kivy.uix.filechooser import FileChooserListView
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.properties import StringProperty
Builder.load_string('''
<MyLayout>:
orientation: "vertical"
Label:
text: root.label_text
Button:
id:ok
text:'OK'
on_press: root.selected(filechooser.path, filechooser.selection)
FileChooserIconView:
id: filechooser
pos:self.pos
multiselect: True
dirselect: True
''')
class MyLayout(BoxLayout):
label_text = StringProperty("File name")
def selected(self, a, b):
self.label_text = b[0]
class MyApp(App):
def build(self):
return MyLayout()
MyApp().run()
Or you can change it directly in kvlang:
<MyLayout>:
orientation: "vertical"
Label:
id: dirlabel
text: root.label_text
Button:
id:ok
text:'OK'
on_press: dirlabel.text = filechooser.selection[0]
FileChooserIconView:
id: filechooser
pos:self.pos
multiselect: True
dirselect: True