KivyMD Tab name containing icons and text - kivymd

I'm using KivyMD and I'm trying to create a tab name that contains both icons and text in it - but it won't display properly on my screen.
I'm trying to get a tab to have the text: [A] Tab1 (where [A] is the icon).
If the icon is called on its own, it displays fine (see second tab in screenshot below). But when it is combined with text it won't show up (it shows a box with an X in it like it's an unrecognised character).
The examples in https://kivymd.readthedocs.io/en/latest/components/tabs/ load the tabs via python, but I want to do it from the .kv file, but I'm obviously not getting my syntax correct and I can't work out how to apply what is being done in those examples.
Here's my code of the different ways I've tried to display it. For simplicity I've put all my code into one place, but in reality it the KV component is in a separate .kv file.
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.tab import MDTabsBase
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import Screen
KV = '''
#:import md_icons kivymd.icon_definitions.md_icons
BoxLayout:
id: boxcad
orientation: 'vertical'
MDToolbar:
id: toolcad
title: "Mainscreen"
md_bg_color: app.theme_cls.primary_color
BoxLayout:
id: tabox
orientation: 'vertical'
MDTabs:
id: itemstab
Tab:
id: tab1
name: 'tab1'
text: "Tab1"
Tab:
id: tab2
name: 'tab2'
text: 'alpha-a-box'
Tab:
id: tab3
name: 'tab3'
text: md_icons.get('alpha-a-box')
Tab:
id: tab4
name: 'tab4'
text: 'alpha-a-box' + 'Tab4'
Tab:
id: tab5
name: 'tab5'
text: md_icons.get('alpha-a-box') + ' Tab5'
Tab:
id: tab6
name: 'tab6'
text: md_icons['alpha-a-box'] + ' Tab6'
'''
class MainScreen(Screen):
pass
class Tab(BoxLayout, MDTabsBase):
"""Class implementing content for a tab."""
pass
class ExampleApp(MDApp):
def build(self):
return Builder.load_string(KV)
ExampleApp().run()
And a screen shot of what I see on screen:
Thank you for your assistance

I've managed to work it out - so incase you are having the same problem I was, here's how I did it.
You can use an f-string in the .KV file as it's displayed in the https://kivymd.readthedocs.io/en/latest/components/tabs/ examples. You need to import the md_icons and fonts in the .KV file too.
Here's my final code, with Tab 7 showing the output I was trying to achieve.
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.tab import MDTabsBase
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import Screen
KV = '''
#:import md_icons kivymd.icon_definitions.md_icons
#:import fonts kivymd.font_definitions.fonts
BoxLayout:
id: boxcad
orientation: 'vertical'
MDToolbar:
id: toolcad
title: "Mainscreen"
md_bg_color: app.theme_cls.primary_color
BoxLayout:
id: tabox
orientation: 'vertical'
MDTabs:
id: itemstab
Tab:
id: tab1
name: 'tab1'
text: "Tab1"
Tab:
id: tab2
name: 'tab2'
text: 'alpha-a-box'
Tab:
id: tab3
name: 'tab3'
text: md_icons.get('alpha-a-box')
Tab:
id: tab4
name: 'tab4'
text: 'alpha-a-box' + 'Tab4'
Tab:
id: tab5
name: 'tab5'
text: md_icons.get('alpha-a-box') + ' Tab5'
Tab:
id: tab6
name: 'tab6'
text: md_icons['alpha-a-box'] + ' Tab6'
Tab:
id: tab7
name: 'tab7'
text: f"[size=50][font={fonts[-1]['fn_regular']}]{md_icons['alpha-a-box']}[/size][/font] Tab7"
'''
class MainScreen(Screen):
pass
class Tab(BoxLayout, MDTabsBase):
"""Class implementing content for a tab."""
pass
class ExampleApp(MDApp):
def build(self):
return Builder.load_string(KV)
ExampleApp().run()

Related

How to call functions inside the kivy code

