PySide Model/View【第5弾】GUIでデータの検索・フィルタ機能!QSortFilterProxyModel

\ 迷ったらまずTechAcademyの無料カウンセリング! /

これまでの記事では QTableViewQTreeView を使ってデータを表示・編集する方法を学びました。
しかし実際のアプリケーションでは、ただ表示するだけでなく「検索」や「絞り込み」ができると格段に便利になります。

  • 表から「名前に 田中 を含む行」だけ表示したい
  • リストから「特定の条件に一致する項目」を抽出したい
  • 大量データを「昇順・降順」でソートしたい

こうした機能を ビューやモデルを直接いじらずに実現できる のが QSortFilterProxyModel です。
今回は ProxyModel を使った検索・フィルタ機能の基本を解説します。

目次

ProxyModelとは?

Qt では Model(データ)と View(表示)の間に「仲介役」として ProxyModel を挟むことができます。

[ 元モデル(QStandardItemModelなど) ]
                ↓ Proxyを通す
[ QSortFilterProxyModel(ソート/検索/絞り込み) ]
                ↓
[ QTableView / QListView などのビュー ]

基本コード例(検索フィルタ付きテーブル)

import sys
from PySide6.QtWidgets import QApplication, QMainWindow, QTableView, QLineEdit, QVBoxLayout, QWidget
from PySide6.QtGui import QStandardItemModel, QStandardItem
from PySide6.QtCore import QSortFilterProxyModel, Qt, QRegularExpression

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        # 元のモデルを作成
        model = QStandardItemModel()
        model.setHorizontalHeaderLabels(["名前", "年齢"])
        data = [("田中太郎", 25), ("佐藤花子", 30), ("鈴木一郎", 28), ("Sara", 34), ("Bob", 41)]
        for name, age in data:
            model.appendRow([QStandardItem(name), QStandardItem(str(age))])

        # Proxyモデルを作成(フィルタ機能付き)
        self.proxy = QSortFilterProxyModel()
        self.proxy.setSourceModel(model)
        self.proxy.setFilterKeyColumn(0)  # 名前の列でフィルタする
        # 大文字小文字を区別せずに検索(推奨)
        self.proxy.setFilterCaseSensitivity(Qt.CaseInsensitive)

        # テーブルビュー
        self.view = QTableView()
        self.view.setModel(self.proxy)

        # 検索入力欄
        self.search = QLineEdit()
        self.search.setPlaceholderText("名前で検索…")
        self.search.textChanged.connect(self.filter_changed)

        layout = QVBoxLayout()
        layout.addWidget(self.search)
        layout.addWidget(self.view)

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

    def filter_changed(self, text):
        # 正規表現を使ったフィルタ
        self.proxy.setFilterRegularExpression(text)

app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())

この例では、入力欄に「田中」と入力すると「田中太郎」だけが表示されるようになります。

主なプロパティとメソッド一覧(全解説)

Noメソッド/プロパティ説明
1setSourceModel(model)元となるモデルをセットする
2sourceModel()現在の元モデルを取得する
3setFilterKeyColumn(column)フィルタ対象の列を指定する
4filterKeyColumn()フィルタ対象列を取得する
5setFilterRegularExpression(pattern)フィルタ用の正規表現を設定する
6filterRegularExpression()現在の正規表現を取得する
7setFilterCaseSensitivity(Qt.CaseSensitivity)大文字・小文字の区別を設定する
8setSortCaseSensitivity(Qt.CaseSensitivity)ソート時の大文字小文字の扱いを指定
9setDynamicSortFilter(bool)データ変更時に自動で再フィルタリングするかを設定
10sort(column, order=Qt.AscendingOrder)指定列でソートする

各プロパティ・メソッド 詳細解説

【1】モデルをセット

概要:元になる「生の」モデル(例:QStandardItemModel やカスタムモデル)をプロキシに渡します。Proxy はこの元モデルのデータを仲介してビューに渡します。
メソッド:setSourceModel(model)
使い方:

from PySide6.QtGui import QStandardItemModel, QStandardItem
from PySide6.QtCore import QSortFilterProxyModel

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        # 元のモデルを作成
        model.setHorizontalHeaderLabels(["名前", "年齢"])
        data = [("田中太郎", 25), ("佐藤花子", 30), ("鈴木一郎", 28)]
        for name, age in data:
            model.appendRow([QStandardItem(name), QStandardItem(str(age))])

        # Proxyモデルを作成(フィルタ機能付き)
        self.proxy = QSortFilterProxyModel()
        self.proxy.setSourceModel(model)

【2】元モデルを取得

概要:現在プロキシが参照している元モデルを取得します。元モデルにアクセスして行追加・編集を行うと Proxy 経由のビューに反映されます。
メソッド:sourceModel()
使い方:

orig = self.proxy.sourceModel()
if orig is not None:
    print("元モデルの行数:", orig.rowCount())

# 元モデルの行数: 3

【3】フィルタ対象列を設定

概要:フィルタ(検索)がどの列を対象に行われるかを指定します。0 が1列目、-1 を指定すると全列を対象にできます。
メソッド:setFilterKeyColumn(column)
使い方:

# 名前が 1列目の名前(インデックス0)なら:
self.proxy.setFilterKeyColumn(0)

# 全列を対象にしたい場合:
self.proxy.setFilterKeyColumn(-1)

【4】フィルタ対象列を取得

概要:現在プロキシに設定されているフィルタ対象の列番号を取得します。UI の設定やデバッグで使います。
メソッド:filterKeyColumn()
使い方:

col = self.proxy.filterKeyColumn()
print("フィルタ対象列:", col)

