Python Forum
How to filter a table view using SortFilterProxyModel
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How to filter a table view using SortFilterProxyModel
#1
Hi,

I have tried to create a sample program to help me figure out how the SortFilterProxyModel works in PyQT.

from PyQt5.QtWidgets import (QApplication, QMainWindow,
                             QTableWidget, QTableWidgetItem, QMenu, QAction, QDialog,
                             QInputDialog, QTableView,  QHeaderView, QLineEdit, QLabel, QVBoxLayout)
from PyQt5.QtGui import QIcon, QStandardItemModel

import sys

from PyQt5.QtCore import Qt, QSortFilterProxyModel, QModelIndex, QRegExp

headers = ["Word", "Meaning", ""]


NUMBER_OF_COLUMNS = 2
NUMBER_OF_ROWS = 6


class TableView(QTableView):
    def __init__(self, title, rows, columns):
        QTableView.__init__(self)

        self.proxyModel = SortFilterProxyModel()
        # This property holds whether the proxy model is dynamically sorted and filtered whenever the contents of the source model change
        self.proxyModel.setDynamicSortFilter(True)
        self.model = QStandardItemModel(
            rows, columns, self)
        self.model.setHeaderData(0, Qt.Horizontal, "Word")
        self.model.setHeaderData(1, Qt.Horizontal, "Meaning")
        self.setWindowTitle(title)
        self.setSampleData()
        self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        self.setAlternatingRowColors(True)
        self.setSortingEnabled(True)
        self.verticalHeader().hide()
        self.setSelectionMode(QTableView.SingleSelection)
        self.setSelectionBehavior(QTableView.SelectRows)
        self.clicked.connect(self.getItem)
        self.filterSTring = ""

    def setSampleData(self):
        self.model.setData(self.model.index(
            0, 0, QModelIndex()), "Abundance", Qt.DisplayRole)
        self.model.setData(self.model.index(
            0, 1, QModelIndex()), "A very large quantity of something", Qt.DisplayRole)
        self.model.setData(self.model.index(
            1, 0, QModelIndex()), "Belonging", Qt.DisplayRole)
        self.model.setData(self.model.index(
            1, 1, QModelIndex()), "An affinity for a place or situation.", Qt.DisplayRole)
        self.model.setData(self.model.index(
            2, 0, QModelIndex()), "Candor", Qt.DisplayRole)
        self.model.setData(self.model.index(
            2, 1, QModelIndex()), "The quality of being open and honest in expression; frankness", Qt.DisplayRole)
        self.proxyModel.setSourceModel(self.model)
        self.setModel(self.proxyModel)

    def setFilterString(self, string):
        self.filterString = "^" + string

    def getItem(self, index):

        mapped_index = self.proxyModel.mapToSource(index)
        item = self.model.itemFromIndex(mapped_index)
        print(item.text() + "  ")
        item = self.model.data(mapped_index)
        row = mapped_index.row()
        column = mapped_index.column()
        data = mapped_index.data()
        item = self.model.itemFromIndex(mapped_index)
        print(item.text() + "  " + str(row) + "  " + str(column) + "  " + data)

    def filterRegExpChanged(self):

        syntax = QRegExp.RegExp  # can be one of QRegExp.RegExp2, QRegExp.WildCard, QRegExp.RegExp2 etc, see https://doc.qt.io/qt-5/qregexp.html#PatternSyntax-enum
        caseSensitivity = Qt.CaseInsensitive
        regExp = QRegExp(self.filterString,
                         caseSensitivity, syntax)
        # This property holds the QRegExp used to filter the contents of the source model
        self.proxyModel.setFilterRegExp(regExp)


class SortFilterProxyModel(QSortFilterProxyModel):
    def __init__(self):
        super().__init__()

    def filterAcceptsRow(self, sourceRow, sourceParent):
        result = False
        if self.filterKeyColumn() == 0: # only interested in the first column

            index = self.sourceModel().index(sourceRow, 0, sourceParent)
            data = self.sourceModel().data(index)
            # we could additionally filter here on the data
            return True

        # Otherwise ignore
        return super(SortFilterProxyModel, self).filterAcceptsRow(sourceRow, sourceParent)


