Up until this point, we have been creating applications that subclass QWidget. These basic applications have consisted of various QWidgets nested inside of a main QWidget. In this tutorial, we will create an application that subclasses QMainWindow instead of QWidget. The difference is that QMainWindow, unlike QWidget, allows our application to have a particular layout structure that includes a menu bar and a status bar. You can have other things as well, like toolbars or dock widgets. Here’s a picture of the layout, taken from here:
1. Subclassing QMainWindow
For this tutorial, we will make a more complicated version of our “Hello Name” app. This new app will have two buttons, “reverse” and “capitalize”. Instead of showing “Hello <Name>”, the app will show either the name reversed or capitalized depending on which button was pressed.
This application will also subclass QMainWindow, so we can add a status bar and menu bar. Whenever you subclass QMainWindow, you must place all of your content inside of a QWidget, and then set that as the central widget. A QMainWindow must have a central widget. We first set up the app as you are used to – but notice that, in our defclass, we are subclassing QMainWindow instead of QWidget:
(ql:quickload :qt) (in-package :qt) (named-readtables:in-readtable :qt) (defclass status-bar-app () ((name-edit :accessor name-edit) (reverse-button :accessor reverse-button) (capital-button :accessor capital-button) (result-label :accessor result-label)) (:metaclass qt-class) (:qt-superclass "QMainWindow") (:slots ("reverse-name()" reverse-name) ("capitalize-name()" capitalize-name))) (defmethod initialize-instance :after ((instance status-bar-app) &key) (new instance) (init-ui instance))
We’ve also created two slots, called reverse-name and capitalize-name, which we intend to connect to our buttons later on. Now let’s take a look at init-ui. Here, we instantiate all of our elements using setf, and then add them to a QVBoxLayout. However, instead of setting this as the layout of our QMainWindow, we will set this as the layout of a QWidget that we then set as the central widget of the QMainWindow using setCentralWidget(). In the code below, we let the variable central-widget be our central QWidget.
(defmethod init-ui ((instance status-bar-app)) (#_setGeometry instance 400 400 300 200) (#_setWindowTitle instance "Status Bar Demo") (setf (name-edit instance) (#_new QLineEdit "" instance) (reverse-button instance) (#_new QPushButton "Reverse" instance) (capital-button instance) (#_new QPushButton "Capitalize" instance) (result-label instance) (#_new QLabel "" instance)) (let ((box (#_new QVBoxLayout instance)) (central-widget (#_new QWidget instance))) (#_addWidget box (#_new QLabel "Type your name..." instance)) (#_addWidget box (name-edit instance)) (#_addWidget box (reverse-button instance)) (#_addWidget box (capital-button instance)) (#_addWidget box (result-label instance)) (#_setLayout central-widget box) (#_setCentralWidget instance central-widget)) (#_setMinimumWidth (result-label instance) 270)
Finally, we write the methods for reversing and capitalizing the inputted name. These should look familiar:
(defmethod reverse-name ((instance status-bar-app)) (let ((name (#_text (name-edit instance)))) (#_setText (result-label instance) (if name (nreverse (princ-to-string name)) "ERROR")))) (defmethod capitalize-name ((instance status-bar-app)) (let ((name (#_text (name-edit instance)))) (#_setText (result-label instance) (if name (string-capitalize (princ-to-string name)) "ERROR")))) (make-qapplication) (with-main-window (window (make-instance 'status-bar-app)))
In the last step, as you see above, we start our application.
2. The Status Bar
Now that we have our basic application set up, we can start adding our status bar. The status bar is a horizontal bar at the bottom of the window that we can use to display little messages. To instantiate the status bar, we use the statusBar() method of QMainWindow. This returns our status bar, which we can give a starting message as follows:
(#_showMessage (#_statusBar instance) "Hello")
Place that line in init-ui, and now when you run the program you’ll see a status bar at the bottom.
Now that we have a status bar, we can set status tips on the various components of our app. When the user hovers over a component, its status tip will show up in the status bar. This is a good way to give the user some hints about how to use different components of the GUI. Here’s how you set these status tips:
(#_setStatusTip (name-edit instance) "Type name here!") (#_setStatusTip (reverse-button instance) "Reverses your name") (#_setStatusTip (capital-button instance) "Capitalizes your name")
Once again, this code will go inside of init-ui. Now, try running the program and hovering over the buttons and textbox to see what happens.
3. The Menu Bar
Finally, let’s add a menu bar to our code. The structure of a menu bar is an object that contains menus, which in turn contain actions. So, there are three steps to go through when we make a menu bar:
- Create actions. To have something happen when the action is clicked, connect the “triggered” signal to the appropriate slot.
- Add the actions to the appropriate menus.
- Add the menus to the menu bar.
Just like the status bar, there is no need to create a menu bar yourself. Instead, you can make one by calling the menuBar() method:
This will return the QMainWindow’s menubar. To create an menu, call the addMenu function on the menuBar, perhaps like this:
(#_addMenu (#_menuBar instance) "File")
The argument is the name of the menu. Finally, you can create an action by making an instance of the QAction class, like so:
(#_new QAction "Action" instance)
Let’s put this together. The following code will create a QAction and add it to a menu which is then added to the menubar:
(let ((action (#_new QAction "Action" instance))) (#_addAction (#_addMenu (#_menuBar instance) "File") action))
To make the actions do things when you click on them, connect the “triggered()” signal of the QAction to any slot that you want.