I would like the user to be able to select multiple items from a list of check boxes, but for some reason checking a second box deselects the first and I cannot understand why. My code is below:
Python:
from kivymd.app import MDApp
from kivy.lang import Builder
from kivymd.uix.dialog import MDDialog
from kivymd.uix.button import MDFlatButton
from kivymd.uix.list import ILeftBodyTouch
from kivymd.uix.selectioncontrol import MDCheckbox
from kivymd.uix.boxlayout import MDBoxLayout
from kivy.properties import StringProperty
class LeftCheckbox(ILeftBodyTouch, MDCheckbox):
pass
class CreateWorkoutCustomDialog(MDBoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
lifts = ['Bench Press', 'Squat', 'Deadlift', 'Weighted Pull-ups', 'Rows', 'Shoulder Press']
for lift in lifts:
self.ids.box.add_widget(CreateWorkoutLiftRow(lift = lift))
class CreateWorkoutLiftRow(MDBoxLayout):
lift = StringProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.lift = kwargs['lift']
def set_icon(self, instance_check):
instance_check.active = True if instance_check.active == False else instance_check.active == False
class Main2App(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.lift_dialog = None
def build(self):
Builder.load_file('dialog.kv')
return Builder.load_file("scratch.kv")
def show_lift_dialog(self):
if not self.lift_dialog:
self.lift_dialog = MDDialog(
title="Create New Workout",
type="custom",
content_cls = CreateWorkoutCustomDialog(),
buttons=[
MDFlatButton(text="CANCEL"),
MDFlatButton(text="OK"),
],
)
self.lift_dialog.open()
Main2App().run()
scratch.kv:
MDScreen:
MDFlatButton:
text: "ALERT DIALOG"
pos_hint: {'center_x': .5, 'center_y': .5}
on_release: app.show_lift_dialog()
dialog.kv:
#:kivy 2.0.0
<CreateWorkoutCustomDialog>:
orientation: 'vertical'
size_hint_y: None
height: '400dp'
MDTextField:
hint_text: "Workout Title"
required: True
id: workout_title
ScrollView:
MDBoxLayout:
orientation: 'vertical'
size_hint_y: None
height: self.minimum_height
id: box
spacing: "12dp"
<CreateWorkoutLiftRow>:
orientation: 'horizontal'
size_hint_y: None
height: self.minimum_height
ItemConfirm:
text: root.lift
on_release: root.set_icon(check)
size_hint_x: .7
divider: None
LeftCheckbox:
id: check
group: "check"
active: True
CreateWorkoutSetsInput:
id: input
hint_text: "# sets"
size_hint_x: .3
<ItemConfirm#OneLineAvatarIconListItem>:
<CreateWorkoutSetsInput#MDTextField>:
I've reviewed several tutorials and tried my best to understand the documentation but I cannot see where the interaction between discrete check boxes is even happening. Any help would be greatly appreciated!
That is the result of using group with the CheckBox. See the documentation.
group: "check"
Just remove that line in the kv.
Related
I'm creating an mp3 player using kivy recycleview, the app has a lot of buttons in the playlist screen and whenever you click on a button, icon of that button change from 'play' to 'pause' and vice versa.
I would like to know how to make it be in a way that clicking another button changes all the other buttons icon to 'play' only that selected button should be with icon of 'pause'.
.py file:
from kivy.lang import Builder
from kivymd.app import MDApp
from kivy.core.window import Window
from kivy.properties import StringProperty, ObjectProperty
from kivymd.theming import ThemableBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.screen import MDScreen
from kivymd.uix.behaviors import RectangularRippleBehavior
from kivy.uix.behaviors import ButtonBehavior
from kivy.clock import Clock
Builder.load_file('playlist.kv')
KV = """
#:import FadeTransition kivy.uix.screenmanager.FadeTransition
ScreenManager:
transition: FadeTransition()
Playlist:
name: "playlist screen"
"""
class Playlist(ThemableBehavior, MDScreen):
rv = ObjectProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_once(self._finish_init)
def music_list(self):
return ['audio '+str(i) for i in range(1, 121)]
def _finish_init(self, dt):
self.set_list_musics()
def set_list_musics(self):
"""Builds a list of audios for the screen Playlist."""
print(self.ids)
def add_music_item(num, sura, secText, icon):
self.ids.rv.data.append(
{
"viewclass": "MusicListItem",
"number": num,
"text": sura,
"secondary_text": secText,
"icon": icon,
"callback": lambda x:x})
for i in range(len(self.music_list())):
music = self.music_list()
add_music_item(str(i+1), music[i], '00:00:00', 'play')
class MusicListItem(ThemableBehavior, RectangularRippleBehavior, ButtonBehavior, MDBoxLayout):
text = StringProperty()
secondary_text = StringProperty()
number = StringProperty()
icon = StringProperty()
def on_release(self, *args):
if self.icon == "play":
self.icon = "pause"
else:
self.icon = "play"
class Mp3Player(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def build(self):
self.theme_cls.primary_palette = "Purple"
self.theme_cls.theme_style = "Dark"
return Builder.load_string(KV)
if '__main__' == __name__:
Mp3Player().run()
.kv file:
#: import gch kivy.utils.get_color_from_hex
#: import StiffScrollEffect kivymd.effects.stiffscroll.StiffScrollEffect
<Playlist>
md_bg_color: gch("#5D1049")
MDGridLayout:
cols: 1
MDToolbar:
left_action_items: [["menu", lambda x: x]]
right_action_items: [["magnify", lambda x: x]]
elevation: 10
md_bg_color: 75/255, 6/255, 54/255, 1
title: 'Playlist'
pos_hint: {'top':1}
MDBoxLayout:
orientation: 'vertical'
RecycleView:
id: rv
effect_cls: 'ScrollEffect'
viewclass: 'MusicListItem'
RecycleBoxLayout:
padding: dp(10)
default_size: None, dp(60)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
<MusicListItem>
size_hint_y: None
padding: dp(14)
height: dp(60)
canvas:
Color:
rgba:
self.theme_cls.divider_color
Line:
points: (root.x+dp(10), root.y, root.x+self.width-dp(10)-0, root.y)
MDBoxLayout:
orientation: "horizontal"
pos_hint: {"center_x": .5, "center_y": .5}
MDBoxLayout:
orientation: 'horizontal'
MDBoxLayout:
orientation: 'vertical'
size_hint_x: .2
MDLabel:
text: root.number
font_style: "H6"
adaptive_height: True
MDLabel:
size_hint_y: .3
MDBoxLayout:
orientation: 'vertical'
MDLabel:
text: root.text
font_style: "Subtitle2"
adaptive_height: True
MDLabel:
text: root.secondary_text
font_style: "Caption"
theme_text_color: "Hint"
adaptive_height: True
MDIconButton:
icon: root.icon
Thank you
So, as I've understood, you want to set an icon as 'pause' while all other as 'play'. One way of doing this could be like, you have to reload the RecyclView data each time the icon changes.
Now to provide data with icon reference (i.e. 'play' or 'pause') I found the number property suitable, so I change it to NumericProperty. Thus number = NumericProperty().
Also this requires some change in kv,
MDLabel:
text: str(int(root.number))
font_style: "H6"
adaptive_height: True
To let Playlist know about the number reference,
def set_list_musics(self, music_no = 0):
"""Builds a list of audios for the screen Playlist."""
print(self.ids)
self.ids.rv.data = [ ] # Since you are appending data and we need to reload everytime.
Make required changes in data,
for i in range(len(self.music_list())):
new_icon = 'pause' if i+1 == music_no else 'play'
music = self.music_list()
add_music_item(str(i+1), music[i], '00:00:00', new_icon)
Now the final part, trigger the change via the button,
def on_release(self, *args):
if self.icon == "play":
self.icon = "pause"
pl = self.parent.parent.parent.parent.parent # Accessing the Playlist according to your design pattern.
pl.set_list_musics(self.number)
else:
self.icon = "play"
Note that I made this change in 'pause' icon (i.e. in if self.icon == "play"), thus you can also freely toggle this icon. Placing it otherwise could not make it possible.
Perhaps this could have been done more systematically with other design styles. I've found some issues with your design pattern. This (such as calling function in for loop repeatedly etc.) may make it a little bit slower as the data increases.
I am a new programmer, working on a Kivy app and I need to use screens (screen manager), the issue is that I am using element cards as but buttons, and it is supposed that when using on_release it will change the screen, however if I include root.manager.transition.direction = "left" I get the error AttributeError: 'MyContentComunes' object has no attribute 'screen'. I don't know if anyone could help me with this.
Here is a piece of code.
from kivy.app import App
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivymd.uix.expansionpanel import MDExpansionPanel, MDExpansionPanelTwoLine
from kivy.core.window import Window
from kivymd.color_definitions import colors
from kivy.uix.screenmanager import ScreenManager, Screen
from kivymd.uix.screen import MDScreen
KV = '''
WindowManager:
id: screen_manager
PrincipalWindow:
LenghtWindow:
<PrincipalWindow>:
name: "principal"
BoxLayout:
MDToolbar:
title: "Conversor"
pos_hint: {"top": 1}
ScrollView:
pos_hint: {"center_x": .5, "center_y": .5} #UbicaciĆ³n
size_hint: 0.90, 0.75
GridLayout:
halign: 'center'
cols: 1
adaptive_height: True
id: box
<LenghtWindow>:
name: "Lenght"
md_bg_color: 0.34,0.024,0.292,1
MDBoxLayout:
orientation: "vertical"
MDToolbar:
title: "Conversor"
pos_hint: {"top": 1}
Button:
text: "Back"
font_size: 32
on_release:
app.root.current = "principal"
root.manager.transition.direction = "right"
<MyContentComunes>:
orientation: 'vertical'
padding: dp(10)
spacing: dp(10)
size_hint_y: None
height: self.minimum_height
ElementCard:
image:"1490820814-13_82405.png"
text:"Longitud"
id:longitud
on_release:
app.root.current = "Lenght"
root.manager.transition.direction = "left"
<ElementCard#MDCard>:
md_bg_color: app.theme_cls.primary_color
padding: dp(15)
spacing: dp(15)
size_hint: 1, None
ripple_behavior: True
# on_release: print("worked")
image:''
text:""
items_count:""
subtext:''
orientation: "vertical"
Image:
source:root.image
halign:"center"
padding: dp (4)
spacing: dp(4)
MDBoxLayout:
orientation: "vertical"
MDLabel:
halign:"center"
text:root.text
MDLabel:
halign:"center"
font_style:"Caption"
text:root.subtext
'''
Window.size = 324, 720
class Conversor(MDApp):
def build(self):
self.theme_cls.primary_palette = "Blue"
return Builder.load_string(KV)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.list_items = [] # Dictionary where the items are stored
self.category_list1 = ['Comunes']
def on_start(self):
for category in self.category_list1:
self.root.get_screen('principal').ids.box.add_widget(
MDExpansionPanel(
icon="3.png",
content=MyContentComunes(),
panel_cls=MDExpansionPanelTwoLine(
text=category,
secondary_text="Ver las unidades"
)
)
)
class MyContentComunes(BoxLayout):
pass
class PrincipalWindow(Screen):
pass
class LenghtWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
Conversor().run()
The element card is inside an expansion panel, which is also inside a screen.
Try adding Screen to class MyContentComunes.
class MyContentComunes(BoxLayout, Screen):
pass
And I recommend you to organize your code like this. It will make your work more eaisier.
from kivy.app import App
from kivymd.app import MDApp
# import libraries ...
KV = '''
# Your KV File
'''
Window.size = 324, 720
class MyContentComunes(BoxLayout, Screen):
pass
class PrincipalWindow(Screen):
pass
class LenghtWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
class Conversor(MDApp):
def build(self):
self.theme_cls.primary_palette = "Blue"
return Builder.load_string(KV)
def __init__(self, **kwargs):
# Code
.
.
.
def on_start(self):
# Code
.
.
.
Conversor().run()
Each kivy carousel slide consists of an image at the top (with a checkbox) and text below. The direction of the carousel is 'right' (one after the other horizontally). Image and text data are fed to the carousel with a recycleview.
The minimum code results with a) Each slide stacked vertically; b) the checkbox not displaying.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.recycleview import RecycleView
from kivy.properties import StringProperty, ListProperty, NumericProperty
Builder.load_string("""
<CarouselSlide>:
BoxLayout:
orientation: 'vertical'
size_hint_y: 1
AsyncImage:
source: root.tile
size_hint_y: 0.5
CheckBox:
id: cb
root_ref: root.index
on_press: app.on_checkbox_press(self, self.active, self.state, self.root_ref)
TextInput:
text: root.text
size_hint_y: 0.5
multiline: True
<CarouselScreen>:
name: 'carousel_screen'
Carousel:
direction: 'right'
ignore_perpendicular_swipes: True
size: root.width, root.height
RV:
id: rv
viewclass: 'CarouselSlide'
RecycleBoxLayout:
orientation: 'vertical'
size_hint_y: None
default_size_hint: 1, None
height: self.minimum_height
default_size: 1, root.height
""")
class CarouselScreen(Screen):
pass
class CarouselSlide(Screen):
tile = StringProperty('')
text = StringProperty('')
index = NumericProperty(-1)
cb_state = StringProperty('normal')
def __init__(self, **kwargs):
super(CarouselSlide, self).__init__(**kwargs)
self.bind(cb_state=self.set_cb_state)
def set_cb_state(self, carouselslide, cb_state_value):
self.ids.cb.state = cb_state_value
class RV(RecycleView):
data = ListProperty('[]')
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.get_slide_data()
def get_slide_data(self):
text="Ooh, a storm is threatening. My very life today. If I don't get some shelter. Ooh yeah I'm gonna fade away. War, children. It's just a shot away. It's just a shot away. War, children. It's just a shot away. It's just a shot away."
self.data = [{'tile': 'The Rolling Stones', 'text': text, "index": i, "cb_state": 'normal'} for i in range(41)]
class ThisApp(App):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def build(self):
self.sm = ScreenManager()
self.sm.add_widget(CarouselScreen(name="carousel_screen"))
return self.sm
def on_checkbox_press(self, checkbox, active, state, root_ref):
if active:
print(active, state, root_ref)
else:
print(active, state, root_ref)
new_state = checkbox.state
checkbox.state = 'normal'
rv = self.root.get_screen('carousel_screen').ids.rv
rv.data[root_ref]['cb_state'] = new_state
rv.refresh_from_data()
if __name__ == "__main__":
ThisApp().run()
I believe the problem is that you are defining the CheckBox as a child of AsyncImage. The AsyncImage is not intended to be a container. Try un-indenting the CheckBox in your kv, and adding size/position attributes:
<CarouselSlide>:
BoxLayout:
orientation: 'vertical'
size_hint_y: 1
AsyncImage:
source: root.tile
size_hint_y: 0.5
CheckBox:
id: cb
size_hint: None, None
size: 48, 48
pos_hint: {'center_x':0.5}
root_ref: root.index
on_press: app.on_checkbox_press(self, self.active, self.state, self.root_ref)
TextInput:
text: root.text
size_hint_y: 0.5
multiline: True
I would like to change font_name property of MDLabel. But I want to add this component dynamically, in .py file, not .kv.
So when I write something like this:
main.py
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.label import MDLabel
from kivymd.uix.screen import MDScreen
class MainApp(MDApp):
def build(self):
Builder.load_string(KV)
otherLabel = MDLabel(
text="one more text",
halign="center",
font_name="18328",
font_size="50sp"
)
screen.add_widget(
otherLabel
)
return screen
MainApp().run()
it does not help. How can I implement font applying dynamically?
KV string
KV = '''
Screen:
MDCard:
id: box
orientation: "vertical"
padding: "8dp"
size_hint: None, None
size: "280dp", "180dp"
pos_hint: {"center_x": .5, "center_y": .5}
MDLabel:
text: "Hello"
theme_text_color: "Secondary"
size_hint_y: None
height: self.texture_size[1]
font_name: '18328.ttf'
MDSeparator:
height: "1dp"
MDLabel:
text: "Body"
MDLabel:
text: "Hello"
font_name: 'BebasNeue-Regular.otf'
font_size: "50sp"
'''
Create a reference in the screen itself (also you don't need to use .ttf, the library adds it automatically, just the filename without extension is fine)
class MyScreen(Screen):
my_MD_Label = None
def on_kv_post(self, instance):
self.my_MD_Label = MDLabel(
text="one more text"
font_name="18328"
)
self.ids.box.add_widget(self.my_MD_Label)
And whenever you want to change it, you can simply do this
self.my_MD_Label.font_name = "new_font" # When in same screen
self.manager.get_screen('MyScreen').my_MD_Label.font_name = "new_font" #When in other screen
I'm trying to grey out unavailable dates on the kivymd date picker (happy to use Kivy calendar if it's easier) and make it so either it doesn't let a user click a greyed out date or it will just bring up a popup saying this date is not available.
I know how to check if a date is unavailable to be selected, I can just check the date selected against a list containing unavailable dates and if the selection is in the list I can bring up a popup but I'm unsure how to do the design side - greying out the unavailable dates when a user opens the calendar.
from kivy.lang import Builder
from kivy.factory import Factory
from kivy.properties import ObjectProperty
from kivymd.app import MDApp
from kivymd.uix.picker import MDDatePicker
kv = """
<Pickers#Screen>
name: "pickers"
BoxLayout:
orientation: "vertical"
spacing: dp(20)
pos_hint: {"center_x": .5, "center_y": .5}
size_hint_y: None
height: self.minimum_height
MDRaisedButton:
text: "Open date picker"
pos_hint: {"center_x": .5}
opposite_colors: True
on_release: app.show_example_date_picker()
MDLabel:
id: date_picker_label
theme_text_color: "Primary"
halign: "center"
BoxLayout:
size_hint: None, None
size: self.minimum_size
pos_hint: {"center_x": .5}
Label:
theme_text_color: "Primary"
text: "Start on previous date"
size_hint_x: None
width: self.texture_size[0]
color: 0, 0, 0, 1
MDCheckbox:
id: date_picker_use_previous_date
size_hint: None, None
size: dp(48), dp(48)
"""
class MainApp(MDApp):
previous_date = ObjectProperty()
def __init__(self, **kwargs):
self.title = "KivyMD Examples - Date Picker"
super().__init__(**kwargs)
def build(self):
Builder.load_string(kv)
self.root = Factory.Pickers()
def show_example_date_picker(self, *args):
if self.root.ids.date_picker_use_previous_date.active:
pd = self.previous_date
try:
MDDatePicker(self.set_previous_date,
pd.year, pd.month, pd.day).open()
except AttributeError:
MDDatePicker(self.set_previous_date).open()
else:
MDDatePicker(self.set_previous_date).open()
def set_previous_date(self, date_obj):
self.previous_date = date_obj
self.root.ids.date_picker_label.text = str(date_obj)
if __name__ == "__main__":
MainApp().run()
You Can set the min and max date in the show_date_picker function.
Everything outside the range will be grayed out.
def show_date_picker(self):
min_date = datetime.strptime("2020:02:15", '%Y:%m:%d').date()
max_date = datetime.strptime("2020:05:30", '%Y:%m:%d').date()
date_dialog = MDDatePicker(
callback=self.get_date,
min_date=min_date,
max_date=max_date,
)
date_dialog.open()