Welcome to part 2 of the Kivy tutorials. In this tutorial, we're going to be working on buttons and events.
Our objective here is to let the user type in the server IP, port, and pick a username, then join the server.
Code up to this point:
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout # one of many layout structures
from kivy.uix.textinput import TextInput # allow for ...text input.
kivy.require("1.10.1")
# An actual app is likely to consist of many different
# "pages" or "screens." Inherit from GridLayout
class ConnectPage(GridLayout):
# runs on initialization
def __init__(self, **kwargs):
# we want to run __init__ of both ConnectPage AAAAND GridLayout (understanding_super.py)
super().__init__(**kwargs)
self.cols = 2 # used for our grid
# widgets added in order, so mind the order.
self.add_widget(Label(text='IP:')) # widget #1, top left
self.ip = TextInput(multiline=False) # defining self.ip...
self.add_widget(self.ip) # widget #2, top right
self.add_widget(Label(text='Port:'))
self.port = TextInput(multiline=False)
self.add_widget(self.port)
self.add_widget(Label(text='Username:'))
self.username = TextInput(multiline=False)
self.add_widget(self.username)
class EpicApp(App):
def build(self):
return ConnectPage()
if __name__ == "__main__":
EpicApp().run()
Let's add a button. To do this, we just import the Button from Kivy's uix:
from kivy.uix.button import Button
Then we can define a button and assign it to a widget and all that fun stuff. The other thing we can do is bind methods to this button. So now, inside of our ConnectPage class, let's first add some code to the end of the __init__ method:
self.join = Button(text="Join")
self.add_widget(Label()) # just take up the spot.
self.add_widget(self.join)
The only thing that might be confusing is our self.add_widget(Label()). We're doing that to just take up the grid spot that is currently the lower-left. The submit button looks better to the right in my opinion. Later we can add grids inside of grids and make things more pretty, or use other layouts, but that's for another time. For now, this is good enough.
Result:
Alright, we've got a button, now we want things to happen when we press that button. To do this, we use the .bind method, so we can do something like:
self.join.bind(on_press=self.join_button)
So now our recent block looks more like:
self.join = Button(text="Join")
self.join.bind(on_press=self.join_button)
self.add_widget(Label()) # just take up the spot.
self.add_widget(self.join)
Now, let's create that join_button method that we just referenced.
def join_button(self, instance):
port = self.port.text
ip = self.ip.text
username = self.username.text
# Joining 127.0.0.1:1234 as sentdex
print(f"Joining {ip}:{port} as {username}")
Now, if you fill out the form and click submit, you should see the following in your console:
Joining 127.0.0.1:1234 as sentdex
Great. While we're here, I can't help but feel like we're going to type in that information possibly a lot when we use the application but also in development. Let's just save a file that contains the last-used details, and pre-populate the fields with them. They can still be changed, but it'd be nice to not have to re-enter the same stuff every time!
Full code up to this point:
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
# to use buttons:
from kivy.uix.button import Button
kivy.require("1.10.1")
class ConnectPage(GridLayout):
# runs on initialization
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 2 # used for our grid
self.add_widget(Label(text='IP:')) # widget #1, top left
self.ip = TextInput(multiline=False) # defining self.ip...
self.add_widget(self.ip) # widget #2, top right
self.add_widget(Label(text='Port:'))
self.port = TextInput(multiline=False)
self.add_widget(self.port)
self.add_widget(Label(text='Username:'))
self.username = TextInput(multiline=False)
self.add_widget(self.username)
# add our button.
self.join = Button(text="Join")
self.join.bind(on_press=self.join_button)
self.add_widget(Label()) # just take up the spot.
self.add_widget(self.join)
def join_button(self, instance):
port = self.port.text
ip = self.ip.text
username = self.username.text
# Joining 127.0.0.1:1234 as sentdex for example
print(f"Joining {ip}:{port} as {username}")
class EpicApp(App):
def build(self):
return ConnectPage()
if __name__ == "__main__":
EpicApp().run()
Now let's save the details of the fields when someone clicks the join button:
def join_button(self, instance):
port = self.port.text
ip = self.ip.text
username = self.username.text
with open("prev_details.txt","w") as f:
f.write(f"{ip},{port},{username}")
print(f"Joining {ip}:{port} as {username}")
Then we want to read it in during the __init__
class ConnectPage(GridLayout):
# runs on initialization
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 2 # used for our grid
# Read settings from text file, or use empty strings
if os.path.isfile("prev_details.txt"):
with open("prev_details.txt","r") as f:
d = f.read().split(",")
prev_ip = d[0]
prev_port = d[1]
prev_username = d[2]
else:
prev_ip = ''
prev_port = ''
prev_username = ''
...
Then populate the TextInputs using the text parm:
self.add_widget(Label(text='IP:')) # widget #1, top left
self.ip = TextInput(text=prev_ip, multiline=False) # defining self.ip...
self.add_widget(self.ip) # widget #2, top right
self.add_widget(Label(text='Port:'))
self.port = TextInput(text=prev_port, multiline=False)
self.add_widget(self.port)
self.add_widget(Label(text='Username:'))
self.username = TextInput(text=prev_username, multiline=False)
self.add_widget(self.username)
Full code now is:
import os
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
# to use buttons:
from kivy.uix.button import Button
kivy.require("1.10.1")
class ConnectPage(GridLayout):
# runs on initialization
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 2 # used for our grid
# Read settings from text file, or use empty strings
if os.path.isfile("prev_details.txt"):
with open("prev_details.txt","r") as f:
d = f.read().split(",")
prev_ip = d[0]
prev_port = d[1]
prev_username = d[2]
else:
prev_ip = ''
prev_port = ''
prev_username = ''
self.add_widget(Label(text='IP:')) # widget #1, top left
self.ip = TextInput(text=prev_ip, multiline=False) # defining self.ip...
self.add_widget(self.ip) # widget #2, top right
self.add_widget(Label(text='Port:'))
self.port = TextInput(text=prev_port, multiline=False)
self.add_widget(self.port)
self.add_widget(Label(text='Username:'))
self.username = TextInput(text=prev_username, multiline=False)
self.add_widget(self.username)
# add our button.
self.join = Button(text="Join")
self.join.bind(on_press=self.join_button)
self.add_widget(Label()) # just take up the spot.
self.add_widget(self.join)
def join_button(self, instance):
port = self.port.text
ip = self.ip.text
username = self.username.text
with open("prev_details.txt","w") as f:
f.write(f"{ip},{port},{username}")
print(f"Joining {ip}:{port} as {username}")
class EpicApp(App):
def build(self):
return ConnectPage()
if __name__ == "__main__":
EpicApp().run()
Now, every time you run the script, your previous connection details are saved you wont need to re-enter them. Woo.