class WordSelector(QDialog):
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
        self.parent = parent
        self.lastStart = 0
        self.initUI()

    def initUI(self):

        self.table = TableView("Words",
                               NUMBER_OF_ROWS, NUMBER_OF_COLUMNS)
        layout = QVBoxLayout()
        self.filterLabel = QLabel("  Filter")
        layout.addWidget(self.filterLabel)
        self.filter = QLineEdit(self)
        self.filter.setStyleSheet(
            "background-color: #FFFFFF; padding:1px 1px 1px 1px")
        self.filter.setFixedWidth(120)
        self.filter.returnPressed.connect(self.lookupWord)
        layout.addWidget(self.filter)
        layout.addWidget(self.table)
        self.setGeometry(300, 300, 500, 300)
        self.setWindowTitle("Find and Replace")
        self.setLayout(layout)

    def lookupWord(self):
        self.table.setFilterString(self.filter.text())
        self.table.filterRegExpChanged()


def main(args):
    app = QApplication(args)

    wordSelector = WordSelector()
    wordSelector.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main(sys.argv)
I want to be able to filter the contents of the table view based on the filter I set in the filter text box.
So, for example, if I type "a", then only the words beginning with a will appear.
In time I will make the filter customisable, but this is my first attempt.

I can see filterRegExpChanged being invoked, but I never see the filter influencing the contents of the table.
Any suggestions. Am I missing something here?
Reply
#2
You are using the wrong model. The model for your view should be the SortFilterProxyModel. From the QtDocs for C++

        QTreeView *treeView = new QTreeView;
        MyItemModel *sourceModel = new MyItemModel(this);
        QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(this);

        proxyModel->setSourceModel(sourceModel);
        treeView->setModel(proxyModel);
Since you are subclasing QTableView, your model attribute needs to be a QSortFilterProxyModel, not a QStandardItemModel.
DrakeSoft likes this post
Reply
#3
If you just want to filter which rows are displayed:

from PyQt5.QtWidgets import (QApplication, QMainWindow,
                             QTableWidget, QTableWidgetItem, QMenu, QAction, QDialog,
                             QInputDialog, QTableView,  QHeaderView, QLineEdit, QLabel, QVBoxLayout)
from PyQt5.QtGui import QIcon, QStandardItemModel
 
import sys
 
from PyQt5.QtCore import Qt, QSortFilterProxyModel, QModelIndex, QRegExp
 
headers = ["Word", "Meaning", ""]
 
 
NUMBER_OF_COLUMNS = 2
NUMBER_OF_ROWS = 3
 
 
class TableView(QTableView):
    def __init__(self, title, rows, columns):
        QTableView.__init__(self)
 
        self.proxyModel = SortFilterProxyModel()
        # This property holds whether the proxy model is dynamically sorted and filtered whenever the contents of the source model change
        self.proxyModel.setDynamicSortFilter(True)
        self.model = QStandardItemModel(
            rows, columns, self)
        self.model.setHeaderData(0, Qt.Horizontal, "Word")
        self.model.setHeaderData(1, Qt.Horizontal, "Meaning")
        self.setWindowTitle(title)
        self.setSampleData()
        self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        self.setAlternatingRowColors(True)
        self.setSortingEnabled(True)
        self.verticalHeader().hide()
        self.setSelectionMode(QTableView.SingleSelection)
        self.setSelectionBehavior(QTableView.SelectRows)
        self.clicked.connect(self.getItem)
        self.filterSTring = ""
 
    def setSampleData(self):
        self.model.setData(self.model.index(
            0, 0, QModelIndex()), "Abundance", Qt.DisplayRole)
        self.model.setData(self.model.index(
            0, 1, QModelIndex()), "A very large quantity of something", Qt.DisplayRole)
        self.model.setData(self.model.index(
            1, 0, QModelIndex()), "Belonging", Qt.DisplayRole)
        self.model.setData(self.model.index(
            1, 1, QModelIndex()), "An affinity for a place or situation.", Qt.DisplayRole)
        self.model.setData(self.model.index(
            2, 0, QModelIndex()), "Candor", Qt.DisplayRole)
        self.model.setData(self.model.index(
            2, 1, QModelIndex()), "The quality of being open and honest in expression; frankness", Qt.DisplayRole)
        self.proxyModel.setSourceModel(self.model)
        self.setModel(self.proxyModel)
 
    def setFilterString(self, string):
        self.filterString = "^" + string
 
    def getItem(self, index):
 
        mapped_index = self.proxyModel.mapToSource(index)
        item = self.model.itemFromIndex(mapped_index)
        print(item.text() + "  ")
        item = self.model.data(mapped_index)
        row = mapped_index.row()
        column = mapped_index.column()
        data = mapped_index.data()
        item = self.model.itemFromIndex(mapped_index)
        print(item.text() + "  " + str(row) + "  " + str(column) + "  " + data)
 
    def filterRegExpChanged(self):
 
        syntax = QRegExp.RegExp  # can be one of QRegExp.RegExp2, QRegExp.WildCard, QRegExp.RegExp2 etc, see https://doc.qt.io/qt-5/qregexp.html#PatternSyntax-enum
        caseSensitivity = Qt.CaseInsensitive
        regExp = QRegExp(self.filterString,
                         caseSensitivity, syntax)
        # This property holds the QRegExp used to filter the contents of the source model
        self.proxyModel.setFilterRegExp(regExp)
 
