I'm trying to build an interface in python with Kivy. To do this, i want to follow this structure:
[ScreenManager#1] -> [Login Screen]
[ScreenManager#2] -> [All others screens]
My ScreenManager#1 is declared in my App function in python file. My ScreenManager#2 is initialised in a kv file. To simply the help that you can bring to me, i've joined the two in the code below.
my AllScreensScreenManager#2, need to have a template shared with all the other screens. I've gave an id to it and call this one with my button in my LoginScreen, like this was suggested here. But the switch is not working. What i'm doing wrong with it.
import kivy
kivy.require('2.1.0') # replace with your current kivy version !
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager,Screen
Builder.load_string('''
<AllScreens>:
id: second_manager
manager: scr_manager
orientation: "vertical"
BoxLayout:
ScreenManager:
id: scr_manager
transition: "left"
Welcome:
GenerateScreen:
ModifyScreen:
ConsultScreen:
LogoutScreen:
BoxLayout:
size_hint: 1, None
height: "40dp"
pos_hint: {"bottom": 0}
spacing: "5dp"
Button:
id: consult
text: "Consult"
on_press: root.manager.current("consult")
Button:
id: modify
text: "Modify"
on_press: root.manager.current("modify")
Button:
id: generate
text: "Generate"
on_press: root.manager.current("generate")
Button:
id: logout
text: "Logout"
on_press: root.manager.current("logout")
<LoginScreen>:
name: "login"
BoxLayout:
Label:
text: "Login"
Button:
text: "Connect"
on_release: second_manager.current("welcome")
<WelcomeScreen>:
name: "welcome"
BoxLayout:
Label:
text: "welcome"
<ConsultScreen>:
name: "consult"
BoxLayout:
Label:
text: "consult"
<GenerateScreen>:
name: "generate"
BoxLayout:
Label:
text: "password"
''')
class ScreenManagement(ScreenManager):
pass
class LoginScreen(Screen):
print("login screen")
pass
class WelcomeScreen(Screen):
pass
class AllScreens(ScreenManager):
pass
class ConsultScreen(Screen):
pass
class GenerateScreen(Screen):
pass
class MyApp(App):
def build(self):
sm = ScreenManagement()
sm.add_widget(LoginScreen(name="login"))
return sm
if __name__ == '__main__':
MyApp().run()
I have spent a reasonable amount of time struggling to understand how to change the screen in python since I had to move the kv builder inside the build(self) function. I have tried using self.parent.current, app.root.current, kv.current (Which is what used to work) and self.manager.current functions and followed other stack overflow solutions such as:
Kivy: changing screen from python code
However, I get to a stage with each of these where I either have the same problem or it does not work.
I am attempting to change to the same screen no matter which button is pressed in a for loop and am still fairly new to this. Any help would be appreciated
The line of code in question is marked as:#THIS NEEDS TO CHANGE TO THE SPECIFIC FIND WINDOW
I changed some code due to adding string properties and it has thus stopped working.
main.py:
***
> import pickle
from click import command
from kivy.app import runTouchApp
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from datetime import datetime, date
from kivy.uix.scrollview import ScrollView
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.properties import StringProperty
from kivy.metrics import dp
from functools import partial
from settingsjson import settings_json
FindsFileName = "Finds.obj"
# Recipe for new find object created by instancing.
class NewFindBlueprint:
def __init__(self, Name=None, Date=None, Time=None, GPS=None, Photos=None, Description=None, Depth=None, Soil_Conditions=None, Weather_Conditions=None) -> None:
self.Name = Name
self.Date = Date
self.Time = Time
self.GPS = GPS
self.Photos = Photos
self.Description = Description
self.Depth = Depth
self.Soil_Conditions = Soil_Conditions
self.Weather_Conditions = Weather_Conditions
# The window on which the app is loaded, this can be used to help explain the purpose and use of the app.
class BaseWindow(Screen):
pass
# The base screen used for inputting a new find into the collection [base information of GPS, photos, name, date and time, as well as ony other information desired].
class InFieldFindInputWindow(Screen):
def __init__(self, **kw):
super().__init__(**kw)
# Keep for reference
#def HowToCollectDataFunction(self):
# print("val={0}".format(self.ids.NewFindName.text))
def RefreshDateAndTime(self):
pass
def AddNewFind(self):
NewFindName = self.ids.NewFindName.text
Current_Time = datetime.now()
Current_Time_HHMMSS = Current_Time.strftime("%H:%M:%S")
Current_Date = date.today()
Current_Date_DDMMYYYY=Current_Date.strftime("%d %B %y")
NewFindDate = Current_Date_DDMMYYYY
NewFindTime = Current_Time_HHMMSS
NewFindGPS = "1"
NewFindPhoto = "1"
if False:
NewFindDepth = self.ids.Depth.text
NewFindObject = NewFindBlueprint(Name=NewFindName,Date=Current_Date_DDMMYYYY,Time=Current_Time_HHMMSS)
print(NewFindObject.Name, NewFindObject.Date, NewFindObject.Time, NewFindObject.GPS)
InputFileObject = open(FindsFileName,"ab")
pickle.dump(NewFindObject,InputFileObject)
InputFileObject.close()
# Show all of the finds in a steack layout window with scroll compatibility, no matter the state.
class ViewFindsWindow(Screen):
pass
class AllFindsGridLayout(GridLayout):
FindButton={} # Create dictionary, used for holding the ids!!
FindInfoName = StringProperty("Name of Find")
FindInfoDate = StringProperty("Date of Find")
FindInfoTime = StringProperty("Time of Find")
def __init__(self, **kwargs):
super().__init__(**kwargs)
BackButton = Button(text="Back",color=(1,1,0,1))
BackButton.bind(on_release=self.BackToMainMenu)
self.add_widget(BackButton)
for Find in self.AllFinds:
self.FindButton[Find.Name] = Button(text = Find.Name, size_hint_y=None,height=dp(100))
self.add_widget(self.FindButton[Find.Name])
self.FindButton[Find.Name].bind(on_release=partial(self.ViewSpecificFindInfo, Find.Name, Find.Date, Find.Time ))
def LoadAllFinds(filename):
with open(filename, "rb") as f:
while True:
try:
yield pickle.load(f)
except EOFError:
break
AllFinds = LoadAllFinds(FindsFileName)
def ViewSpecificFindInfo(self,FindName,FindDate,FindTime,ObjectInfo):
print(FindName,FindDate,FindTime)
# First thing is code to change the info to display.
App.get_running_app().FindInfoName = FindName
App.get_running_app().FindInfoDate = FindDate
App.get_running_app().FindInfoTime = FindTime
# Secondly go to the screen to show the specific find.
#THIS NEEDS TO CHANGE TO THE SPECIFIC FIND WINDOW
# Return to Main Menu
def BackToMainMenu(self,event):
App.root_window="Base Window"
# Shows only finds which have been entered in the field but not updated at home yet. Works based on a set of standard key conditions to qualify as updated.
class AtHomeUpdateFindsWindow(Screen):
pass
# Shows the information of one specific find including images, 3D scans, name, gps coords, location in collection, etc.
class SpecificFindInfoWindow(Screen):
FindInfoName = StringProperty("Name of Find")
FindInfoDate = StringProperty("Date of Find")
FindInfoTime = StringProperty("Time of Find")
pass
# Allows to view all inputted permissions.
class PermissionsWindow(Screen):
pass
# Enables the addition of a permission zone, including name, area, phone number, date and time.
class AddPermissionsWindow(Screen):
pass
# Options window for disabling more opportunities.
class OptionsWindow(Screen):
pass
###
class WindowManager(ScreenManager):
pass
###
# Designating the design .kv file
#kv = Builder.load_file('MetalDetectorsFriend.kv')
class MetalDetectorsFriendApp(App):
FindInfoName = StringProperty("Name of Find")
FindInfoDate = StringProperty("Date of Find")
FindInfoTime = StringProperty("Time of Find")
def build(self):
pass #This loads the kv file as it must load before due to string properties
def build_config(self, config):
config.setdefaults('In Field Find Input',{
'DepthBoolean':True,
'GroundConditionBoolean':True,
'WeatherConditionsBoolean':True
})
def build_settings(self, settings):
settings.add_json_panel('In Field Find Options',self.config,data=settings_json)
if __name__ == "__main__":
MetalDetectorsFriendApp().run()
***
MetalDetectorsFriend.kv
***
> WindowManager:
BaseWindow:
InFieldFindInputWindow:
ViewFindsWindow:
AtHomeUpdateFindsWindow:
SpecificFindInfoWindow:
PermissionsWindow:
AddPermissionsWindow:
OptionsWindow:
<BaseWindow>:
name:"Base Window"
BoxLayout:
orientation:"vertical"
Label:
text:"Welcome to Metal Detectors Friend"
font_size:16
Button:
text:"Add new find"
on_release:
app.root.current = "In Field Find Input Window"
Button:
text:"View all finds"
on_release:
app.root.current = "View Finds Window"
Button:
text:"Options"
on_release:
app.open_settings()
Button:
text:"test"
on_release:
app.root.current = "Specific Find Info Window"
<InFieldFindInputWindow>:
name: "In Field Find Input Window"
BoxLayout:
orientation:"vertical"
BoxLayout:
orientation:"horizontal"
Button:
id: IFFIWBackButton
text: "Back"
on_release:
app.root.current = "Base Window"
size_hint:0.1,1
TextInput:
id: NewFindName
size_hint:0.9,1
BoxLayout:
orientation:"horizontal"
Label:
text: "Photos"
Label:
text: "Map"
Label:
text:"GPS coordinates"
TextInput:
id: Depth
disabled:True
TextInput:
id: SoilConditions
disabled:True
TextInput:
id: WeatherConditions
disabled:True
Button:
id: AddNewFindButton
text: "Add find to collection"
on_release:
root.AddNewFind()
# root.HowToCollectDataFunction() # Reference: How to use the function from Python
#app.root.current = "View Finds Window"
<ViewFindsWindow>:
name: "View Finds Window"
ScrollView:
do_scroll_x:False
do_scroll_y:True
GridLayout:
cols:1
size_hint_y:None
height:self.minimum_height
BoxLayout:
orientation:"horizontal"
size_hint_y:None
height:"100dp"
Button:
size_hint:0.2,1
text: "Back"
on_release:
app.root.current="Base Window"
Label:
text:"All Finds"
size_hint:0.8,1
AllFindsGridLayout:
cols: 2
padding: 10
spacing: 10
size_hint_y:None
height:self.minimum_height
##ScrollView:
##do_scroll_x:False
##do_scroll_y:True
##AllFindsGridLayout:
## cols: 2
## padding: 10
## spacing: 10
## size_hint_y:None
## height:self.minimum_height
#BoxLayout:
# orientation:"vertical"
# size_hint_y:None
# height:self.minimum_height
#BoxLayout:
# orientation:"horizontal"
# Button:
# text:"Back"
# on_release:
# app.root.current = "Base Window"
# size_hint:0.2,1
# Label:
# text:"All Finds"
# size_hint: 0.8,1
#TEST
<AtHomeUpdateFindsWindow>:
name: "At Home Update Finds Window"
<SpecificFindInfoWindow>:
name: "Specific Find Info Window"
ScrollView:
do_scroll_x:False
do_scroll_y:True
GridLayout:
cols:1
size_hint:1, None
height:self.minimum_height
#Test this:::
BoxLayout:
orientation:"horizontal"
size_hint_y:None
height:"50dp"
Button:
on_release:app.root.current = "Base Window"
text:"Back"
size_hint_x:0.2
Label:
text: "View specific find"
size_hint_x:0.8
BoxLayout:
orientation:"vertical"
size_hint_y:None
height:"100dp"
Label:
id: SpecificFindInfoName
text: app.FindInfoName
BoxLayout:
orientation:"horizontal"
size_hint_y:None
height:"100dp"
Label:
id: SpecificFindInfoDate
text:app.FindInfoDate
Label:
id: SpecificFindInfoTime
text:app.FindInfoTime
BoxLayout:
orientation:"horizontal"
size_hint_y:None
height:"400dp"
Button:
text:"Photos"
Button:
text:"Map"
BoxLayout:
orientation:"vertical"
size_hint_y:None
height:"400dp"
Button:
text:"WAYYYY YEEAH"
BoxLayout:
orientation:"vertical"
size_hint_y:None
height:"400dp"
Button:
text:"WAYYYY YEEAH"
#####
<PermissionsWindow>:
name: "Permissions Window"
<AddPermissionsWindow>:
name: "Add Permissions Window"
<OptionsWindow>:
name: "Options Window"
ScrollView:
do_scroll_x: False
do_scroll_y: True
GridLayout:
cols:1
size_hint_y:None
height:self.minimum_height
BoxLayout:
orientation:"horizontal"
size_hint_y:None
height:self.minimum_height
Button:
text:"Back"
on_release:
app.root.current = "Base Window"
size_hint_x:0.2
Label:
text:"In field input settings"
size_hint_y:None
height:"100dp"
size_hint_x:0.8
BoxLayout:
orientation:"vertical"
size_hint_y:None
height:self.minimum_height
BoxLayout:
orientation:"horizontal"
size_hint_y:None
height:"100dp"
Label:
text:"Depth enabled"
Switch:
id:Depth_Enabled
BoxLayout:
orientation:"horizontal"
size_hint_y:None
height:"100dp"
Label:
text:"Soil conditions enabled"
Switch:
id:Soil_Conditions_Enabled
BoxLayout:
orientation:"horizontal"
size_hint_y:None
height:"100dp"
Label:
text: "Weather Conditions enabled"
Switch:
id: Weather_Conditions_Enabled
Thanks for any help
If I understand your question correctly, you just need to use the current attribute of the ScreenManager and the name of the desired Screen. In your ViewSpecificFindInfo() method, just add:
App.get_running_app().root.current = "Specific Find Info Window"
This code get the current running App, then gets its root (which is WindowManager, in your code), and sets its current property to the name of the desired Screen.
I'm very new to Kivy (been using for about four hours...) and I've hit a wall with popups.
I have a main screen which has four buttons in a float layout. On press down I want the 'MOVE' button to open a popup. Now I've got this working but the popup contains the same four buttons as my mainscreen.
This is my Python code:
def show_movepop():
show = MovePop()
movepopWindow = Popup(title="Move", content=show, size_hint=(None, None),size=(400,400))
movepopWindow.open()
class MovePop(FloatLayout):
pass
class MainWindow(Screen):
def movebtn(self):
show_movepop()
class StatsWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
kv = Builder.load_file("gamegui.kv")
class MainFloatApp(App):
def build(self):
return kv
if __name__ == "__main__":
MainFloatApp().run()
and this is my .kv file:
WindowManager:
MainWindow:
StatsWindow:
<Button>
font_size:40
color:0.3,0.6,0.7,1
size_hint: 0.5, 0.1
<MainWindow>:
name: "mainscreen"
FloatLayout
Button:
text: "MOVE"
id: move
pos_hint: {"x":0, "y":0.1}
on_release: root.movebtn()
Button:
text: "ACTION"
id: action
pos_hint: {"x":0.5, "y":0.1}
Button:
text: "EXAMINE"
id: examine
pos_hint: {"x":0, "y":0}
Button:
text: "STATS"
id: stats
pos_hint: {"x":0.5, "y":0}
on_release:
app.root.current = "statsscreen"
root.manager.transition.direction = "left"
<StatsWindow>:
name: "statsscreen"
Button:
text: "Back"
on_release:
app.root.current = "mainscreen"
root.manager.transition.direction = "right"
<MovePop>:
Button:
text: "!"
pos_hint: {"x":0.1, "y":0.5}
on_release:
Apologies in advance if the above is super dirty, I'm not very efficient :')
All suggestions appreciated!
Okay so I don't know why but it was the FloatLayout that was causing the problem.
Changed
class MovePop(FloatLayout):
pass
to:
class MovePop(AnchorLayout):
pass
BoxLayout also got rid of the duplicate buttons but I couldn't arrange the content on the popup the way I wanted in that layout.
I was trying to use a new feature in KivyMD, the MDNavigationRail and wanted to give the icons in it a function. The goal is that the user could change to the desired screen by pressing the icon that represents it. I gave the icon an on_press. But something goes wrong, I get an error; ValueError: MDNavigationRail.state is set to an invalid option 'down'. Must be one of: ['close', 'open']. The rail should be open or closed I guess, isn't it possible to give it a function? Furthermore, I would want to know if it is possible to not break the text. If anyone could help me out, it would be very nice!
My .py file
from kivy.uix.screenmanager import ScreenManager
from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen
class Screen1(MDScreen):
def screen2(self):
self.manager.current = 'screen2'
class Screen2(MDScreen):
def screen1(self):
self.manager.current = 'screen1'
def rail_open(self):
if self.ids.rail.state == "open":
self.ids.rail.state = "close"
else:
self.ids.rail.state = "open"
class MyScreenManager(ScreenManager):
def __init__(self, **kwargs):
super(MyScreenManager, self).__init__(**kwargs)
class Test(MDApp):
def build(self):
return MyScreenManager()
Test().run()
My kv file
<MyScreenManager>:
Screen1:
id: screen1
name: 'screen1'
Screen2:
id: screen2
name: 'screen2'
<Screen1>:
id: screen1
MDFloatLayout:
MDRectangleFlatButton:
text: "Change to screen 2"
on_press: root.screen2()
pos_hint: {'center_x':0.5, 'center_y':0.5}
<Screen2>:
id: screen2
MDBoxLayout:
orientation: "vertical"
MDToolbar:
left_action_items: [["menu", lambda x: root.rail_open()]]
MDBoxLayout:
MDNavigationRail:
id: rail
elevation: 1
use_resizeable: True
MDNavigationRailItem:
icon: "home"
text: "homepage"
on_press: root.screen1()
MDNavigationRailItem:
icon: ""
text: ""
MDFloatLayout:
MDTextField:
id: field1
hint_text: "Enter something:"
size_hint_x: 0.4
pos_hint: {'center_x':0.25,'top':0.8}
It was a bug. Already fixed - https://github.com/kivymd/KivyMD/commit/8a31b0f3ccad9c2d9ad35d80953f7396f2dc78f2
When I click on Account(root.display_account()) then call display_account().After that RVACCOUNT() function call .After that when i click on +Add Account then def add_account(self): call
I have a class AccountPopup which define a attribute state_text and assign value text:'Testing' in .kv file
How to get value of state_text 'Testing' and pass in on_text: root.filter(self.text,state_text) and print in def filter function.
test.py
class AccountPopup(Popup):
state_text = ObjectProperty(None)
popupAccountCity = ObjectProperty(None)
def display_cities_treeview_account(self, instance):
if len(instance.text) > 0:
#if self.popupAccountCity is None:
self.popupAccountCity = TreeviewCityAccount(self.state_text.text)
self.popupAccountCity.filter(instance.text,self.state_text.text)
self.popupAccountCity.open()
class TreeviewCityAccount(Popup):
state_text = ObjectProperty(None)
def __init__(self,state_text, **kwargs):
print(state_text)
def filter(self, f,state):
print(state)
class RVACCOUNT(BoxLayout):
def add_account(self):
self.mode = "Add"
popup = AccountPopup(self)
popup.open()
class MainMenu(BoxLayout):
def display_account(self):
self.dropdown.dismiss()
self.remove_widgets()
self.rvaccount = RVACCOUNT()
self.content_area.add_widget(self.rvaccount)
class FactApp(App):
title = "Test"
def build(self):
self.root = Builder.load_file('test.kv')
return MainMenu()
if __name__ == '__main__':
FactApp().run()
test.kv
<AccountPopup>:
state_text:state_text
TextInput:
id:state_text
text:'Testing'
<TreeviewCityAccount>:
BoxLayout
orientation: "vertical"
TextInput:
id: treeview
size_hint_y: .1
on_text: root.filter(self.text,state_text)
<RVACCOUNT>:
BoxLayout:
orientation: "vertical"
Button:
size_hint: .07, .03
text: "+Add Account"
on_press: root.add_account()
<MainMenu>:
content_area: content_area
dropdown: dropdown
BoxLayout:
orientation: 'vertical'
#spacing : 10
BoxLayout:
canvas.before:
Rectangle:
pos: self.pos
size: self.size
MenuButton:
id: btn
text: 'Master'
size : (60,30)
on_release: dropdown.open(self)
CustDrop:
DropdownButton:
text: 'Account'
size_hint_y: None
height: '32dp'
on_release: root.display_account()
Can someone help me?
You should reference it as self.state_text everywhere, also make it a StringProperty in the py file and can than access it as
on_text: root.filter(self.text,root.state_text)
root in kv refers to the most left widget aka <TreeviewCityAccount>: in your case.
See https://kivy.org/docs/api-kivy.lang.html
Alternatively you can work with ids in the kv file.
the value you are looking for is not in your immediate root which is why this is not working.The say thing to do is get the full path to that property like so:
Snippet:
<AccountPopup>:
id: ac_popup
#bunch of code
<TreeviewCityAccount>:
#chunk of code
TextInput:
id: tree view
on_text:root.filter(self.text,app.ac_popup.state_text
Also,generally,it's a good idea to id your classes mate
Disclaimer:code not tested