How to add a screen onto another screen in kivy? - python

I'm trying to make a screen that has two rows, one of those rows are the actual game screen and the another one is a button to clear the table above. But the problem here is that the first row is blank when I run the app. That's the code that I am stuck till now:
import kivy
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
kivy.require('1.9.2')
class VelhaGame(GridLayout, Screen):
def __init__(self, **kwargs):
super(VelhaGame, self).__init__(**kwargs)
self.cols = 3
self.rows = 3
self.font_size = 100
self.buttons = []
for i in range(9):
button = Button(text='', font_size=self.font_size)
button.bind(on_release=self.player_turn)
self.add_widget(button)
self.buttons.append(button)
self.player1 = True
def player_turn(self, button, *args):
if self.player1 and button.text == '':
self.player1 = False
button.text = 'X'
elif not self.player1 and button.text == '':
self.player1 = True
button.text = 'O'
class MainMenu(GridLayout, Screen):
def __init__(self, **kwargs):
super(MainMenu, self).__init__(**kwargs)
self.cols = 1
self.rows = 2
self.button_play = Button(text='PLAY', font_size=40)
self.button_play.bind(on_release=self.play)
self.add_widget(self.button_play)
self.button_exit = Button(text='EXIT', font_size=40)
self.button_exit.bind(on_release=self.exit)
self.add_widget(self.button_exit)
def play(self, *args):
screen_manager.current = 'velhaPage'
def exit(self, *args):
App.get_running_app().stop()
game_screen_manager = ScreenManager()
game_screen_manager.add_widget(VelhaGame(name='velhaGame'))
class VelhaScreen(GridLayout, Screen):
def __init__(self, **kwargs):
super(VelhaScreen, self).__init__(**kwargs)
self.rows = 2
self.add_widget(game_screen_manager)
self.clear_button = Button(text='Clear', font_size=40)
self.add_widget(self.clear_button)
screen_manager = ScreenManager()
screen_manager.add_widget(MainMenu(name='menu'))
screen_manager.add_widget(VelhaScreen(name='velhaPage'))
class VelhaGameApp(App):
def build(self):
return screen_manager
if __name__ == '__main__':
VelhaGameApp().run()
Do you know what I could do to accomplish this task?

The answer to you question, is "You cannot do that".
Screenmanager only shows one screen at a time.
you can read about that in the docs https://kivy.org/docs/api-kivy.uix.screenmanager.html
But to acheive something like you are trying to, you could do like this:
Instead of screens inside another screen, you could put the gridlayout, and button inside a vertical orientated boxlayout like this:
import kivy
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.properties import ListProperty
kivy.require('1.9.1')
class VelhaGame(Screen):
buttons = ListProperty([])
def __init__(self, **kwargs):
super(VelhaGame, self).__init__(**kwargs)
self.game_window = BoxLayout(orientation="vertical")
self.add_widget(self.game_window)
self.game_table = GridLayout(cols=3,rows=3)
for i in range(9):
button = Button(text='', font_size="100sp")
button.bind(on_release=self.player_turn)
self.game_table.add_widget(button)
self.buttons.append(button)
self.player1 = True
self.clear_button = Button(on_press=self.clear,text='Clear', font_size="40sp")
self.game_window.add_widget(self.game_table)
self.game_window.add_widget(self.clear_button)
def clear(self,*args):
for button in self.buttons:
button.text = ""
def player_turn(self, button, *args):
if self.player1 and button.text == '':
self.player1 = False
button.text = 'X'
elif not self.player1 and button.text == '':
self.player1 = True
button.text = 'O'
class MainMenu(GridLayout, Screen):
def __init__(self, **kwargs):
super(MainMenu, self).__init__(**kwargs)
self.cols = 1
self.rows = 2
self.button_play = Button(text='PLAY', font_size="40sp")
self.button_play.bind(on_release=self.play)
self.add_widget(self.button_play)
self.button_exit = Button(text='EXIT', font_size=40)
self.button_exit.bind(on_release=self.exit)
self.add_widget(self.button_exit)
def play(self, *args):
screen_manager.current = 'velhaPage'
def exit(self, *args):
App.get_running_app().stop()
screen_manager = ScreenManager()
screen_manager.add_widget(MainMenu(name='menu'))
screen_manager.add_widget(VelhaGame(name='velhaPage'))
class VelhaGameApp(App):
def build(self):
return screen_manager
if __name__ == '__main__':
VelhaGameApp().run()