class SortFilterProxyModel(QSortFilterProxyModel):
    def __init__(self):
        super().__init__()
 
    def filterAcceptsRow(self, sourceRow, sourceParent):
        result = False
        if self.filterKeyColumn() == 0: # only interested in the first column
 
            index = self.sourceModel().index(sourceRow, 0, sourceParent)
            data = self.sourceModel().data(index)
            # we could additionally filter here on the data
            return True
 
        # Otherwise ignore
        return super(SortFilterProxyModel, self).filterAcceptsRow(sourceRow, sourceParent)
 
 
class WordSelector(QDialog):
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
        self.parent = parent
        self.lastStart = 0
        self.initUI()
 
    def initUI(self):
 
        self.table = TableView("Words",
                               NUMBER_OF_ROWS, NUMBER_OF_COLUMNS)
        layout = QVBoxLayout()
        self.filterLabel = QLabel("  Filter")
        layout.addWidget(self.filterLabel)
        self.filter = QLineEdit(self)
        self.filter.setStyleSheet(
            "background-color: #FFFFFF; padding:1px 1px 1px 1px")
        self.filter.setFixedWidth(120)
        self.filter.textChanged.connect(self.findInTable)
        layout.addWidget(self.filter)
        layout.addWidget(self.table)
        self.setGeometry(300, 300, 500, 300)
        self.setWindowTitle("Find and Replace")
        self.setLayout(layout)

        
    def findInTable(self, text):      
        for row in range(NUMBER_OF_ROWS):
            row_text = self.table.proxyModel.data(self.table.proxyModel.index(row, 0), 0)
            if row_text:
                if row_text.lower().startswith(text.lower()):
                    print(self.table.proxyModel.data(self.table.proxyModel.index(row, 0), 0))
                    self.table.showRow(row)
                else:
                    self.table.hideRow(row)

 
def main(args):
    app = QApplication(args)
 
    wordSelector = WordSelector()
    wordSelector.show()
    sys.exit(app.exec_())
 
 
if __name__ == "__main__":
    main(sys.argv)
DrakeSoft likes this post
Reply
#4
There is an example at github PyQt5 Examples using QSortFilterProxyModel

https://raw.githubusercontent.com/PyQt5/...ermodel.py
DrakeSoft likes this post
Reply
#5
Thank you Alex and Deanhystad, I appreciate your help

I can understand how the findInTable method will allow me to hide the data I do not want to see, but I am still curious as to why my implementation is not working. I tried Deanhystads suggestion of using the QSortFilterProxyModel, but still could not get something to work using the subclassing approach.

I then tried a non-sub classed approach based on https://raw.githubusercontent.com/PyQt5/...ermodel.py

This is still not working for me, so I think I have some logic error or other flaw in the code that is probably preventing both implementations from working. I am going to use the findInTable approach for now, but I would love to understand where I am going wrong in the proxy model approach.

from PyQt5.QtWidgets import (QApplication, QMainWindow,
                             QTableWidget, QTableWidgetItem, QMenu, QAction, QDialog,
                             QInputDialog, QTableView,  QHeaderView, QLineEdit, QLabel, QVBoxLayout)
