I am creating an Input Dialog using kivymd. Whenever I try to fetch the text from the text field, it doesn't output the text, rather it seems like the text is not there. (the dialog just pops up ok and the buttons are working fine).
part of the kivy code
<Content>
MDTextField:
id: pin
pos_hint: {"center_x": 0.5, "center_y": 0.5}
color_mode: 'custom'
line_color_focus: [0,0,1,1]
part of the python code
class Content(FloatLayout):
pass
class MenuScreen(Screen):
def __init__(self, **kwargs):
super(MenuScreen, self).__init__(**kwargs)
def show_confirmation_dialog(self):
# if not self.dialog:
self.dialog = MDDialog(
title="Enter Pin",
type="custom",
content_cls=Content(),
buttons=[
MDFlatButton(
text="cancel",on_release=self.callback
),
MDRaisedButton(
text="[b]ok[/b]",
on_release=self.ok,
markup=True,
),
],
size_hint_x=0.7,
auto_dismiss=False,
)
self.dialog.open()
def callback(self, *args):
self.dialog.dismiss()
def ok(self, *args):
pin = Content().ids.pin.text
if pin == "":
toast("enter pin")
else:
toast(f"pin is {pin}")
You can call the Content class to a variable and use it in the MDDialog.content_cls.
def show_custom_dialog(self):
content_cls = Content() # call the class to a variable
self.cdialog = MDDialog(content_cls=content_cls,
type='custom', title='Enter Pin'
)
self.cdialog.buttons = [
MDFlatButton(text='cancel',on_release=self.close_dialog),
MDRaisedButton(text='Ok',
on_release=lambda x:self.get_data(x,content_cls))
]
self.cdialog.open()
Then create a function that will get the event_button and the content class as arguments by use of lambda expression in the button as shown above.
def get_data(self,instance_btn, content_cls):
textfield = content_cls.ids.pin
# get input
value = textfield._get_text()
# do stufs here
toast(value)
At this point you can extract any id in the Content class.I hope this helps.
Refer below for full code
from kivmd.app import MDApp
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.dialog import MDDialog
from kivymd.uix.button import MDFlatButton, MDRaisedButtom
from kivy.lang.builder impor Builder
from kivy.toast import toast
kv = '''
MDBoxLayout:
orientation : 'vertical'
MDFlatbutton:
text : 'Dialog'
pos_hint : {'center_x':.5'}
on_release : app.show_custom_dialog()
<Content>:
MDTextField:
id : pin
pos_hint : {'center_x':.5,'center_y':.5}
'''
class Content(MDFloatLayout):
pass
class InputDialogApp(MDApp):
cdialog = None
def build(self):
return Builder.load_string(kv)
def show_custom_dialog(self):
content_cls = Content()
self.cdialog = MDDialog(title='Enter Pin',
content_cls=content_cls,
type='custom')
self.cdialog.buttons = [
MDFlatButton(text="Cancel",on_release=self.close_dialog),
MDRaisedButton(text="Ok",on_release=lambda x:self.get_data(x,content_cls))
]
self.cdialog.open()
def close_dialog(self, instance):
if self.cdialog:
self.cdialog.dismiss()
def get_data(self, instance_btn, content_cls):
textfield = content_cls.ids.pin
value = textfield._get_text()
# do stuffs here
toast(value)
self.close_dialog(instance_btn)
if __name__ == '__main__':
InputDialogApp().run()
I was also having this problem, and the comment by edwin helped a lot. However, one suggestion. instead of formatting it with buttons = [...] inside of MDDialog as opposed to after, like so self.cdialog = MDDialog(..., buttons = [...]) since otherwise the buttons dont show up but other than that it should work!
Related
I want to pass to my project_selected function the selected item in my MDDialog after pressing the OK button. But I can't figure out how can I get the value and do this.
I could print the value inside my set_icon funtion in ItemConfirm class, but I don't know whats the better way to pass that value to HomeWindow(Screen) class or if it is possible to call it directly from inside of it once that the ItemConfirm is already called from inside a function that is part of HomeWindow class.
main.py
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivymd.app import MDApp
from kivymd.uix.button import MDFlatButton
from kivymd.uix.dialog import MDDialog
from kivymd.uix.list import OneLineAvatarIconListItem
class ItemConfirm(OneLineAvatarIconListItem):
divider = None
def set_icon(self, instance_check):
print(self.text)
instance_check.active = True
check_list = instance_check.get_widgets(instance_check.group)
for check in check_list:
if check != instance_check:
check.active = False
class HomeWindow(Screen):
dialog = None
def show_confirmation_dialog(self):
projects = [{id: 0, name: "example1"},{id: 1, name: "example2"}]
if not self.dialog:
self.dialog = MDDialog(
title="Projects",
type="confirmation",
auto_dismiss=False,
items=[ItemConfirm(text=f"{project['name']}") for project in projects],
buttons=[
MDFlatButton(text="CANCEL", theme_text_color="Custom"),
MDFlatButton(
text="OK",
theme_text_color="Custom",
on_release=self.project_selected,
),
],
)
self.dialog.open()
def project_selected(self, *args, **kwargs):
self.ids.project_selection.text = self.dialog.text
class WindowManager(ScreenManager):
pass
class RlogTimer(MDApp):
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "BlueGray"
self.root = Builder.load_file("templates/main.kv")
return self.root
if __name__ == "__main__":
RlogTimer().run()
templates/main.kv
#: import NoTransition kivy.uix.screenmanager.NoTransition
WindowManager:
HomeWindow:
<ScreenManager>:
transition: NoTransition()
<ItemConfirm>:
on_release: root.set_icon(check)
CheckboxLeftWidget:
id: check
group: "check"
<HomeWindow>:
name: "home"
MDBoxLayout:
orientation: "vertical"
MDToolbar:
id: title
title: "Redmine logTimer"
right_action_items: [["clock", lambda x: app.callback_2()]]
MDFlatButton:
id: project_selection
text: "Select Project"
pos_hint: {'center_x': .5, 'center_y': .5}
on_release: root.show_confirmation_dialog()
You can achieve that as follows,
First create a prop. say, selected_project in HomeWindow as,
class HomeWindow(Screen):
dialog = None
selected_project = StringProperty()
Now set its value in method set_icon as,
def set_icon(self, instance_check):
...
app = MDApp.get_running_app() # Access the running app instance.
home_screen = app.root.get_screen("home") # Access required screen.
home_screen.selected_project = self.text # Now set value.
...
Now it's time to set this value in method project_selected,
def project_selected(self, *args, **kwargs):
self.ids.project_selection.text = self.selected_project
I'm using ScreenManager. I would like to link the items in a OneLineListItem to new screen.
This is my KV file
screen_helper = """
ScreenManager:
GroupsScreen:
MyGroupScreen:
<GroupsScreen>:
name: 'groups'
ScrollView:
MDList:
id: container_groups
<MyGroupScreen>:
name: 'my_group'
ScrollView:
MDList:
id: container_group
MDRectangleFlatButton:
text: 'Back'
pos_hint: {'center_x':0.5,'center_y':0.1}
on_press: root.manager.current = 'groups'
and this is the python file
class GroupsScreen(Screen):
def on_enter(self, *args):
for i in range(5):
item = OneLineListItem(text='Gruppo ' + str(i))
self.ids.container_groups.add_widget(item)
class MyGroupScreen(Screen):
pass
sm = ScreenManager()
sm.add_widget(GroupsScreen(name='groups'))
sm.add_widget(MyGroupScreen(name='my_group'))
class DemoApp(MDApp):
def build(self):
self.theme_cls.primary_palette = "Red"
self.theme_cls.primary_hue = "500"
screen = Builder.load_string(screen_helper)
return screen
I would like to click on an item in the groups screen and go to MyGroupScreen.
The OneLineListItem class actually extends ButtonBehavior, so you can treat it like a Button. Just assign a on_press or on_release method to the OneLineListItem:
class GroupsScreen(Screen):
def on_kv_post(self, *args):
for i in range(5):
item = OneLineListItem(text='Gruppo ' + str(i))
item.on_release = self.switch_to_my_groups
self.ids.container_groups.add_widget(item)
def switch_to_my_groups(self, *args):
self.manager.current = 'my_group'
I changed your on_enter() method to a on_kv_post() to be sure that the kv rules have been executed (so that the ids are available).
Another problem is the lines:
sm = ScreenManager()
sm.add_widget(GroupsScreen(name='groups'))
sm.add_widget(MyGroupScreen(name='my_group'))
These lines should be removed. They attempt build the GUI, and then the result is not used. The line:
screen = Builder.load_string(screen_helper)
then rebuilds the GUI again, and this time it is actually used.
I've put a loop into thread and I want my label to show the given thext. The issue is that it doesn't show most of texts I see in the terminal. It shows only:
>Text 0
>nothing
>Text 4
>Text 5
>nothing
>text 14
>text 15
>etc.
I've no idea why is that. I tried to use Clock.schedule_once to call prompt function but the resoult is the same.
CODE
from kivy.uix.boxlayout import BoxLayout
import threading
from kivy.app import App
from kivy.lang.builder import Builder
from time import sleep
kv = '''
<MainWindow>
Button:
text: 'Go!'
on_press: root.go()
Label:
id: label
MainWindow:
'''
class MainWindow(BoxLayout):
def __init__(self):
super(MainWindow, self).__init__()
def go(self):
thread = Prompter()
thread.start()
def prompt(self, text):
self.ids.label.text = text
print(self.ids.label.text)
class MyThread(threading.Thread):
def __init__(self):
super(MyThread, self).__init__()
def run(self):
for i in range(100):
text = 'TEXT {}'.format(i)
App.get_running_app().root.prompt(text)
sleep(0.5)
class Setup(App):
def build(self):
return Builder.load_string(kv)
if __name__ == '__main__':
Setup().run()
Try adding the #mainthread decorator to the prompt() method. Changes to the GUI must be done on the min thread.
I'm having some trouble adding the kivymd MDDataTable component as a child widget to a Screen in a .kv file. Keep getting a KeyError: 'container' and AttributeError: 'super' object has no attribute '__getattr__ error. I've looked through the docs and multiple sites and everyone seems to be using some variant of the example found in the docs, which starts the component in the build method.
What I'm trying to say is if this works
class Example(MDApp):
def build(self):
screen = Screen()
data_tables = MDDataTable(
size_hint=(0.9, 0.6),
column_data=[
('Template Id', dp(30)),
('Name', dp(30))
],
row_data=[
('23lkjk33', 'Ayang Paul'),
('28ij399kk', 'Ringwa Justin')
]
)
screen.add_widget(data_tables)
return screen
Example().run()
then why doesn't this work
KV = '''
Screen:
MDDataTable:
size_hint: 0.9, 0.6
pos_hint: {"center_x": 0.5, "center_y": 0.5}
column_data: [('Template Id', dp(30)), ('Name', dp(30))]
row_data: [('23lkjk33', 'Ayang Paul'), ('28ij399kk', 'Ringwa Justin')]
'''
class Example(MDApp):
def build(self):
return Builder.load_string(KV)
Example().run()
???
The problem is that the MDDataTable__init__() method references ids, but when used in a kv file, the ids are not yet available. The MDDataTable is a subclass of ModalView, so it is apparently intended to be used like a Popup rather than in a kv file.
Here is a hack that will let it work in a kv file:
from kivymd.uix.datatables import MDDataTable, TableHeader, TableData, TablePagination
from kivymd.uix.dialog import BaseDialog
class MyDataTable(MDDataTable):
def __init__(self, **kwargs):
# skip the MDDataTable.__init__() and call its superclass __init__()
super(BaseDialog, self).__init__(**kwargs)
# schedule call to MDDataTable.__init__() contents after ids are populated
Clock.schedule_once(partial(self.delayed_init, **kwargs))
def delayed_init(self, dt, **kwargs):
# this is copied from MDDataTable.__init__() with super() call deleted
self.register_event_type("on_row_press")
self.register_event_type("on_check_press")
self.header = TableHeader(column_data=self.column_data, sort=self.sort)
self.table_data = TableData(
self.header,
row_data=self.row_data,
check=self.check,
rows_num=self.rows_num,
_parent=self,
)
self.pagination = TablePagination(table_data=self.table_data)
self.table_data.pagination = self.pagination
self.header.table_data = self.table_data
self.table_data.fbind("scroll_x", self._scroll_with_header)
self.ids.container.add_widget(self.header)
self.ids.container.add_widget(self.table_data)
if self.use_pagination:
self.ids.container.add_widget(self.pagination)
Clock.schedule_once(self.create_pagination_menu, 0.5)
So use MyDataTable in your kv in place of MDDataTable. The above code delays the execution of the guts of the MDDataTable.__init__() method until the ids are available. If the MDDataTable code is ever updated, this hack may not work.
Looks like you have some indentation errors. From the documentation:
The indentation is important and must be consistent. The spacing must
be a multiple of the number of spaces used on the first indented line.
Spaces are encouraged: mixing tabs and spaces is not recommended.
Try this:
<ModifyTeacherInfo#Screen>:
name: "modifyTeacherInfo"
MDDataTable:
size_hint: 0.9, 0.6
pos_hint: {"center_x": 0.5, "center_y": 0.5}
column_data: [('Template Id', dp(30)), ('Name', dp(30))]
row_data: [('23lkjk33', 'Ayang Paul'), ('28ij399kk', 'Ringwa Justin')]
As previous have commented, this can be achieved via an override. The only change in the override is to remove the super() call from the original class and schedule the call to your new delayed_init. For the sake of copy+paste, as of kivy (2.1.0) and kivymd (0.104.2) the following works:
from kivymd.theming import ThemableBehavior
from kivymd.uix.datatables import MDDataTable
from kivy.clock import Clock
class MyDataTable(MDDataTable):
def __init__(self, **kwargs):
# skip the MDDataTable.__init__() and call its superclass __init__()
super(ThemableBehavior, self).__init__(**kwargs)
# schedule call to MDDataTable.__init__() contents after ids are populated
Clock.schedule_once(partial(self.delayed_init, **kwargs))
def delayed_init(self, dt, **kwargs):
# this is copied from MDDataTable.__init__() with super() call deleted
self.header = TableHeader(
column_data=self.column_data,
sorted_on=self.sorted_on,
sorted_order=self.sorted_order,
)
self.table_data = TableData(
self.header,
row_data=self.row_data,
check=self.check,
rows_num=self.rows_num,
_parent=self,
)
self.register_event_type("on_row_press")
self.register_event_type("on_check_press")
self.pagination = TablePagination(table_data=self.table_data)
self.table_data.pagination = self.pagination
self.header.table_data = self.table_data
self.table_data.fbind("scroll_x", self._scroll_with_header)
self.ids.container.add_widget(self.header)
self.ids.container.add_widget(self.table_data)
if self.use_pagination:
self.ids.container.add_widget(self.pagination)
Clock.schedule_once(self.create_pagination_menu, 0.5)
self.bind(row_data=self.update_row_data)
Your KV file:
Screen:
MDDataTable:
size_hint: 0.9, 0.6
pos_hint: {"center_x": 0.5, "center_y": 0.5}
column_data: [('Template Id', dp(30)), ('Name', dp(30))]
row_data: [('23lkjk33', 'Ayang Paul'), ('28ij399kk', 'Ringwa Justin')]
I am trying to create a modalview with a timer in it. Upon pressing the "begin" button, a modal view should appear and the countdown should start. However, I am getting an valueerror message. ValueError: TimerView.timer has an invalid format (got main.TimerView object at 0x0000017AD40D6180>>>).May I know which part of the code is wrong? Thanks in advance.
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition, WipeTransition, NoTransition, SlideTransition
from kivymd.theming import ThemeManager
from kivy.properties import StringProperty, NumericProperty
from kivy.uix.modalview import ModalView
from kivymd.uix.label import MDLabel
from kivy.clock import Clock
class TimerView(ModalView):
number = NumericProperty(15)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.background = "transparent_image.png"
self.background_color = [1/3,1/3,1/3,0.8]
self.auto_dismiss = False
self.size_hint = (None,None)
self.size = (150,50)
timer_countdown = MDLabel(font_style = 'H1', theme_text_color = 'Primary',
text = str(self.number), halign = 'center',
text_color = (1,1,1,1), size_hint_y = None)
self.add_widget(timer_countdown)
def decrement_time(self, dt):
self.number -= 1
def start(self,*args):
self.timer = Clock.schedule_interval(self.decrement_time, 1)
def stop(self):
Clock.unschedule(self.timer)
class MainScreen(Screen):
pass
class BeginScreen(Screen):
pass
class MyScreenManager(ScreenManager):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.view = TimerView()
def open_view(self):
self.view.bind(on_open=self.view.start)
self.view.open()
main_widget_kv = ('''
#: import ScrollEffect kivy.effects.scroll.ScrollEffect
MyScreenManager:
BeginScreen:
<BeginScreen>:
begin_button:begin_button
name: "begin"
canvas.before:
Color:
rgb: .1, .1, .1
FloatLayout:
id: begin_layout
Button:
id: begin_button
text: 'Begin'
font_size: 24
on_press: app.root.open_view()
size_hint: (.4,.25)
pos_hint: {"center_x":.5, "center_y":.2}
color: [0,0,0,1]
''')
class TestApp(MDApp):
def __init__(self,**kwargs):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Red"
super().__init__(**kwargs)
def build(self):
main_widget = Builder.load_string(main_widget_kv)
return main_widget
if __name__ == '__main__':
TestApp().run()
When you set the text of a Label in python code, it uses the value at that time, and will not change automatically. If you do the same thing in kv, it will update automatically (provided that the text references a Property). So just changing self.number has no effect on your timer_countdown Label.
So, you need to update that text explicitly. Here is a modified version of your code that does that:
class TimerView(ModalView):
number = NumericProperty(15)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.background = "transparent_image.png"
self.background_color = [1/3,1/3,1/3,0.8]
self.auto_dismiss = False
self.size_hint = (None,None)
self.size = (150,50)
self.timer_countdown = MDLabel(font_style = 'H1', theme_text_color = 'Primary',
text = str(self.number), halign = 'center',
text_color = (1,1,1,1), size_hint_y = None)
self.add_widget(self.timer_countdown)
def decrement_time(self, dt):
self.number -= 1
# self.timer_countdown.text = str(self.number)
def on_number(self, instance, value):
self.timer_countdown.text = str(value)
def start(self,*args):
self.t = Clock.schedule_interval(self.decrement_time, 1)
def stop(self):
Clock.unschedule(self.t)
A reference to the MDLabel is kept in self.timer_countdown and the on_number() method gets executed whenever number changes, and just updates the MDLabel. Note that you can also do the update by just uncommenting the line:
# self.timer_countdown.text = str(self.number)
In that case, number does not need to be a Property, and the on_number() method is not needed.