Related

Problem with Screen Manager in Kivy Python

What I want to do is to switch the screen when the "join" button is pressed.
This is what I have written.
import kivy
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
class ConnectingPage(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 2
self.add_widget(Label(text = "Usename:"))
self.username = TextInput(multiline=False)
self.add_widget(self.username)
self.add_widget(Label(text = "Password:"))
self.password = TextInput(multiline=False,password = True)
self.add_widget(self.password)
self.joinbutton = Button(text="Join")
self.joinbutton.bind(on_press = self.click_join_button)
self.add_widget(Label())
self.add_widget(self.joinbutton)
def click_join_button(self, instance):
username = self.username.text
password = self.password.text
info = "you can enter"
MyApp.screen_manager.current = "Info"
# Simple information/error page
class InfoPage(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 1
self.message = Label(text = "welcome",halign="center", valign="middle", font_size=30)
self.add_widget(self.message)
class MyApp(App):
def build(self):
self.screen_manager = ScreenManager()
self.connecting_page = ConnectingPage()
screen = Screen(name='Connect')
screen.add_widget(self.connecting_page)
# Info page
self.info_page = InfoPage()
screen = Screen(name='Info')
screen.add_widget(self.info_page)
return ConnectingPage()
MyApp().run()
Everything works fine in the starting but when the button "join" is pressed, this is the error:
AttributeError: type object 'MyApp' has no attribute 'self'
Please suggest what to do next.
Your build() method returns ConnectingPage(), so your App is not actually using a ScreenManager. All the code in that build() method (except that return) has no effect. Here is a modified version of your MyApp class that actually uses ScreenManager:
class MyApp(App):
screen_manager = ScreenManager() # this make screen_manager a class vaiable
def build(self):
# self.screen_manager = ScreenManager()
self.connecting_page = ConnectingPage()
screen = Screen(name='Connect')
screen.add_widget(self.connecting_page)
self.screen_manager.add_widget(screen) # add screen to ScreenManager
# Info page
self.info_page = InfoPage()
screen = Screen(name='Info')
screen.add_widget(self.info_page)
self.screen_manager.add_widget(screen) # add screen to ScreenManager
# return ConnectingPage()
return self.screen_manager
This allows your code:
MyApp.screen_manager.current = "Info"
to work, but it is an odd construction.
A more typical construction would be something like this:
class MyApp(App):
def build(self):
screen_manager = ScreenManager()
connecting_page = ConnectingPage()
screen = Screen(name='Connect')
screen.add_widget(connecting_page)
screen_manager.add_widget(screen) # add screen to ScreenManager
# Info page
info_page = InfoPage()
screen = Screen(name='Info')
screen.add_widget(info_page)
screen_manager.add_widget(screen) # add screen to ScreenManager
# return ConnectingPage()
return screen_manager # return the ScreenManager
But this requires a change in the code that changes the Screen:
App.get_running_app().root.current = "Info"

Is there a way to have a TextInput box that searches automatically searches a list of addresses?

I would like a user to be able to start typing an address in a TextInput box and as they start typing it is searching through a list and brining up matching options. Is this something that can be done in kivy?
I can find an example if it's not clear enough but they are seen everywhere on most websites where you've got to fil in an address (e.g delivery address)
Here is a start for another approach that uses Spinner:
from kivy.app import App
from kivy.properties import StringProperty, ListProperty
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.spinner import Spinner
class AddressChooser(FocusBehavior, Spinner):
addressfile = StringProperty()
addresslist = ListProperty([])
def __init__(self, **kwargs):
self.addressfile = kwargs.pop('addressfile', '')
self.sync_height = True
super(AddressChooser, self).__init__(**kwargs)
self.modifiers = []
self.bind(addressfile=self.load_addresses)
self.load_addresses()
def on_parent(self, widget, parent):
self.focus = True
def load_addresses(self):
if self.addressfile:
with open(self.addressfile) as fd:
for line in fd:
self.addresslist.append(line)
else:
self.addresslist = []
self.values = []
if len(self.text) > 0:
self.on_text(self, self.text)
def on_text(self, chooser, text):
values = []
for addr in self.addresslist:
if addr.startswith(text):
values.append(addr)
self.values = values
self.is_open = True
def keyboard_on_key_up(self, window, keycode):
if keycode[0] == 304:
self.modifiers.remove('shift')
super(AddressChooser, self).keyboard_on_key_up(window, keycode)
def keyboard_on_key_down(self, window, keycode, text, modifiers):
if keycode[0] == 304: # shift
self.modifiers.append('shift')
elif keycode[0] == 8 and len(self.text) > 0: # backspace
self.text = self.text[:-1]
else:
if 'shift' in self.modifiers:
self.text += text.upper()
else:
self.text += text
super(AddressChooser, self).keyboard_on_key_down(window, keycode, text, modifiers)
class TestApp(App):
def build(self):
layout = RelativeLayout()
chooser = AddressChooser(addressfile='adresses.txt', size_hint=(0.5,None), height=50, pos_hint={'center_x':0.5, 'center_y':0.5})
layout.add_widget(chooser)
return layout
if __name__ == '__main__':
TestApp().run()
Here is another approach that uses a TextInput with a DropDown:
# -*- encoding: utf-8 -*-
"""
Chooser
=======
Uses TextInput with a DropDown to choose from a list of choices
The 'choicesfile' attribute can be used to specify a file of possible choices (one per line)
The 'choiceslist' attribute can be used to provide a list of choices
When typing in the TextInput, a DropDown will show the possible choices
and a suggestion will be shown in the TextInput for the first choice.
Hitting enter will select the suggested choice.
"""
from kivy.properties import ListProperty, StringProperty
from kivy.uix.button import Button
from kivy.uix.dropdown import DropDown
from kivy.uix.textinput import TextInput
class Chooser(TextInput):
choicesfile = StringProperty()
choiceslist = ListProperty([])
def __init__(self, **kwargs):
self.choicesfile = kwargs.pop('choicesfile', '') # each line of file is one possible choice
self.choiceslist = kwargs.pop('choiceslist', []) # list of choices
super(Chooser, self).__init__(**kwargs)
self.multiline = False
self.halign = 'left'
self.bind(choicesfile=self.load_choices)
self.bind(text=self.on_text)
self.load_choices()
self.dropdown = None
def open_dropdown(self, *args):
if self.dropdown:
self.dropdown.open(self)
def load_choices(self):
if self.choicesfile:
with open(self.choicesfile) as fd:
for line in fd:
self.choiceslist.append(line.strip('\n'))
self.values = []
def keyboard_on_key_down(self, window, keycode, text, modifiers):
if self.suggestion_text and keycode[0] == ord('\r'): # enter selects current suggestion
self.suggestion_text = ' ' # setting suggestion_text to '' screws everything
self.text = self.values[0]
if self.dropdown:
self.dropdown.dismiss()
self.dropdown = None
else:
super(Chooser, self).keyboard_on_key_down(window, keycode, text, modifiers)
def on_text(self, chooser, text):
if self.dropdown:
self.dropdown.dismiss()
self.dropdown = None
if text == '':
return
values = []
for addr in self.choiceslist:
if addr.startswith(text):
values.append(addr)
self.values = values
if len(values) > 0:
if len(self.text) < len(self.values[0]):
self.suggestion_text = self.values[0][len(self.text):]
else:
self.suggestion_text = ' ' # setting suggestion_text to '' screws everything
self.dropdown = DropDown()
for val in self.values:
self.dropdown.add_widget(Button(text=val, size_hint_y=None, height=48, on_release=self.do_choose))
self.dropdown.open(self)
def do_choose(self, butt):
self.text = butt.text
if self.dropdown:
self.dropdown.dismiss()
self.dropdown = None
if __name__ == '__main__':
from kivy.app import App
from kivy.uix.relativelayout import RelativeLayout
class TestApp(App):
def build(self):
layout = RelativeLayout()
choices = ['Abba', 'dabba', 'doo']
chooser = Chooser(choiceslist=choices, hint_text='Enter one of Fred\'s words', size_hint=(0.5,None), height=30, pos_hint={'center_x':0.5, 'center_y':0.5})
layout.add_widget(chooser)
return layout
TestApp().run()

How to show each label in a row or column

In the code below I have a second screen, and I tried to add two buttons to this screen, but one button is on top of the other, I can see it when I use label instead.
I need to add each Label on a row or column because at the moment I cant even read the Label text.
This is how I add the label to new screen:
self.new_screen.add_widget(Label(text='LABEL NUMER 1'))
self.new_screen.add_widget(Label(text='THIS IS THE LABEL NUMBER 2'))
full code:
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
class Grid(GridLayout):
def __init__(self, **kwargs):
super(Grid, self).__init__(**kwargs)
self.rows= 3
self.titlet = Label(text='MAIN SCREEN')
self.add_widget(self.titlet)
self.MainGrid = GridLayout()
self.MainGrid.cols = 2
self.b4 = Button(text="Botao")
self.MainGrid.add_widget(self.b4)
self.add_widget(self.MainGrid)
class MyScreenManager(ScreenManager):
def __init__(self, **kwargs):
super(MyScreenManager, self).__init__(**kwargs)
self.main_screen = Screen(name="main_screen")
self.new_screen = Screen(name="new_screen")
self.add_widget(self.main_screen)
self.add_widget(self.new_screen)
self.grid = Grid()
self.grid.b4.bind(on_press=self.change_screen)
self.new_screen.cols = 2
self.new_screen.add_widget(Label(text='LABEL NUMER 1'))
self.new_screen.add_widget(Label(text='THIS IS THE LABEL NUMBER 2'))
self.main_screen
self.main_screen.add_widget(self.grid)
def change_screen(self, *args):
self.current = "new_screen"
class MyApp(App):
def build(self):
return MyScreenManager()
if __name__ == "__main__":
MyApp().run()
In the same way that you have established a layout on the first Screen you must do it with the second Screen, in this case you must use BoxLayout:
from kivy.uix.boxlayout import BoxLayout
# ...
class MyScreenManager(ScreenManager):
def __init__(self, **kwargs):
super(MyScreenManager, self).__init__(**kwargs)
self.main_screen = Screen(name="main_screen")
self.new_screen = Screen(name="new_screen")
self.add_widget(self.main_screen)
self.add_widget(self.new_screen)
self.grid = Grid()
self.grid.b4.bind(on_press=self.change_screen)
box_layout = BoxLayout(orientation="vertical")
self.new_screen.add_widget(box_layout)
box_layout.add_widget(Label(text='LABEL NUMER 1'))
box_layout.add_widget(Label(text='THIS IS THE LABEL NUMBER 2'))
self.main_screen.add_widget(self.grid)
# ...

Bind function to Kivy button

I'm trying to bind the following function to a Button in Kivy.
def auth(self):
print(self.username)
if self.username == "Hendricko":
print("self.username == Hendricko")
popup = Popup(title="success",
content=Label(text="Howdy !"),
size=(100, 100),
size_hint=(0.3, 0.3),
auto_dismiss=False)
popup.open()
I've tried
class Foo():
def initUI(self):
self.add_widget(Button(text="Auth User and Password", on_press=self.auth))
but this doesn't work. What am I doing wrong?
here is my whole code
from kivy.uix.popup import Popup
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.stacklayout import StackLayout
class LoginScreen(GridLayout):
def __init__(self, **kwargs):
super(LoginScreen, self).__init__(**kwargs)
self.cols = 2
self.row = 2
self.add_widget(Label(text='User Name'))
self.username = TextInput(multiline=False)
self.add_widget(self.username)
self.add_widget(Label(text='password'))
self.password = TextInput(password=True, multiline=False)
self.add_widget(self.password)
self.hello = Button(text="hello", on_press=self.auth)
self.add_widget(self.hello)
def auth(self):
if self.username == "Hendricko":
popup = Popup(title="success",
content=Label(text="Howdy !"),
size=(100, 100),
size_hint=(0.3, 0.3),
auto_dismiss=False)
popup.open()
class MyApp(App):
def build(self):
return LoginScreen()
if __name__ == '__main__':
MyApp().run()
I don't think any of the answers are very clear. Neither explains that problem is that the callback given to on_press gets called with a parameter, the instance of button, so LoginScreen.auth must accept a parameter after the self:
def auth(self, button):
print('button pressed:', instance)
The problem is not that on_press must be given via Button.bind or that the callback must be a function, it can be a bound method, and the docs cited by other answer and by comments link to ButtonbBhavior which indicates that OP use of on_press in constructor was fine:
self.hello = Button(text="hello", on_press=self.auth)
would have worked if auth had been as described above.
If you read the Button documentation, the key seems to be to use the bind function:
def callback(instance):
print('The button <%s> is being pressed' % instance.text)
btn1 = Button(text='Hello world 1')
btn1.bind(on_press=callback)
Replace line
self.hello = Button(text="hello", on_press=lambda a:self.auth())
of your code and use this :
self.hello = Button(text="hello", on_press=lambda a:self.auth())
Also add below line in auth function to see if its called :)
print "auth called"
and There are many ways to perform a particular task .Above code will be to fix your code in minimum effort , However If you would like to do it in another way . Just use code below .
from kivy.uix.popup import Popup
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.stacklayout import StackLayout
class LoginScreen(GridLayout):
def __init__(self, **kwargs):
super(LoginScreen, self).__init__(**kwargs)
self.cols = 2
self.row = 2
self.add_widget(Label(text='User Name'))
self.username = TextInput(multiline=False)
self.add_widget(self.username)
self.add_widget(Label(text='password'))
self.password = TextInput(password=True, multiline=False)
self.add_widget(self.password)
self.hello = Button(text="hello")
self.hello.bind(on_press=self.auth)
self.add_widget(self.hello)
def auth(self,instance):
print "auth called"
if self.username == "Hendricko":
popup = Popup(title="success",
content=Label(text="Howdy !"),
size=(100, 100),
size_hint=(0.3, 0.3),
auto_dismiss=False)
popup.open()
class MyApp(App):
def build(self):
return LoginScreen()
if __name__ == '__main__':
MyApp().run()
I report an example of buttons created dynamically within the main class, and then triggered into a single listener:
class allMyApp(TabbedPanel):
def __init__(self, **kwargs):
super(allMyApp, self).__init__(**kwargs)
#[...]
for y in sorted(set(emissionYears)):
btn = Button(text = y,
size_hint_y = None,
height = '48dp',
on_release = lambda btn: self.ids.choseEmissionDate.select(btn.text))
btn.bind(on_press = self.sortByYear)
self.ids.choseEmissionDate.add_widget(btn)
def sortByYear(self, instance):
year = instance.text
print(year)

adding an on_release action to a kivy button

I am trying to refactor the last code sample so that the Button is actually its own class with an on_release action. But my code is failing.
I want to not only refactor it (per my attempt below) but I also need to set the text of the Button to "Clear"
from random import random
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.graphics import Color, Ellipse, Line
class MyPaintWidget(Widget):
def on_touch_down(self, touch):
userdata = touch.ud
userdata['color'] = c = (random(), 1, 1)
with self.canvas:
Color(*c, mode='hsv')
d = 30
Ellipse(pos=(touch.x - d/2, touch.y - d/2), size=(d, d))
userdata['line'] = Line(points=(touch.x, touch.y))
def on_touch_move(self, touch):
touch.ud['line'].points += [touch.x, touch.y]
class ClearButton(Button):
def __init__(self, paint_widget):
self.paint_widget=paint_widget
def on_release(self, button):
self.paint_widget.canvas.clear()
class MyPaintApp(App):
def build(self):
parent = Widget()
painter = MyPaintWidget()
clearbtn = ClearButton(painter)
parent.add_widget(painter)
parent.add_widget(clearbtn)
return parent
if __name__ == '__main__':
MyPaintApp().run()
Without subclassing, you could just do:
class MyPaintWidget(Widget):
# ... put your previous methods here
def clear_canvas(self, *largs):
self.canvas.clear()
class MyPaintApp(App):
def build(self):
root = FloatLayout()
painter = MyPaintWidget()
cleanbtn.bind(on_release=self.painter.clear_canvas)
root.add_widget(painter)
root.add_widget(clearbtn)
return root
With subclassing, i would prefer to go with Kv langage:
from kivy.lang import Builder
Builder.load_string('''
<ClearButton>:
text: "Clear"
on_release: app.painter.canvas.clear()
''')
class ClearButton(Button):
pass
class MyPaintApp(App):
def build(self):
parent = Widget()
painter = MyPaintWidget()
clearbtn = ClearButton()
parent.add_widget(painter)
parent.add_widget(clearbtn)
return parent

Categories

Resources