from PyQt5.QtGui import QIcon, QStandardItemModel

import sys

from PyQt5.QtCore import Qt, QSortFilterProxyModel, QModelIndex, QRegExp
from typing import Callable

headers = ["Word", "Meaning", ""]


NUMBER_OF_COLUMNS = 2
NUMBER_OF_ROWS = 3


class SortFilterProxyModel(QSortFilterProxyModel):
    def __init__(self):
        super().__init__()

    def filterAcceptsRow(self, sourceRow, sourceParent):
        result = False
        if self.filterKeyColumn() == 0:

            index = self.sourceModel().index(sourceRow, 0, sourceParent)
            data = self.sourceModel().data(index)
            if data:
                print("Soft Filter checking " + data)
            # we could additionally filter here on the data
            return True

        # Otherwise ignore
        return super(SortFilterProxyModel, self).filterAcceptsRow(sourceRow, sourceParent)

# Reference: https://doc.qt.io/archives/qtjambi-4.5.2_01/com/trolltech/qt/qtjambi-customfilter.html


class WordSelector(QDialog):
    def __init__(self,  title,  parent=None):
        QDialog.__init__(self,  parent)
        self.parent = parent
        self.lastStart = 0

        self.proxyModel = SortFilterProxyModel()
        # This property holds whether the proxy model is dynamically sorted and filtered whenever the contents of the source model change
        self.proxyModel.setDynamicSortFilter(True)

        self.sourceView = QTableView()
        self.sourceView.setAlternatingRowColors(True)

        self.proxyView = QTableView()
        self.proxyView.setAlternatingRowColors(True)
        self.proxyView.setModel(self.proxyModel)
        self.proxyView.setSortingEnabled(True)

        self.setWindowTitle(title)
        self.proxyView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.proxyView.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        self.proxyView.setAlternatingRowColors(True)
        self.proxyView.setSortingEnabled(True)
        self.proxyView.verticalHeader().hide()
        self.proxyView.setSelectionMode(QTableView.SingleSelection)
        self.proxyView.setSelectionBehavior(QTableView.SelectRows)
        self.proxyView.clicked.connect(self.getItem)
        self.filterSTring = ""
        self.initUI()

    def initUI(self):

        layout = QVBoxLayout()
        self.filterLabel = QLabel("  Filter")
        layout.addWidget(self.filterLabel)
        self.filter = QLineEdit(self)
        self.filter.setStyleSheet(
            "background-color: #FFFFFF; padding:1px 1px 1px 1px")
        self.filter.setFixedWidth(120)
        self.filter.returnPressed.connect(self.lookupWord)
        layout.addWidget(self.filter)
        layout.addWidget(self.proxyView)
        self.setGeometry(300, 300, 500, 300)
        self.setWindowTitle("Find and Replace")
        self.setLayout(layout)

    def setFilterString(self, string):
        self.filterString = "^" + string

    def getItem(self, index):

        mapped_index = self.proxyModel.mapToSource(index)
        item = self.model.itemFromIndex(mapped_index)
        print(item.text() + "  ")
        item = self.model.data(mapped_index)
        row = mapped_index.row()
        column = mapped_index.column()
        data = mapped_index.data()
        item = self.model.itemFromIndex(mapped_index)
        print(item.text() + "  " + str(row) + "  " + str(column) + "  " + data)

    def lookupWord(self):
        self.setFilterString(self.filter.text())
        self.filterRegExpChanged()

    def setSourceModel(self, model):
        self.proxyModel.setSourceModel(model)
        self.sourceView.setModel(model)

    def filterRegExpChanged(self):

        syntax = QRegExp.RegExp  # can be one of QRegExp.RegExp2, QRegExp.WildCard, QRegExp.RegExp2 etc, see https://doc.qt.io/qt-5/qregexp.html#PatternSyntax-enum
        caseSensitivity = Qt.CaseInsensitive
        regExp = QRegExp(self.filterString,
                         caseSensitivity, syntax)
        # This property holds the QRegExp used to filter the contents of the source model
        self.proxyModel.setFilterRegExp(regExp)


def addWord(model, word, meaning):
    model.insertRow(0)
    model.setData(model.index(0, 0), word)
    model.setData(model.index(0, 1), meaning)


