Kivy, Label text and multithreading - kivy

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.

Related

Python3 KivyMD - MDDialog get ItemConfirm text value

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

buttons do not appear with a recycle view

'''
Does not appear the buttons inside recycleview
'''
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.bx = RecycleBoxLayout(default_size=(None, dp(56)), default_size_hint=(1, None),
size_hint=(1, None), orientation='vertical',)
self.but = Button(text= 'hola')
self.bx.add_widget(self.but)
self.bx.bind(minimum_height=self.bx.setter("height"))
self.data = [{'text': str(x)} for x in range(100)]
class TestApp(App):
def build(self):
return RV()
if __name__ == '__main__':
TestApp().run()
The class RecycleView uses the attribute viewclass as data container, so you have to use self.viewclass = Button here.
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.bx = RecycleBoxLayout(
default_size=(None, dp(56)),
default_size_hint=(1, None),
size_hint=(1, None),
orientation='vertical',
)
self.bx.bind(minimum_height=self.bx.setter("height"))
self.add_widget(self.bx)
Clock.schedule_once(self.update_view)
def update_view(self, *args):
#Items that will be used as data-container.
self.viewclass = Button # Or, "Button"
self.data = [{'text': str(x)} for x in range(100)]
Also note that you've to schedule the data updation in order to get the view. Alternatively, you can define (almost) everything in kivy-lang without the need of scheduling. You can find an example in Kivy documentation.

Kivymd Custom Input Dialog. problem with getting text

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!

Recycleview AttributeError: 'super' object has no attribute '__getattr__'

please check why the below program is giving an
AttributeError: 'super' object has no attribute '__getattr__'
.py:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ListProperty, NumericProperty, ObjectProperty
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.textinput import TextInput
# from kivy.effects.scroll.ScrollEffect import ScrollEffect
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.gridlayout import GridLayout
from kivy.uix.recycleboxlayout import RecycleBoxLayout
Builder.load_file('so_extractTIC.kv')
class RecycleItem(ScreenManager,RecycleDataViewBehavior, TextInput):
index = NumericProperty(0)
def refresh_view_attrs(self, rv, index, data):
self.index = index
return super(RecycleItem, self).refresh_view_attrs(rv, index, data)
class DataView(Screen):
DataList = ListProperty()
TextInputNum = NumericProperty(10)
def __init__(self,*args,**kwargs):
super(DataView, self).__init__(*args,**kwargs)
# for key, val in self.ids.items():
# print("key={0}, val={1}".format(key, val))
data12= []
for x in range(self.TextInputNum):
data12.append({'text': '', 'height': 50})
self.ids.rv.data = data12
def extract_data(self,rv):
print(self.parent.parent.parent)
self.DataList.clear()
for x in range(self.TextInputNum):
self.DataList.append(self.ids.rv.data[x]['text'])
print(self.DataList)
class RootWidget(ScreenManager):
pass
class MainApp(App):
def build(self):
# self.root = Builder.load_string(APP_KV)
return RootWidget()
if __name__ == '__main__':
MainApp().run()
.kv:
<DataView>:
BoxLayout:
orientation: 'vertical'
RecycleView:
size_hint_y: 0.9
viewclass: 'RecycleItem'
id: rv
key_size: 'size'
# effect_cls: ScrollEffect
cols: 1
RecycleBoxLayout:
id: rvbox
cols: rv.cols
orientation: 'vertical'
size_hint_y: None
height: self.minimum_height
default_size_hint: 1, None
Button:
text: 'Submit'
size_hint_y: 0.1
on_release: root.extract_data()
<RecycleItem>:
on_text: self.parent.parent.data[self.index]['text'] = self.text
<RootWidget>:
DataView:
name:"DataView_screen"
I have been searching for extract data from recycleview using textInput boxes. Please find the link for the query:
Retrieve Data from Kivy Recycleview
I am trying to inherit from ScreenManager, but it is giving the 'super' attribute error. Tried passing id as argument in the .kv and tried to find real parent but nothing works.
Also, please suggest how to use the above code for recycle GridLaout, with 2d rows and columns, i tried using for loops, but getting key related errors.
like:
for z in range(12):
for y in range(8):
self.table_data12.append(self.ids.idname.data[y][z]['text'])
thanks!
In your DataView class, the __init__() method references self.ids, but the ids are not yet available at that point. You can delay that referencing by using Clock.schedule_once() like this:
def __init__(self, *args, **kwargs):
super(DataView, self).__init__(*args, **kwargs)
# for key, val in self.ids.items():
# print("key={0}, val={1}".format(key, val))
Clock.schedule_once(self.setup_data)
def setup_data(self, dt):
data12 = []
for x in range(self.TextInputNum):
data12.append({'text': '', 'height': 50})
self.ids.rv.data = data12
I see no reason for making the RecycleItem a subclass of ScreenManager.
In your 'kv, the on_textline forRecycleItem` can be:
<RecycleItem>:
on_text: app.root.get_screen('DataView_screen').ids.rv.data[self.index]['text'] = self.text
And the submit Button can be:
Button:
text: 'Submit'
size_hint_y: 0.1
on_release: root.extract_data(rv)

