Python packages!

When I started building the ReFilament GUI, I started with the Python package Tkinter. I had already heard of Tkinter and from what I could tell, it was relatively easy to use. Building the homepage only took about 10 minutes, so it seemed like a viable option. Then I started to play with formatting.

Tkinter creates GUIs that work with your native OS, but the GUIs don’t necessarily look as new as your OS might be. For instance, if you’re running Windows 7 or later, no matter what a Tkinter-powered GUI will look like it belongs in Windows 7. I’m running Linux on my laptop, and the Raspi is running Rasbian — so the Tkinter window came out looking really ugly. Any and all attempts to round out the buttons, make the background nicer, etc. were ultimately futile. It was important to me that our GUI look nice; the more updated the GUI in appearance, the more professional the project seems overall. Unfortunately, even looking into Ttk, which is supposed to allow a programmer to overwrite Tkinter’s default format, proved less helpful than I hoped. Plus some things I wanted to do in order to place buttons and text required one formatting method, but others required a different formatting method. According to Tkinter’s documentation, mixing methods is a bad idea.

Oh well — time to explore other packages!

I started looking into what other Python packages I could use to create a GUI. Kivy seemed like a good option, and it appeared quickly when I started searching. I decided to download someone else’s code and run it, just to see how what they had written behaved. At the same time, I started playing with PyQt. As far as I could tell from the internet, PyQt was the most highly recommended Python GUI package, and a friend from another PoE team suggested I check it out.

I got involved enough in PyQt that I sort of forgot I had experimented with Kivy. All of a sudden, I was writing our GUI in PyQt. I liked that PyQt’s app structure works similarly to other apps. I noticed my understanding of how to write a GUI in PyQt aligning with my understanding of how to write Android apps using Java and Android Studio. Plus, PyQt does have a slightly more updated look than that of Tkinter, so my exploration was fruitful.

I built the homepage in PyQt and basked in the glory of having accomplished something for our GUI. But then came a difficult challenge: figuring out how to switch between pages by pressing a button. I had started by initializing the homepage as a class; that was all the GUI was at that point. My next step was to create the form page, where the user inputs information about the recycling job they’re about to do. Then I started trying to figure out how to switch from the homepage to the form page at the press of a button. This took me nearly an entire week. It was so frustrating! No matter how hard I searched, I could hardly find any useful information on how to switch between screens.

At some point, I figured out that it would help to initialize a QMainWindow class, which creates a window for the GUI to live in. If your GUI is one page, you can create that window at the same time that you create the page. But if your GUI requires more than one page, you want a main window which isn’t attached to any one page — kind of like the Activity in an Android app.

The init for my QMainWindow class

Each page was still a QWidget, as it had originally been. The Android equivalent to this would be a Fragment. In order to switch between widgets, I would need to be able to add them to and remove them from the main window. In an Android app, I would be initializing my Fragment dynamically within the Activity. The widget (returning to PyQt terms) is what holds all elements of the page. For instance, if I want there to be a label which says “Hello world!” in the middle of my homepage, I would create a QLabel within the homepage widget, then set the QLabel’s text to “Hello world!”

The QWidget class for our GUI’s homepage
The homepage generated by the above code

Once both a QMainWindow and a QWidget have been initialized, the widget needs to be added to the window. From what I found, this is best done by establishing a CentralWidget for the window, which will be the main focus of the window. If this CentralWidget is a QStackedWidget, it can hold multiple widgets. All the widgets you add to that QStackedWidget can then be accessed from the main window.

Initializing a QStackedWidget as the CentralWidget for the QMainWindow
Adding widgets, like the home page, to the QStackedWidget also happens within the QMainWindow init

Now comes the part that took me forever to figure out and that frustrated me for days: adding functionality to the buttons I created in those widgets. If you look back at the screenshot of our homepage code, you’ll see that I created a button called self.next_button. Well, next_button is an attribute of the HomePage class because we need to be able to call it from the main window. In the image above, you’ll notice that after I added the HomePage widget to the main window’s QStackedWidget, I called next_button and passed it a function to use. That function needs to be accessible from the main window, so where did I create it? Within the main window init. And how much code went into this function?

The function which tells the QStackedWidget to display the form page when the homepage’s next_button is clicked
The form page, which now appears when the previous page’s next button is pressed

One line. It’s a one-line function. That took me a week to figure out. In the hopes of helping future PyQt tinkerers avoid the same frustrations, I will say plainly:

When creating a GUI using PyQt, keep these things in mind.

Create a main window.

Make each new page a widget.

Add all widgets to the main window through a stacked widget.

Make your buttons functional from the main window, not from their respective widgets.

Once I passed this hurdle, I was able to create the rest of our GUI. Now we have a functional GUI, and all of the buttons do what I want them to! Nathan and I have been working on reliably connecting the GUI to our Arduino, but the issue there lies more in port reliability and fringe cases than in the GUI. Once the GUI and the Arduino talk to each other properly, integration will be done!

--

--