def createModel(parent):

    model = QStandardItemModel(3, 2, parent)
    model.setHeaderData(0, Qt.Horizontal, "Word")
    model.setHeaderData(1, Qt.Horizontal, "Meaning")
    addWord(model, "Abundance", "A very large quantity of something.")
    addWord(model, "Belonging", "An affinity for a place or situation.")
    addWord(model, "Candor",
            "The quality of being open and honest in expression; frankness")
    return model


def main(args):
    app = QApplication(args)
    wordSelector = WordSelector("words")
    wordSelector.setSourceModel(createModel(wordSelector))
    wordSelector.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main(sys.argv)
Reply
#6
wordSelector.setSourceModel(createModel(wordSelector))

You're still using QStandardItemModel
Reply
#7
return True in filterAcceptsRow prevents the filter

from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, 
                             QTableWidget, QTableWidgetItem, QMenu, QAction, QDialog,
                             QInputDialog, QTableView,  QHeaderView, QLineEdit, QLabel, QVBoxLayout)
from PyQt5.QtGui import QIcon, QStandardItemModel
 
import sys
 
from PyQt5.QtCore import Qt, QSortFilterProxyModel, QModelIndex, QRegExp
from typing import Callable
 
headers = ["Word", "Meaning", ""]
 
 
NUMBER_OF_COLUMNS = 2
NUMBER_OF_ROWS = 3
 
 
class SortFilterProxyModel(QSortFilterProxyModel):
    def __init__(self):
        super().__init__()
 
    def filterAcceptsRow(self, sourceRow, sourceParent):
        result = False
        if self.filterKeyColumn() == 0:
 
            index = self.sourceModel().index(sourceRow, 0, sourceParent)
            data = self.sourceModel().data(index)
            if data:
                print("Soft Filter checking " + data)
            # we could additionally filter here on the data
            #return True
 
        # Otherwise ignore
        return super(SortFilterProxyModel, self).filterAcceptsRow(sourceRow, sourceParent)
 
# Reference: https://doc.qt.io/archives/qtjambi-4.5.2_01/com/trolltech/qt/qtjambi-customfilter.html
 
 
class WordSelector(QMainWindow):
    def __init__(self,  title,  parent=None):
        QDialog.__init__(self,  parent)
        self.parent = parent
        self.lastStart = 0
 
        self.proxyModel = SortFilterProxyModel()
        # This property holds whether the proxy model is dynamically sorted and filtered whenever the contents of the source model change
        self.proxyModel.setDynamicSortFilter(True)
 
        self.sourceView = QTableView()
        self.sourceView.clicked.connect(self.getItem)
        self.sourceView.setAlternatingRowColors(True)
 
        self.proxyView = QTableView()
        self.proxyView.setAlternatingRowColors(True)
        self.proxyView.setModel(self.proxyModel)
        self.proxyView.setSortingEnabled(True)
 
        self.proxyView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.proxyView.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        self.proxyView.setAlternatingRowColors(True)
        self.proxyView.setSortingEnabled(True)
        self.proxyView.verticalHeader().hide()
        self.proxyView.setSelectionMode(QTableView.SingleSelection)
        self.proxyView.setSelectionBehavior(QTableView.SelectRows)
        
        self.filterSTring = ""
 
        layout = QVBoxLayout()
        self.filterLabel = QLabel("  Filter")
        layout.addWidget(self.filterLabel)
        self.filter = QLineEdit(self)
        self.filter.setStyleSheet(
            "background-color: #FFFFFF; padding:1px 1px 1px 1px")
        self.filter.setFixedWidth(120)
        self.filter.textChanged.connect(self.lookupWord)
        layout.addWidget(self.filter)
        layout.addWidget(self.sourceView)
        layout.addWidget(self.proxyView)
        self.setWindowTitle("Find and Replace")
        wid = QWidget()
        wid.setLayout(layout)
        self.setCentralWidget(wid)
 
    def setFilterString(self, string):
        self.filterString = "^" + string
 
    def getItem(self, index):
        value=index.sibling(index.row(),index.column()).data()
        row = index.row()
        column = index.column()
        print(f'Row: {row} Column: {column} Value: {value}')
 
    def lookupWord(self):
        self.setFilterString(self.filter.text())
        self.filterRegExpChanged()
 
    def setSourceModel(self, model):
        self.proxyModel.setSourceModel(model)
        self.sourceView.setModel(model)
 
    def filterRegExpChanged(self):
 
        syntax = QRegExp.RegExp  # can be one of QRegExp.RegExp2, QRegExp.WildCard, QRegExp.RegExp2 etc, see https://doc.qt.io/qt-5/qregexp.html#PatternSyntax-enum
        caseSensitivity = Qt.CaseInsensitive
        regExp = QRegExp(self.filterString,
                         caseSensitivity, syntax)
        # This property holds the QRegExp used to filter the contents of the source model
        self.proxyModel.setFilterRegExp(regExp)
 
 