Kivy Clipboard.copy label text

I want to copy the content of a Label: self.text when I double tap the label, but the following is not working:
main.py
#!/usr/bin/kivy
# -*- coding: utf-8 -*-
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
class DoubletapClipboardInterface(BoxLayout):
pass
class DoubletapClipboardApp(App):
#copy_clipboard = ObjectProperty()
def build(self):
self.title = 'DoubletapClipboard'
#self.copy_clipboard = DoubletapClipboardInterface()
return(DoubletapClipboardInterface()) # self.copy_clipboard
if __name__ == '__main__':
DoubletapClipboardApp().run()
doubletapclipboard.kv
#:kivy 1.9.0
#:import Clipboard kivy.core.clipboard.Clipboard
<DoubletapClipboardInterface>:
orientation: 'vertical'
TextInput:
hint_text: 'Try to paste here to see if it works'
Label:
text: 'Can I be copied?'
on_double_tap: Clipboard.copy(self.text) # <-- How do I do this the correct way?
Error
kivy.lang.builder.BuilderException: Parser: File "/home/stef-ubuntu/bitbucket/kanjiorigin_data/test/doubletap_clipboard/doubletapclipboard.kv", line 11:
...
9: Label:
10: text: 'Can I be copied?'
>> 11: on_double_tap: Clipboard.copy(self.text) # <-- How do I do this the correct way?
...
AttributeError: double_tap
File "/usr/lib/python3/dist-packages/kivy/lang/builder.py", line 628, in _apply_rule
raise AttributeError(key)
On suggestion of #MatthiasSchreiber I copied the code from TextInput()
main.py
#!/usr/bin/kivy
# -*- coding: utf-8 -*-
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.properties import ObjectProperty
from kivy.utils import platform
Clipboard = None
CutBuffer = None
class TouchLabel(Label):
def __init__(self, **kwargs):
self._touch_count = 0
super(TouchLabel, self).__init__(**kwargs)
self.register_event_type('on_double_tap')
if platform == 'linux':
self._ensure_clipboard()
def _ensure_clipboard(self):
global Clipboard, CutBuffer
if not Clipboard:
from kivy.core.clipboard import Clipboard, CutBuffer
def on_touch_down(self, touch):
print("hello")
if self.disabled:
return
touch_pos = touch.pos
if not self.collide_point(*touch_pos):
return False
if super(TouchLabel, self).on_touch_down(touch):
return True
touch.grab(self)
self._touch_count += 1
if touch.is_double_tap:
self.dispatch('on_double_tap')
def on_double_tap(self, *args):
Clipboard.copy(self.text) # <-- How do I do this the correct way?
print("Copied :D")
class DoubletapClipboardInterface(BoxLayout):
pass
class DoubletapClipboardApp(App):
def build(self):
self.title = 'DoubletapClipboard'
return(DoubletapClipboardInterface())
if __name__ == '__main__':
DoubletapClipboardApp().run()
doubletabclipboard.kv
#:kivy 1.9.0
# #:import Clipboard kivy.core.clipboard.Clipboard
<TouchLabel>
<DoubletapClipboardInterface>:
orientation: 'vertical'
TextInput:
hint_text: 'Try to paste here to see if it works'
TouchLabel:
text: 'Can I be copied?'
#on_double_tap: Clipboard.copy(self.text) # <-- not working
There is no "on_double_tap" for a Label, you need to create that method yourself.
There is one for TexInput though and you could look how it's done there in the code.
Also, you need to import the Clipboard into your kv file.

Resources