Major issue with some of the code in the tutorial on searchable dashboards

There are some issues with the code in the tutorial on searchable dashboards here: Creating searchable dashboards in PyQt5 GUIs, widget filters and text prediction. There were one or two import errors at the beginning that I was able to figure out, but then I reached the section beginning with the header " Building our main application layout" - below it says “Now we have completed our custom control widget, we can finish the layout of the main application. The full code is shown at first, and then explained in steps below.” The code immediately below that does not make sense. It imports a SearchBar class from our custom widgets file that does not exist, and it raises several errors when run. It also makes the MainWindow a subclass of QWidgets instead of QMainWindow. I am not sure where it comes from.
At the end of the tutorial, the correct final code is provided. Perhaps the tutorial was edited and the code not entirely updated.

The customwidgets file should be created as part of the tutorial, where it says –

Create a new file called customwidgets.py in the same folder as app.py . We will define our custom widget here, then import it into our main application code.

The custom widget code goes in this file, and then is imported into app.py.

I’ll add notes so it’s clearer which file is which when working on them. I can see it could get confusing where it switches back and forth.

I’m sorry, I should have been clearer about where the issue was. The code is not all wrong, and most of the problem (outside of some code that uses modules without importing them at the beginning) is in a single section, beginning with the header " Building our main application layout."
Here it is:

from PyQt5 import QtWidgets, uic
import sys
from PyQt5.QtWidgets import (QWidget, QLineEdit, QLabel, QPushButton, QScrollArea,QApplication, QHBoxLayout, QVBoxLayout)
from PyQt5.QtCore import QObject, Qt, pyqtSignal
from PyQt5.QtGui import QPainter, QFont, QColor, QPen

from customwidgets import OnOffWidget, Searchbar

class MainWindow(QWidget):

    def __init__(self, *args, **kwargs):
        super().__init__()

        self.controls = QWidget()  # Controls container widget.
        self.controlsLayout = QVBoxLayout()   # Controls container layout.
        # List of names, widgets are stored in a dictionary by these keys.
        widget_names = [
            "Heater", "Stove", "Living Room Light", "Balcony Light", 
            "Fan", "Room Light", "Oven", "Desk Light", 
            "Bedroom Heater", "Wall Switch"
        ]
        self.widgets = []

        # Iterate the names, creating a new OnOffWidget for 
        # each one, adding it to the layout and 
        # and storing a reference in the `self.widgets` list
        for name in widget_names:
            item = OnOffWidget(name)
            self.controlsLayout.addWidget(item)
            self.widgets.append(item)

        spacer = QSpacerItem(1, 1, QSizePolicy.Minimum, QSizePolicy.Expanding)
        self.controlsLayout.addItem(spacer)
        self.controls.setLayout(self.controlsLayout)

        # Scroll Area Properties.
        self.scroll = QScrollArea()
        self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.scroll.setWidgetResizable(True)
        self.scroll.setWidget(self.controls)

        # Search bar.
        self.searchbar = QLineEdit()

        # Add the items to VBoxLayout (applied to container widget) 
        # which encompasses the whole window.
        container = QWidget()
        containerLayout = QVBoxLayout()
        containerLayout.addWidget(self.searchbar)
        containerLayout.addWidget(self.scroll)

        container.setLayout(containerLayout)
        self.setCentralWidget(container)

        self.setGeometry(600, 100, 800, 600)
        self.setWindowTitle('Control Panel')

        
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())

There are a number of issues with this code - first, there is no Searchbar class in customwidgets.py. We are only shown how to make the OnOffWidget class. Secondly, there are modules imported that are never used and the MainWindow is changed to a subclass of QWidget from one of QMainWindow. There are many errors that come up when you run this as written. I tried to edit it to account for the differences but then realised that the correct final code for app.py is at the bottom. It does work, and is significantly different:

# app.py
import sys
from PyQt5.QtWidgets import (QMainWindow, QWidget, QScrollArea, QApplication, QLineEdit,
                             QHBoxLayout, QVBoxLayout, QCompleter, QSpacerItem, QSizePolicy)
from PyQt5.QtCore import Qt
from customwidgets import OnOffWidget


class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super().__init__()

        self.controls = QWidget()  # Controls container widget.
        self.controlsLayout = QVBoxLayout()   # Controls container layout.

        # List of names, widgets are stored in a dictionary by these keys.
        widget_names = [
            "Heater", "Stove", "Living Room Light", "Balcony Light", 
            "Fan", "Room Light", "Oven", "Desk Light", 
            "Bedroom Heater", "Wall Switch"
        ]
        self.widgets = []

        # Iterate the names, creating a new OnOffWidget for 
        # each one, adding it to the layout and 
        # and storing a reference in the `self.widgets` dict
        for name in widget_names:
            item = OnOffWidget(name)
            self.controlsLayout.addWidget(item)
            self.widgets.append(item)

        spacer = QSpacerItem(1, 1, QSizePolicy.Minimum, QSizePolicy.Expanding)
        self.controlsLayout.addItem(spacer)
        self.controls.setLayout(self.controlsLayout)

        # Scroll Area Properties.
        self.scroll = QScrollArea()
        self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.scroll.setWidgetResizable(True)
        self.scroll.setWidget(self.controls)

        # Search bar.
        self.searchbar = QLineEdit()
        self.searchbar.textChanged.connect(self.update_display)

        # Adding Completer.
        self.completer = QCompleter(widget_names)
        self.completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.searchbar.setCompleter(self.completer)

        # Add the items to VBoxLayout (applied to container widget) 
        # which encompasses the whole window.
        container = QWidget()
        containerLayout = QVBoxLayout()
        containerLayout.addWidget(self.searchbar)
        containerLayout.addWidget(self.scroll)

        container.setLayout(containerLayout)
        self.setCentralWidget(container)

        self.setGeometry(600, 100, 800, 600)
        self.setWindowTitle('Control Panel')

    def update_display(self, text):

        for widget in self.widgets:
            if text.lower() in widget.name.lower():
                widget.show()
            else:
                widget.hide()


app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())

I think that perhaps the author intended to remove the other code but forgot.

Ah, I see it now, thanks for clarifying! I’ve updated the code just now to fix the imports/code so it works at each step (and matches the final result).

The example we were using for the article got simplified at some point, but the edits I made to the earlier parts weren’t complete. Thanks for spotting it.

1 Like

Thank you very much for correcting it, and for doing it so promptly! This is a great website and I appreciate all the work you put into it.

1 Like