def addWord(model, word, meaning):
    model.insertRow(0)
    model.setData(model.index(0, 0), word)
    model.setData(model.index(0, 1), meaning)
 
 
def createModel(parent):
 
    model = QStandardItemModel(3, 2, parent)
    model.setHeaderData(0, Qt.Horizontal, "Word")
    model.setHeaderData(1, Qt.Horizontal, "Meaning")
    addWord(model, "Abundance", "A very large quantity of something.")
    addWord(model, "Belonging", "An affinity for a place or situation.")
    addWord(model, "Candor",
            "The quality of being open and honest in expression; frankness")
    return model
 
 
def main(args):
    app = QApplication(args)
    wordSelector = WordSelector("words")
    wordSelector.setSourceModel(createModel(wordSelector))
    wordSelector.setGeometry(0, 0, 500, 600)
    wordSelector.show()
    sys.exit(app.exec_())
 
 
if __name__ == "__main__":
    main(sys.argv)
DrakeSoft likes this post
Reply
#8
Thank Alex,

That was it! I had initially tried to do the filtering in filterAcceptsRow, and put in the return True as a way of disabling it. I think I should have been using this as follows then:

class SortFilterProxyModel(QSortFilterProxyModel):
    def __init__(self):
        super().__init__()

    def filterAcceptsRow(self, sourceRow, sourceParent):
        result = False
        if self.filterKeyColumn() == 0: # if we are interested in checking the first column's (0) data for validity
            index = self.sourceModel().index(sourceRow, 0, sourceParent)
            data = self.sourceModel().data(index)
            if data:
                print("Additional Validity Checking for: " + data)
            # we could additionally filter here on the data and return true or false explicitly
            # for example:
            # if my additional check is not met:
            #    return False
            # else:  # allow the proxy filter we have set up with self.proxyModel.setFilterRegExp(regExp) decide if this should be included.
            #    return super(SortFilterProxyModel, self).filterAcceptsRow(sourceRow, sourceParent)

        # Otherwise ignore
        return super(SortFilterProxyModel, self).filterAcceptsRow(sourceRow, sourceParent)
Regarding:
You're still using QStandardItemModel

I used that in the non-subclassed implementation because that is what the example at https://raw.githubusercontent.com/PyQt5/...ermodel.py used. I understood deanhystad's comments to mean that if subclassing you use QSortFilterProxyModel, but I am not sure how relevant that is to this example because once I fixed the filterAcceptsRow function the subclassed model also works with QStandardItemModel. I have included the working subclassed version for reference.
My understanding is that the source model should (or can be) QStandardItemModel, but the proxy model must be QSortFilterProxyModel.

from PyQt5.QtWidgets import (QApplication, QMainWindow,
                             QTableWidget, QTableWidgetItem, QMenu, QAction, QDialog,
                             QInputDialog, QTableView,  QHeaderView, QLineEdit, QLabel, QVBoxLayout)
from PyQt5.QtGui import QIcon, QStandardItemModel

import sys

from PyQt5.QtCore import Qt, QSortFilterProxyModel, QModelIndex, QRegExp

headers = ["Word", "Meaning", ""]


NUMBER_OF_COLUMNS = 2
NUMBER_OF_ROWS = 6