# フィルタ対象列: 0

【5】正規表現フィルタを設定

概要:フィルタ条件を正規表現で指定します。部分一致・先頭一致・完全一致など柔軟に表現できます。QRegularExpression を使うと細かいオプションも設定可能です。

オプション説明
QRegularExpression.CaseInsensitiveOption大文字小文字を区別しないでマッチします。"abc""ABC" がマッチ
QRegularExpression.DotMatchesEverythingOption. が改行文字も含めてマッチします。"a.b""a\nb" にマッチ
QRegularExpression.MultilineOption^$ が行頭・行末にもマッチします。"^abc""xyz\nabc" にマッチ
QRegularExpression.ExtendedPatternSyntaxOption正規表現内の空白を無視し、# 以降をコメントとして扱えます。"a b c""abc" にマッチ
QRegularExpression.InvertedGreedinessOption量指定子がデフォルトで「最小一致」になります。"a.*b""acccb""ab" にマッチ
QRegularExpression.UseUnicodePropertiesOptionUnicode の文字クラスを利用してマッチします(国際化対応)。\w が日本語文字にもマッチ
QRegularExpression の主なオプション一覧

メソッド:setFilterRegularExpression(pattern)
使い方:

from PySide6.QtCore import QRegularExpression

# QRegularExpression を明示的に使う例(先頭一致)
rx = QRegularExpression("^田中")
self.proxy.setFilterRegularExpression(rx)

【6】現在の正規表現を取得

概要:プロキシに設定されている正規表現オブジェクトを取得します。QRegularExpression オブジェクトが返るため、パターンやオプションを確認できます。
メソッド:filterRegularExpression()
使い方:

rx = QRegularExpression("^田中")
self.proxy.setFilterRegularExpression(rx)

if rx.isValid():
    print("現在のパターン:", rx.pattern())
else:
    print("正規表現が未設定または無効です")

# 現在のパターン: ^田中

【7】フィルタの大文字小文字の扱いを設定

概要:フィルタマッチ時に大文字小文字を区別するか(Qt.CaseSensitive)しないか(Qt.CaseInsensitive)を設定します。日本語には直接影響しませんが英字の検索では重要です。
メソッド:setFilterCaseSensitivity(Qt.CaseSensitivity)
使い方:

from PySide6.QtCore import Qt

# 大文字小文字を区別せずに検索(推奨)
self.proxy.setFilterCaseSensitivity(Qt.CaseInsensitive)

【8】ソート時の大文字小文字の扱いを設定

概要:sort() 実行やビューによるソート時に、大文字小文字をどのように扱うかを指定します(フィルタとは別設定)。
メソッド:setSortCaseSensitivity(Qt.CaseSensitivity)
使い方:

from PySide6.QtCore import Qt

# ソートでも大文字小文字を区別しない
proxy.setSortCaseSensitivity(Qt.CaseInsensitive)
デフォルトQt.CaseInsensitive

【9】動的ソート/フィルタの自動更新

概要:True にすると、元モデルのデータが変更された際に Proxy が自動で再フィルタ/再ソートを行います(動的反映)。大量データで頻繁に更新する場合はパフォーマンスに注意が必要です。
メソッド:setDynamicSortFilter(bool)
使い方:

# 変更があるたびに自動で再評価したい場合
self.proxy.setDynamicSortFilter(True)

# パフォーマンス重視で手動更新にしたい場合
# self.proxy.setDynamicSortFilter(False)

動的フィルタを有効にしていると、sourceModel() 側で行を追加した際に自動的にフィルタが再評価されます。

【10】指定列でソートする

概要:Proxy 上で指定列を昇順/降順にソートします。ビュー側のヘッダでソートを有効化する場合は view.setSortingEnabled(True) を行ってください。
メソッド:sort(column, order=Qt.AscendingOrder)
使い方:

from PySide6.QtCore import Qt

# 2列目(インデックス1)を昇順でソート
self.proxy.sort(1, Qt.AscendingOrder)

# 1列目を降順でソート
# self.proxy.sort(1, Qt.DescendingOrder)

view.setSortingEnabled(True) を呼んでおけば、ユーザーがヘッダをクリックしたときに QSortFilterProxyModel 側で自動的に sort() が呼ばれます。

よくある質問 Q&A

複数列をフィルタしたい場合は?

setFilterKeyColumn(-1) を使うと全列対象になります。

部分一致ではなく完全一致にしたい場合は?

setFilterRegularExpression(f"^{text}$") のように正規表現で制御します。

ソートとフィルタを同時に使える?

はい、setDynamicSortFilter(True) にしておくと便利です。

数値の大小比較でフィルタしたい場合は?

QSortFilterProxyModel を継承して filterAcceptsRow() をオーバーライドします。

日本語の濁点やひらがな・カタカナの違いは?

Qt の正規表現依存なので、必要に応じて re モジュールで前処理するのも手です。

まとめ

本記事では QSortFilterProxyModel を使った検索・フィルタ機能を解説しました。
ProxyModel を使うことで「元データをそのままに」「ビュー側で簡単に」絞り込みや並べ替えが実現できます。

  • setSourceModel() で元モデルを渡す
  • setFilterKeyColumn()setFilterRegularExpression() で検索対象と条件を指定
  • sort() で列ごとの並び替えが可能
  • setDynamicSortFilter(True) にしておくとデータ更新も自動反映

今後の実装では、さらにカスタマイズした filterAcceptsRow() を使えば「数値条件」や「複雑な検索」も実現できます。
まずはシンプルなテキスト検索から試してみましょう。

シェアしてくださると嬉しいです!
  • URLをコピーしました!

コメント

コメントする

目次