When i click on the Button: Profile, one error appears, i need the function to call the button to be pressed, the function checks if the variable is correct, and sends the person to another window
I've tried to put the function inside the kivy code but it doesn't recognize it, I don't know what else to do I just want to check the login and change the page, can someone help me?
Images:
Thats my code:
from kivymd.app import MDApp
from kivy.lang.builder import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
txtuser = 'Root'
def verified_Login():
if txtuser == 'Root':
root.manager.current = 'profile'
else:
print("oi")
screen_helper = """
ScreenManager:
LoginScreen:
ProfileScreen:
<LoginScreen>:
name: 'login'
MDRectangleFlatButton:
text: 'Profile'
pos_hint: {'center_x':0.5,'center_y':0.5}
on_press: verified_Login()
<ProfileScreen>:
name: 'profile'
MDLabel:
text: 'Test'
halign: 'center'
MDRectangleFlatButton:
text: 'Back'
pos_hint: {'center_x':0.5,'center_y':0.2}
on_press: root.manager.current = 'login'
"""
class LoginScreen(Screen):
pass
class ProfileScreen(Screen):
pass
sm = ScreenManager()
sm.add_widget(LoginScreen(name='menu'))
sm.add_widget(ProfileScreen(name='profile'))
class DemoApp(MDApp):
def build(self):
screen = Builder.load_string(screen_helper)
return screen
DemoApp().run()
The Error when i click on the button:
line 57, in custom_callback
exec(__kvlang__.co_value, idmap)
File "<string>", line 12, in <module>
NameError: name 'verified_Login' is not defined
You need to import the verified_Login() method in your screen_helper. At the beginning of that string add:
#:import verified_Login file_name.verified_Login
where file_name is the name of the file containing your code (without the .py extension).
You also need to fix your verified_Login() method:
def verified_Login():
if txtuser == 'Root':
MDApp.get_running_app().root.current = 'profile'
else:
print("oi")
By the way, the following lines:
sm = ScreenManager()
sm.add_widget(LoginScreen(name='menu'))
sm.add_widget(ProfileScreen(name='profile'))
are building an unused instance of your App and can be removed. The actual App is built in the build() method by the line:
screen = Builder.load_string(screen_helper)

Kivy manages multiple pages and widgets

Goodmorning everyone. I have a problem with the management of widgets within the application that I am developing with kivy. If I use only one page (window) everything works fine. If I add a page as you can see from the code I get errors of this type. I can't solve them, can you give me a hand?
self.root.ids["mdlabel"].text = "Hello"
KeyError: 'mdlabel'
The code I used is the following:
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivymd.app import MDApp
KV="""
ScreenManager:
MainWindow:
name: "screen1"
SecondWindow:
name: "screen2"
<MainWindow>:
id: screen1
RelativeLayout:
MDTextField:
id: mdtext
hint_text: "No helper text"
pos: (120,120)
MDLabel:
id: mdlabel
text: "No helper text"
pos: (0,120)
MDRectangleFlatIconButton:
id: mdbu
text: "Invia"
on_press: app.fun()
pos: (0, 10)
MDRectangleFlatIconButton:
id: mdbu_cambia1
text: "Order"
on_press: app.root.current = "screen2"
pos: (250, 10)
<SecondWindow>:
id: screen2
RelativeLayout:
MDTextField:
id: testo_2
hint_text: "Testo2"
pos: (10,150)
MDRectangleFlatIconButton:
id: mdbu_cambia
text: "Back"
on_press: app.root.current = "screen1"
pos: (250, 10)
"""
class MainWindow(Screen):
pass
class SecondWindow(Screen):
pass
class ScreenManager(ScreenManager):
pass
class MyApp(MDApp):
def build(self):
self.title = "Ciao Mondo"
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Green"
return Builder.load_string(KV)
def fun(self):
self.root.ids["mdlabel"].text = "Hello"
MyApp().run()
The problem is that the id (mdlabel) that you are trying to access is defined in the MainWindow rule, but you are trying to access it as though it were defined in the app root. The app root is the ScreenManager.
You can fix it by either:
Move the fun() method into the MainWindow and change the button on_press: to on_press: root.fun()
Or change the fun() method to access the MainWindow Screen as self.root.get_screen("screen1").ids["mdlabel"].text = "Hello"

Why are my application's screens joining together?

