Search a QTablewidget and select matching items
Finding and selecting matching items in a QTableWidget

We've previously covered how to use search in a QTableView. However, if you're using QTableWidget instead of model views you may still want to be able to search through the items in the table and highlight or select them. In this quick tutorial we'll show how to do that.

Searching in a QTableWidget is handled with the .findItems method. The method definition from the documentation is shown below (converted to Python).

python
[QTableWidgetItem] = QTableWidget.findItems(str, Qt.MatchFlags)

This tells us that the method accepts a str "text" to search, and a Qt.MatchFlags "flags" object which is what determines how we search. The .findItems method when called will return a list of QTableWidgetItem objects. These objects are the actual data items in the table. Once you have them, you can select a given item by calling .setCurrentItem and passing in an individual item, or for multiple-selection calling .setSelected(True) on the item itself.

Below is a quick example showing a QTableWidget with a QLineEdit search box. As you type in the box matching items are highlighted in the table view.

python

from PyQt5.QtWidgets import QTableWidget, QLineEdit, QPushButton, QApplication, QMainWindow, QVBoxLayout, QWidget, QTableWidgetItem
from PyQt5.QtCore import Qt

import random, string, sys


class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()


        self.query = QLineEdit()
        self.query.setPlaceholderText("Search...")
        self.query.textChanged.connect(self.search)

        n_rows = 50
        n_cols = 4

        self.table = QTableWidget()
        self.table.setRowCount(n_rows)
        self.table.setColumnCount(n_cols)

        for c in range(0, n_cols):
            for r in range(0, n_rows):
                s = ''.join(random.choice(string.ascii_lowercase) for n in range(10))
                i = QTableWidgetItem(s)
                self.table.setItem(c, r, i)

        layout = QVBoxLayout()

        layout.addWidget(self.query)
        layout.addWidget(self.table)

        w = QWidget()
        w.setLayout(layout)
        self.setCentralWidget(w)

    def search(self, s):
        # Clear current selection.
        self.table.setCurrentItem(None)

        if not s:
            # Empty string, don't search.
            return

        matching_items = self.table.findItems(s, Qt.MatchContains)
        if matching_items:
            # We have found something.
            item = matching_items[0]  # Take the first.
            self.table.setCurrentItem(item)


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

app.exec_()
python

from PySide2.QtWidgets import QTableWidget, QLineEdit, QPushButton, QApplication, QMainWindow, QVBoxLayout, QWidget, QTableWidgetItem
from PySide2.QtCore import Qt

import random, string, sys


class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()


        self.query = QLineEdit()
        self.query.setPlaceholderText("Search...")
        self.query.textChanged.connect(self.search)

        n_rows = 50
        n_cols = 4

        self.table = QTableWidget()
        self.table.setRowCount(n_rows)
        self.table.setColumnCount(n_cols)

        for c in range(0, n_cols):
            for r in range(0, n_rows):
                s = ''.join(random.choice(string.ascii_lowercase) for n in range(10))
                i = QTableWidgetItem(s)
                self.table.setItem(c, r, i)

        layout = QVBoxLayout()

        layout.addWidget(self.query)
        layout.addWidget(self.table)

        w = QWidget()
        w.setLayout(layout)
        self.setCentralWidget(w)

    def search(self, s):
        # clear current selection.
        self.table.setCurrentItem(None)

        if not s:
            # Empty string, don't search.
            return

        matching_items = self.table.findItems(s, Qt.MatchContains)
        if matching_items:
            # we have found something
            item = matching_items[0]  # take the first
            self.table.setCurrentItem(item)


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

app.exec_()

python

from PyQt6.QtWidgets import QTableWidget, QLineEdit, QPushButton, QApplication, QMainWindow, QVBoxLayout, QWidget, QTableWidgetItem
from PyQt6.QtCore import Qt

import random, string, sys


class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()


        self.query = QLineEdit()
        self.query.setPlaceholderText("Search...")
        self.query.textChanged.connect(self.search)

        n_rows = 50
        n_cols = 4

        self.table = QTableWidget()
        self.table.setRowCount(n_rows)
        self.table.setColumnCount(n_cols)

        for c in range(0, n_cols):
            for r in range(0, n_rows):
                s = ''.join(random.choice(string.ascii_lowercase) for n in range(10))
                i = QTableWidgetItem(s)
                self.table.setItem(c, r, i)

        layout = QVBoxLayout()

        layout.addWidget(self.query)
        layout.addWidget(self.table)

        w = QWidget()
        w.setLayout(layout)
        self.setCentralWidget(w)

    def search(self, s):
        # clear current selection.
        self.table.setCurrentItem(None)

        if not s:
            # Empty string, don't search.
            return

        matching_items = self.table.findItems(s, Qt.MatchFlag.MatchContains)
        if matching_items:
            # we have found something
            item = matching_items[0]  # take the first
            self.table.setCurrentItem(item)


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