class TableView(QTableView):
    def __init__(self, title, rows, columns):
        QTableView.__init__(self)

        self.proxyModel = SortFilterProxyModel()
        # This property holds whether the proxy model is dynamically sorted and filtered whenever the contents of the source model change
        self.proxyModel.setDynamicSortFilter(True)
        self.model = QStandardItemModel(
            rows, columns, self)
        self.model.setHeaderData(0, Qt.Horizontal, "Word")
        self.model.setHeaderData(1, Qt.Horizontal, "Meaning")
        self.setWindowTitle(title)
        self.setSampleData()
        self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        self.setAlternatingRowColors(True)
        self.setSortingEnabled(True)
        self.verticalHeader().hide()
        self.setSelectionMode(QTableView.SingleSelection)
        self.setSelectionBehavior(QTableView.SelectRows)
        self.clicked.connect(self.getItem)
        self.filterSTring = ""

    def setSampleData(self):
        self.model.setData(self.model.index(
            0, 0, QModelIndex()), "Abundance", Qt.DisplayRole)
        self.model.setData(self.model.index(
            0, 1, QModelIndex()), "A very large quantity of something", Qt.DisplayRole)
        self.model.setData(self.model.index(
            1, 0, QModelIndex()), "Belonging", Qt.DisplayRole)
        self.model.setData(self.model.index(
            1, 1, QModelIndex()), "An affinity for a place or situation.", Qt.DisplayRole)
        self.model.setData(self.model.index(
            2, 0, QModelIndex()), "Candor", Qt.DisplayRole)
        self.model.setData(self.model.index(
            2, 1, QModelIndex()), "The quality of being open and honest in expression; frankness", Qt.DisplayRole)
        self.proxyModel.setSourceModel(self.model)
        self.setModel(self.proxyModel)

    def setFilterString(self, string):
        self.filterString = "^" + string

    def getItem(self, index):

        mapped_index = self.proxyModel.mapToSource(index)
        item = self.model.itemFromIndex(mapped_index)
        print(item.text() + "  ")
        item = self.model.data(mapped_index)
        row = mapped_index.row()
        column = mapped_index.column()
        data = mapped_index.data()
        item = self.model.itemFromIndex(mapped_index)
        print(item.text() + "  " + str(row) + "  " + str(column) + "  " + data)

    def filterRegExpChanged(self):

        syntax = QRegExp.RegExp  # can be one of QRegExp.RegExp2, QRegExp.WildCard, QRegExp.RegExp2 etc, see https://doc.qt.io/qt-5/qregexp.html#PatternSyntax-enum
        caseSensitivity = Qt.CaseInsensitive
        regExp = QRegExp(self.filterString,
                         caseSensitivity, syntax)
        # This property holds the QRegExp used to filter the contents of the source model
        self.proxyModel.setFilterRegExp(regExp)


class SortFilterProxyModel(QSortFilterProxyModel):
    def __init__(self):
        super().__init__()

    def filterAcceptsRow(self, sourceRow, sourceParent):
        result = False
        if self.filterKeyColumn() == 0:  # only interested in the first column

            index = self.sourceModel().index(sourceRow, 0, sourceParent)
            data = self.sourceModel().data(index)
            # we could additionally filter here on the data
            # return True
        # Otherwise ignore
        return super(SortFilterProxyModel, self).filterAcceptsRow(sourceRow, sourceParent)


class WordSelector(QDialog):
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
        self.parent = parent
        self.lastStart = 0
        self.initUI()

    def initUI(self):

        self.table = TableView("Words",
                               NUMBER_OF_ROWS, NUMBER_OF_COLUMNS)
        layout = QVBoxLayout()
        self.filterLabel = QLabel("  Filter")
        layout.addWidget(self.filterLabel)
        self.filter = QLineEdit(self)
        self.filter.setStyleSheet(
            "background-color: #FFFFFF; padding:1px 1px 1px 1px")
        self.filter.setFixedWidth(120)
        self.filter.returnPressed.connect(self.lookupWord)
        layout.addWidget(self.filter)
        layout.addWidget(self.table)
        self.setGeometry(300, 300, 500, 300)
        self.setWindowTitle("Find and Replace")
        self.setLayout(layout)

    def lookupWord(self):
        self.table.setFilterString(self.filter.text())
        self.table.filterRegExpChanged()


def main(args):
    app = QApplication(args)

    wordSelector = WordSelector()
    wordSelector.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main(sys.argv)
Thank you for all of your help. I really appreciate it.
Reply
#9
I got it
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020