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.