I am making the login and signup page for an app using kivy, but when I run the program both the screens
are joining together.
Please help me.
This is my python code:-
from kivy.app import App
from kivy.lang import Builder
from kivy.core.text import LabelBase
from kivy.uix.screenmanager import ScreenManager, Screen
Builder.load_file('design.kv')
LabelBase.register(name= "oswald",
fn_regular= "oswald/Oswald-Regular.ttf")
class LoginScreen(Screen):
def sign_up(self):
self.manager.current = "signup_screen"
class SignupScreen(Screen):
pass
class RootWidget(Screen):
pass
class MainApp(App):
def build(self):
return RootWidget()
if __name__ == "__main__":
MainApp().run()
This is my kivy code:-
<LoginScreen>:
GridLayout:
cols: 1
GridLayout:
cols: 1
Label:
text: "User Login"
TextInput:
hint_text: "Username"
TextInput:
hint_text: "Password"
Button:
text: "Login"
GridLayout:
cols: 2
Button:
text: "Forgot Password?"
Button:
text: "Signup here"
on_press: root.sign_up()
<SignupScreen>:
GridLayout:
cols:1
Label:
text: "Signup for lots of fun!!!"
TextInput:
hint_text: "Username"
TextInput:
hint_text: "Password"
Button:
text: "Signup"
<RootWidget>:
LoginScreen:
name: "login_screen"
SignupScreen:
name: "signup_screen"
This is how it is supposed to look:-
Login page
Signup page
But this is the error page:-
Error page
Please help me.
Try changing:
class RootWidget(Screen):
pass
to:
class RootWidget(ScreenManager):
pass
You are adding Screens to a Screen, when I believe you want to add them to a ScreenManager.

How can I disable a section in Kivy settings?

I have a kivy app with settings and I wish to disable some settings (for example 'General') for certain users. I have tried to 'walk' through the children of settings (to use the disabled setting) but I cannot seem to do so.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import Screen
from kivy.uix.settings import SettingsWithSidebar
import json
settings_general = json.dumps([
{'type': 'title',
'title': 'Set general preferences'},
{'type': 'bool',
'title': 'Use widgets',
'desc': 'Allow the use of widgets in the application',
'section': 'general',
'key': 'use widgets'},
{'type': 'bool',
'title': 'Print log',
'desc': 'Print log on closing application',
'section': 'general',
'key': 'print log'}
])
settings_appearance = json.dumps([
{'type': 'title',
'title': 'Appearance preferences'},
{'type': 'bool',
'title': 'Use colour',
'desc': 'Allow the use of colour in the application',
'section': 'appearance',
'key': 'use colour'}
])
class MainFrame(Screen):
def __init__(self, **kwargs):
super(MainFrame, self).__init__(**kwargs)
pass
def on_settings_button_click(self):
settings = self.app.settings_cls
print('type settings', type(settings))
print('is SettingsWithSidebar', isinstance(settings,
SettingsWithSidebar))
self.app.open_settings()
def on_quit_button_click(self):
quit()
class Settings(object):
def build_config(self, config):
config.setdefaults('general',
{
'use widgets': 0,
'print log': 0
})
config.setdefaults('appearance',
{
'use colour': 0
})
def build_settings(self, parent, settings):
settings.add_json_panel('General',
parent.config,
data=settings_general)
settings.add_json_panel('Appearance',
parent.config,
data=settings_appearance)
class BasicApp(App):
def build(self):
main_frame = MainFrame()
main_frame.app = self
self.settings_cls = SettingsWithSidebar
self.use_kivy_settings = False
return main_frame
def build_config(self, config):
self.settings = Settings()
self.settings.build_config(config)
def build_settings(self, settings):
self.settings.build_settings(self, settings)
if __name__ == '__main__':
BasicApp().run()
My kv file is:
<MainFrame>:
BoxLayout:
orientation: 'vertical'
Button:
text: 'Settings'
on_press: root.on_settings_button_click()
Button:
text: 'Click to close'
on_press: root.on_quit_button_click()
Can someone please suggest an approach that I can use?
[This section does not need to be read]
I have to enter more text before I am allowed to post this (ratio of code to text I think). It seems a shame because the question is so simple and I wanted to provide a basic working example of the application which is, of necessity, quite verbose.
[end of extra text]
You can disable the items in a panel with the disabled attribute.
settings_general = json.dumps([
{'type': 'title',
'title': 'Set general preferences'},
{'type': 'bool',
'disabled': True,
'title': 'Use widgets',
'desc': 'Allow the use of widgets in the application',
'section': 'general',
'key': 'use widgets'}])
The second item is disabled in this example.
However, I did not find an intuitive way to disable the whole Appearance section, for example.
So I went for a hackery method.
First was to walk the settings widget tree, to find that label.
tab = list(self.app.settings.walk(loopback=True))[5]
I found out that the label is the 6th element in this case.
But it was not enough to set disable attribute to True. It grays the label out, but it still works to click it, since they used the on_touch_down method.
So we can override the on_touch_down method.
I added a switch to the mainframe and a toggle method in the app class, to test this.
<MainFrame>:
BoxLayout:
orientation: 'vertical'
Button:
text: 'Settings'
on_press: root.on_settings_button_click()
Switch:
on_active: app.toggle_setting(self.active)
Button:
text: 'Click to close'
on_press: root.on_quit_button_click()
I found the original on_touch_down method here
def on_touch_down(touch, self):
# we need to write this method to override back to the original method
# the original method was found in kivy/uix/settings.py.
# look at the link above
if not self.collide_point(*touch.pos):
return
self.selected = True
self.menu.selected_uid = self.uid
class MainFrame(Screen):
def on_settings_button_click(self):
self.app.open_settings()
tab = list(self.app.settings.walk(loopback=True))[5]
if not self.app.toggle: # if switch is inactive
tab.disabled = True
tab.on_touch_down = lambda x: False
else:
tab.disabled = False
# we need partial from functools, so we can pass the tab as self
tab.on_touch_down = partial(on_touch_down,self=tab)
def on_quit_button_click(self):
quit()
The complete code:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import Screen
from kivy.uix.settings import SettingsWithSidebar
from kivy.lang import Builder
import json
from functools import partial
settings_general = json.dumps([
{'type': 'title',
'title': 'Set general preferences'},
{'type': 'bool',
'disabled': True,
'title': 'Use widgets',
'section': 'general',
'key': 'use widgets'}
])
settings_appearance = json.dumps([
{'type': 'title',
'title': 'Appearance preferences'},
{'type': 'bool',
'title': 'Use colour',
'section': 'appearance',
'key': 'use colour'}
])
def on_touch_down(touch, self):
if not self.collide_point(*touch.pos):
return
self.selected = True
self.menu.selected_uid = self.uid
class MainFrame(Screen):
def on_settings_button_click(self):
self.app.open_settings()
tab = list(self.app.settings.walk(loopback=True))[5]
if not self.app.toggle:
tab.disabled = True
tab.on_touch_down = lambda x: False
else:
tab.disabled = False
tab.on_touch_down = partial(on_touch_down,self=tab)
def on_quit_button_click(self):
quit()
Builder.load_string("""
<MainFrame>:
BoxLayout:
orientation: 'vertical'
Button:
text: 'Settings'
on_press: root.on_settings_button_click()
Switch:
on_active: app.toggle_setting(self.active)
Button:
text: 'Click to close'
on_press: root.on_quit_button_click()
""")
class BasicApp(App):
toggle = False
def build(self):
self.main = MainFrame()
self.main.app = self
self.settings_cls = SettingsWithSidebar
self.use_kivy_settings = False
return self.main
def build_config(self, config):
self.config = config
self.config.setdefaults('general',{'use widgets': 0,'print log': 0})
self.config.setdefaults('appearance',{'use colour': 0})
def build_settings(self, settings):
self.settings = settings
self.settings.add_json_panel('General', self.config, data=settings_general)
self.settings.add_json_panel('Appearance', self.config, data = settings_appearance)
def toggle_setting(self, active):
self.toggle = active
BasicApp().run()