app.exec()
python

from PySide6.QtWidgets import QTableWidget, QLineEdit, QPushButton, QApplication, QMainWindow, QVBoxLayout, QWidget, QTableWidgetItem
from PySide6.QtCore import Qt

import random, string, sys


class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()


        self.query = QLineEdit()
        self.query.setPlaceholderText("Search...")
        self.query.textChanged.connect(self.search)

        n_rows = 50
        n_cols = 4

        self.table = QTableWidget()
        self.table.setRowCount(n_rows)
        self.table.setColumnCount(n_cols)

        for c in range(0, n_cols):
            for r in range(0, n_rows):
                s = ''.join(random.choice(string.ascii_lowercase) for n in range(10))
                i = QTableWidgetItem(s)
                self.table.setItem(c, r, i)

        layout = QVBoxLayout()

        layout.addWidget(self.query)
        layout.addWidget(self.table)

        w = QWidget()
        w.setLayout(layout)
        self.setCentralWidget(w)

    def search(self, s):
        # clear current selection.
        self.table.setCurrentItem(None)

        if not s:
            # Empty string, don't search.
            return

        matching_items = self.table.findItems(s, Qt.MatchContains)
        if matching_items:
            # we have found something
            item = matching_items[0]  # take the first
            self.table.setCurrentItem(item)


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

app.exec_()

The search method receives the textChanged signal from the QLineEdit as s. We first clear the current selection to simplify the conditions below: we don't need to clear on empty string and if there are no matches. Next we check if s is empty (not s is True if the string is empty). If it is we return without searching, leaving the table with no selection.

This step is necessary because calling .findItems with an empty string will return a list containing all currently selected items and None for non-selected items. This isn't what we want!

Once we know we have a search string we call .findItems with a Qt.MatchContains search, which will return any items containing the search string. If there are items, we take the first via matching_items[0] and set it as the current item (the currently selected item). If not, we clear the selection.

If you type a search string in the line input, you'll see the first matching item in the table selected (and highlighted) like below.

Search selecting a single item in the table Search selecting a single item in the table

If you would like to select all matching items in the table, you can iterate the items and use .setSelected(True) instead. The modified search method is shown below.

python

class MainWindow(QMainWindow):

    # ... __init__ unchanged


    def search(self, s):
        # Clear current selection.
        self.table.setCurrentItem(None)

        if not s:
            # Empty string, don't search.
            return

        matching_items = self.table.findItems(s, Qt.MatchContains)
        if matching_items:
            # We have found something.
            for item in matching_items:
                item.setSelected(True)

This works similarly to the previous example. First we clear the active selection: here it is more important to do this, as we need to ensure selected items are unselected when the search string is extended. As before we then check if s is empty, then perform the search. However, instead of setting only a single item we call setSelected on each item to mark it as selected. You'll see any matching items in the table highlighted as you type.

Search selecting a single item in the table Search selecting multiple items in the table

Below is a a short demo video.

You can experiment with different matching types for your search. Below are the available options in Qt.MatchFlags -- for example starts with, contains, etc. and whether the search is case sensitive.

Flag Value Search type
Qt.MatchContains 1 The search term is contained in the item.
Qt.MatchStartsWith 2 The search term matches the start of the item.
Qt.MatchEndsWith 3 The search term matches the end of the item.
Qt.MatchWildcard 5 Performs string-based matching using a string with wildcards as the search term.
Qt.MatchFixedString 8 Performs string-based matching. String-based comparisons are case-insensitive unless the MatchCaseSensitive flag is also specified.
Qt.MatchRegularExpression 9 Performs string-based matching using a regular expression as the search term. Uses QRegularExpression. When using this flag, a QRegularExpression object can be passed as parameter and will directly be used to perform the search. The case sensitivity flag will be ignored as the QRegularExpression object is expected to be fully configured. This enum value was added in Qt 5.15.
Qt.MatchCaseSensitive 16 The search is case sensitive.

You can combine MatchCaseSensitive with other searches to modify case matching behavior. For example Qt.MatchContains | Qt.MatchCaseSensitive would do a case-sensitive contains match.

For PyQt6 you must use the fully-qualified flag name, for example Qt.MatchFlag.MatchFixedString.

Continue reading

Creating searchable widget dashboards  pyside

Dashboard applications are a popular way to display live data and user controls, whether interacting with APIs or controlling locally attached devices. However, as more controls are added, dashboards can quickly get out of control, making it hard to find the controls you want when you want them. One common … More