Buttons and Events - Kivy with Python Tutorials




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:

python tutorials

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.





  • Introduction to Kivy, a multi-platform GUI library for computers (Windows, Linux, Mac) and Mobile (iOS, Android) - Kivy with Python Tutorials
  • Buttons and Events - Kivy with Python Tutorials
  • Changing screens/views/pages with ScreenManager - Kivy with Python Tutorials
  • Scheduling tasks/functions/methods- Kivy with Python Tutorials
  • Chat Application Page - Kivy with Python Tutorials
  • Finishing Chat Application - Kivy with Python Tutorials
  • Packaging and Deployment (PyInstaller, Buildozer) - Kivy with Python Tutorials