How do I use text inputted into my kivy GUI as a string to use elsewhere?

I have created a very simple GUI in kivy and am trying to use it to send an e-mail to a specific user, how do I use the text inputted into the GUI as I am unfamiliar with GUI.
This is my code so far below:
import textwrap
import time
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
class Main(GridLayout):
def __init__(self,**kwargs):
super(Main, self).__init__(**kwargs)
self.cols = 2
self.add_widget(Label(text = "Who"))
self.Who = TextInput(multiline = True)
self.add_widget(self.Who)
self.add_widget(Label(text = "What"))
self.What = TextInput(multiline = True)
self.add_widget(self.What)
self.add_widget(Label(text = "Where"))
self.Where = TextInput(multiline = True)
self.add_widget(self.Where)
self.add_widget(Label(text = "When"))
self.When = TextInput(multiline = True)
self.add_widget(self.When)
self.add_widget(Label(text = "How"))
self.How = TextInput(multiline = True)
self.add_widget(self.How)
class AMAPP(App):
def build(self):
return Main()
def sendMail(FROM,TO,SUBJECT,TEXT,SERVER):
import smtplib
"""this is some test documentation in the function"""
message = textwrap.dedent("""\
From: %s
To: %s
Subject: %s
%s
""" % (FROM, ", ".join(TO), SUBJECT, TEXT))
# Send the mail
server = smtplib.SMTP(SERVER)
server.starttls()
server.login('E-mail', 'Password')
server.sendmail(FROM, TO, message)
server.quit()
FROM = 'me'
TO = 'you'
SUBJECT = 'test'
TEXT = (Who, What, Where, When, How)
SERVER = 'smtp.gmail.com'
sendMail(FROM,TO,SUBJECT,TEXT,SERVER)
if __name__ == "__main__":
AMAPP().run()
whenever I run this I get the same error:
line 66, in __init__
TEXT = (Who,What,Where,When,How)
NameError: name 'Who' is not defined
You have to give id to the Textinput widgets as you create them so that you can reference them to extract the text. Add a button so that you invoke the sendEmail method. Please refer to the example below for details.
Example
main.py
import textwrap
import time
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
FROM = 'me'
TO = 'you'
SUBJECT = 'test'
TEXT = ""
SERVER = 'smtp.gmail.com'
def sendMail(FROM, TO, SUBJECT, TEXT, SERVER):
import smtplib
"""this is some test documentation in the function"""
message = textwrap.dedent("""\
From: %s
To: %s
Subject: %s
%s
""" % (FROM, ", ".join(TO), SUBJECT, TEXT))
# Send the mail
server = smtplib.SMTP(SERVER, 587)
server.starttls()
server.login('E-mail', 'Password')
server.sendmail(FROM, TO, message)
server.quit()
class Main(BoxLayout):
def send_eMail(self):
global TEXT
print(self.ids.Who.text)
print(self.ids.What.text)
print(self.ids.Where.text)
print(self.ids.When.text)
print(self.ids.How.text)
TEXT = (self.ids.Who.text, self.ids.What.text,
self.ids.Where.text, self.ids.When.text, self.ids.How.text)
sendMail(FROM, TO, SUBJECT, TEXT, SERVER)
class AMAPP(App):
def build(self):
return Main()
if __name__ == "__main__":
AMAPP().run()
amapp.kv
#:kivy 1.10.0
<Main>:
orientation: "vertical"
GridLayout:
cols: 2
Label:
text: "Who"
TextInput:
id: Who
multiline: True
Label:
text: "What"
TextInput:
id: What
multiline: True
Label:
text: "Where"
TextInput:
id: Where
multiline: True
Label:
text: "When"
TextInput:
id: When
multiline: True
Label:
text: "How"
TextInput:
id: How
multiline: True
Button:
text: "Send Mail"
on_release: root.send_eMail()
Output
You were actually quite close with your code. Rather than doing a rewrite to kv code (which might be better practice), I simply added the button, fixed your dedent call, added the self.Field.text references, and it worked fine. Here's the edited code.
import textwrap
import time
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button # ADDED
EMAIL="<senders email address>" # ADDED
PASSWORD="<senders password>" # ADDED
class Main(GridLayout):
def __init__(self,**kwargs):
super(Main, self).__init__(**kwargs)
self.cols = 2
self.add_widget(Label(text = "Who"))
self.Who = TextInput(multiline = True)
self.add_widget(self.Who)
self.add_widget(Label(text = "What"))
self.What = TextInput(multiline = True)
self.add_widget(self.What)
self.add_widget(Label(text = "Where"))
self.Where = TextInput(multiline = True)
self.add_widget(self.Where)
self.add_widget(Label(text = "When"))
self.When = TextInput(multiline = True)
self.add_widget(self.When)
self.add_widget(Label(text = "How"))
self.How = TextInput(multiline = True)
self.add_widget(self.How)
self.add_widget(Button(text="Send",on_press=self.sendmail)) # ADDED
# ADDED function callable by the button press:
def sendmail(self,*args):
FROM = 'me'
TO = '<receivers email address>'
SUBJECT = 'test'
TEXT = '\n'.join([self.Who.text, self.What.text,
self.Where.text, self.When.text, self.How.text])
SERVER = 'smtp.gmail.com'
sendMail(FROM,[TO],SUBJECT,TEXT,SERVER) # watch out for the TO argument
class AMAPP(App):
def build(self):
return Main()
def sendMail(FROM,TO,SUBJECT,TEXT,SERVER):
import smtplib
"""this is some test documentation in the function"""
message = textwrap.dedent("""\
From: %s
To: %s
Subject: %s
%s
""") % (FROM, ", ".join(TO), SUBJECT, TEXT) # FIXED the dedent call
# Send the mail
server = smtplib.SMTP(SERVER)
server.starttls()
server.login(EMAIL,PASSWORD)
server.sendmail(FROM, TO, message)
server.quit()
if __name__ == "__main__":
AMAPP().run()
Warning, it is more involved if you want to send data TO the kivy GUI, because kivy has to know the data has changed. But that's beyond the scope of this question.

Resources