このページでは

カスタムソート/フィルタモデルの例

「カスタムソート/フィルタモデル」の例では、QSortFilterProxyModel をサブクラス化して高度なソートやフィルタリングを行う方法を示しています。

「カスタムソート/フィルタモデル」の例のスクリーンショット

QSortFilterProxyModel クラスは、別のモデルとビューの間で受け渡されるデータのソートおよびフィルタリングをサポートします。

このモデルは、自身が提供するモデルインデックスを、ビューが使用する異なる位置に対応する新しいインデックスにマッピングすることで、ソースモデルの構造を変換します。このアプローチにより、基盤となるデータへの変換やメモリ内でのデータの複製を必要とすることなく、ビューの観点からは特定のソースモデルの構造を再構築することができます。

「カスタムソート/フィルタモデル」のサンプルは、次の2つのクラスで構成されています。

  • MySortFilterProxyModel クラスは、カスタムプロキシモデルを提供します。
  • Window クラスは、カスタムプロキシモデルを使用して標準のアイテムモデルをソートおよびフィルタリングする、メインのアプリケーションウィンドウを提供します。

まず、MySortFilterProxyModel クラスを確認してカスタムプロキシモデルがどのように実装されているかを調べ、次にWindow クラスを確認してモデルがどのように使用されているかを調べます。最後に、main() 関数について簡単に見ていきます。

MySortFilterProxyModel クラスの定義

MySortFilterProxyModel クラスは、QSortFilterProxyModel クラスを継承しています。

QAbstractProxyModel およびそのサブクラスはQAbstractItemModel から派生しているため、通常のモデルのサブクラス化に関するアドバイスの多くは、プロキシモデルにも当てはまります。

一方、QSortFilterProxyModel の関数のデフォルト実装の多くは、関連するソースモデルの同等の関数を呼び出すように記述されている点に留意する必要があります。この単純なプロキシ機構は、より複雑な動作を持つソースモデルではオーバーライドする必要があるかもしれません。 この例では、QSortFilterProxyModel クラスを継承することで、フィルタが有効な日付範囲を認識できるようにし、ソート動作を制御します。

class MySortFilterProxyModel : public QSortFilterProxyModel
{
    Q_OBJECT

public:
    MySortFilterProxyModel(QObject *parent = nullptr);

    QDate filterMinimumDate() const { return minDate; }
    void setFilterMinimumDate(QDate date);

    QDate filterMaximumDate() const { return maxDate; }
    void setFilterMaximumDate(QDate date);

protected:
    bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
    bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;

private:
    bool dateInRange(QDate date) const;

    QDate minDate;
    QDate maxDate;
};

特定の期間を指定してデータをフィルタリングできるようにしたいと考えています。そのため、カスタムsetFilterMinimumDate() およびsetFilterMaximumDate() 関数に加え、対応するfilterMinimumDate() およびfilterMaximumDate() 関数を実装します。QSortFilterProxyModel のfilterAcceptsRow()関数を再実装し、有効な日付を持つ行のみを受け入れるようにし、QSortFilterProxyModel::lessThan()関数を再実装して、送信者をメールアドレス順に並べ替えられるようにします。最後に、日付が有効かどうかを判定するために使用する、dateInRange() という便利関数を実装します。

MySortFilterProxyModel クラスの実装

MySortFilterProxyModel のコンストラクタは単純で、parentパラメータを基底クラスのコンストラクタに渡すだけです:

MySortFilterProxyModel::MySortFilterProxyModel(QObject *parent)
    : QSortFilterProxyModel(parent)
{
}

MySortFilterProxyModel の実装において最も注目すべき部分は、QSortFilterProxyModel のfilterAcceptsRow()およびlessThan()関数の再実装です。まず、カスタマイズしたlessThan() 関数を見てみましょう。

bool MySortFilterProxyModel::lessThan(const QModelIndex &left,
                                      const QModelIndex &right) const
{
    QVariant leftData = sourceModel()->data(left);
    QVariant rightData = sourceModel()->data(right);

送信者をメールアドレス順に並べ替えたいと考えています。lessThan()関数は、並べ替えの際の<演算子として使用されます。デフォルトの実装では、QDateTime やStringなどの型のコレクションを扱えますが、送信者をメールアドレス順に並べ替えるためには、まず指定された文字列の中からメールアドレスを特定する必要があります:

    if (leftData.userType() == QMetaType::QDateTime)
        return leftData.toDateTime() < rightData.toDateTime();

    static const QRegularExpression emailPattern(u"[\\w\\.]*@[\\w\\.]*"_s);

    QString leftString = leftData.toString();
    if (left.column() == 1) {
        const QRegularExpressionMatch match = emailPattern.match(leftString);
        if (match.hasMatch())
            leftString = match.captured(0);
    }
    QString rightString = rightData.toString();
    if (right.column() == 1) {
        const QRegularExpressionMatch match = emailPattern.match(rightString);
        if (match.hasMatch())
            rightString = match.captured(0);
    }

    return QString::localeAwareCompare(leftString, rightString) < 0;
}

QRegularExpression を使用して、対象となるメールアドレスのパターンを定義します。match()関数は、マッチングの結果を含むQRegularExpressionMatch オブジェクトを返します。一致がある場合、hasMatch()はtrueを返します。 一致の結果は、QRegularExpressionMatch のcaptured()関数を使用して取得できます。一致した部分全体にはインデックス0が割り当てられ、括弧で囲まれた部分式には1から始まるインデックスが割り当てられます(非キャプチャ括弧は除く)。

bool MySortFilterProxyModel::filterAcceptsRow(int sourceRow,
                                              const QModelIndex &sourceParent) const
{
    QModelIndex index0 = sourceModel()->index(sourceRow, 0, sourceParent);
    QModelIndex index1 = sourceModel()->index(sourceRow, 1, sourceParent);
    QModelIndex index2 = sourceModel()->index(sourceRow, 2, sourceParent);

    return (sourceModel()->data(index0).toString().contains(filterRegularExpression())
            || sourceModel()->data(index1).toString().contains(filterRegularExpression()))
            && dateInRange(sourceModel()->data(index2).toDate());
}

一方、filterAcceptsRow() 関数は、指定された行をモデルに含めるべき場合に true を返すものと想定されています。この例では、件名または送信者に指定された正規表現が含まれており、かつ日付が有効である場合に、その行が受け入れられます。

bool MySortFilterProxyModel::dateInRange(QDate date) const
{
    return (!minDate.isValid() || date > minDate)
            && (!maxDate.isValid() || date < maxDate);
}

日付が有効かどうかを判定するために、独自のdateInRange() 関数を使用します。

指定された期間でデータをフィルタリングできるようにするため、最小日付と最大日付を取得・設定するための関数も実装します:

void MySortFilterProxyModel::setFilterMinimumDate(QDate date)
{
    beginFilterChange();
    minDate = date;
    endFilterChange(QSortFilterProxyModel::Direction::Rows);
}

void MySortFilterProxyModel::setFilterMaximumDate(QDate date)
{
    beginFilterChange();
    maxDate = date;
    endFilterChange(QSortFilterProxyModel::Direction::Rows);
}

取得関数であるfilterMinimumDate() とfilterMaximumDate() は単純なもので、ヘッダーファイル内でインライン関数として実装されています。

これでカスタムプロキシモデルの実装は完了です。アプリケーションでこれをどのように使用できるか見てみましょう。

ウィンドウクラスの定義

CustomFilter クラスはQWidget を継承し、この例のメインアプリケーションウィンドウを提供します:

class Window : public QWidget
{
    Q_OBJECT

public:
    Window();

    void setSourceModel(QAbstractItemModel *model);

private slots:
    void textFilterChanged();
    void dateFilterChanged();

private:
    MySortFilterProxyModel *proxyModel;

    QTreeView *sourceView;
    QTreeView *proxyView;
    FilterWidget *filterWidget;
    QDateEdit *fromDateEdit;
    QDateEdit *toDateEdit;
};

ユーザーがフィルタパターン、大文字小文字の区別、またはいずれかの日付を変更した際に反応するため、2つのプライベートスロット `textFilterChanged() ` と `dateFilterChanged()` を実装します。さらに、モデルとビューの関係を設定するためのパブリックな利便性関数 `setSourceModel() ` を実装します。

ウィンドウクラスの実装

この例では、main () 関数内でソースモデルを作成・設定する方式を採用しています(これについては後ほど詳しく説明します)。したがって、メインアプリケーションウィンドウを構築する際には、ソースモデルがすでに存在していることを前提とし、まずカスタムプロキシモデルのインスタンスを作成することから始めます:

Window::Window()
    : proxyModel(new MySortFilterProxyModel(this)),

プロキシモデルが動的にソートおよびフィルタリングされるかどうかを保持するdynamicSortFilter プロパティを設定します。このプロパティをtrueに設定することで、ソースモデルの内容が変更されるたびに、モデルが確実にソートおよびフィルタリングされるようになります。

メインアプリケーションウィンドウには、ソースモデルとプロキシモデルの両方のビューが表示されます。ソースビューは非常にシンプルです:

sourceView->setRootIsDecorated(false);
sourceView->setAlternatingRowColors(true);

QTreeView クラスは、ツリービューのデフォルトのモデル/ビュー実装を提供します。当方のビューは、アプリケーションのソースモデル内の項目をツリー形式で表現するように実装されています。

auto *sourceGroupBox = new QGroupBox(tr("Original Model"));
auto *sourceLayout = new QHBoxLayout(sourceGroupBox);
sourceLayout->addWidget(sourceView);

QTreeView クラスは、ツリービューのデフォルトのモデル/ビュー実装を提供します。当方のビューは、アプリケーションのソースモデル内の項目をツリー形式で表現します。当方のビューウィジェットを、対応するグループボックスに配置したレイアウトに追加します。

一方、プロキシ・モデル・ビューには、ソースモデルのデータ構造を変換するさまざまな側面を制御する複数のウィジェットが含まれています。

filterWidget->setText(tr("Grace|Sports"));
fromDateEdit->setDate(QDate(1970, 01, 01));
toDateEdit->setDate(QDate(2099, 12, 31));

ユーザーがフィルタリングオプションのいずれかを変更するたびに、フィルタを明示的に再適用する必要がある点に注意してください。これは、各種エディタをプロキシモデルを更新する関数に接続することで行われます。

connect(filterWidget, &FilterWidget::filterChanged,
        this, &Window::textFilterChanged);
connect(filterWidget, &QLineEdit::textChanged,
        this, &Window::textFilterChanged);
connect(fromDateEdit, &QDateTimeEdit::dateChanged,
        this, &Window::dateFilterChanged);
connect(toDateEdit, &QDateTimeEdit::dateChanged,
        this, &Window::dateFilterChanged);

ソート処理はビュー側で処理されます。必要なのは、QTreeView::sortingEnabled プロパティ(デフォルトではfalse)を設定して、プロキシビューのソート機能を有効にすることだけです。その後、すべてのフィルタリングウィジェットとプロキシビューを、対応するグループボックスに配置するレイアウトに追加します。

proxyView->setRootIsDecorated(false);
proxyView->setAlternatingRowColors(true);
proxyView->setModel(proxyModel);
proxyView->setSortingEnabled(true);
proxyView->sortByColumn(1, Qt::AscendingOrder);

auto *proxyGroupBox = new QGroupBox(tr("Sorted/Filtered Model"));
auto *proxyLayout = new QVBoxLayout(proxyGroupBox);
proxyLayout->addWidget(proxyView);
auto *formLayout = new QFormLayout;
proxyLayout->addLayout(formLayout);
formLayout->addRow(tr("&Filter pattern:"), filterWidget);
formLayout->addRow(tr("F&rom:"), fromDateEdit);
formLayout->addRow(tr("&To:"), toDateEdit);

最後に、2つのグループボックスを別のレイアウトに配置し、そのレイアウトをメインのアプリケーションウィジェットに配置した後、アプリケーションウィンドウをカスタマイズします。

    auto *mainLayout = new QVBoxLayout(this);
    mainLayout->addWidget(sourceGroupBox);
    mainLayout->addWidget(proxyGroupBox);

    setWindowTitle(tr("Custom Sort/Filter Model"));
    auto screenGeometry = screen()->geometry();
    resize(screenGeometry.width() / 2, screenGeometry.height() * 2 / 3);
}

前述の通り、main () 関数内でソースモデルを作成し、Window::setSourceModel() 関数を呼び出して、アプリケーションがそれを使用するようにします:

void Window::setSourceModel(QAbstractItemModel *model)
{
    proxyModel->setSourceModel(model);
    sourceView->setModel(model);

    for (int i = 0; i < proxyModel->columnCount(); ++i)
        proxyView->resizeColumnToContents(i);
    for (int i = 0; i < model->columnCount(); ++i)
        sourceView->resizeColumnToContents(i);
}

QSortFilterProxyModel::setSourceModel() 関数は、プロキシモデルに指定されたモデル(この場合はメールモデル)のデータを処理させます。ビューウィジェットがQAbstractItemModel クラスから継承するsetModel() は、ビューが表示するモデルを設定します。なお、後者の関数は新しい選択モデルも作成・設定することに注意してください。

void Window::textFilterChanged()
{
    FilterWidget::PatternSyntax s = filterWidget->patternSyntax();
    QString pattern = filterWidget->text();
    switch (s) {
    case FilterWidget::Wildcard:
        pattern = QRegularExpression::wildcardToRegularExpression(pattern);
        break;
    case FilterWidget::FixedString:
        pattern = QRegularExpression::escape(pattern);
        break;
    default:
        break;
    }

    QRegularExpression::PatternOptions options = QRegularExpression::NoPatternOption;
    if (filterWidget->caseSensitivity() == Qt::CaseInsensitive)
        options |= QRegularExpression::CaseInsensitiveOption;
    QRegularExpression regularExpression(pattern, options);
    proxyModel->setFilterRegularExpression(regularExpression);
}

textFilterChanged() 関数は、ユーザーがフィルタパターンや大文字小文字の区別設定を変更するたびに呼び出されます。

まず、優先される構文を取得し(FilterWidget::PatternSyntax 列挙型を使用して、指定されたパターンの意味を解釈します)、次に優先される大文字小文字の区別設定を決定します。これらの設定と現在のフィルタパターンに基づいて、プロキシモデルのfilterRegularExpression プロパティを設定します。filterRegularExpression プロパティには、ソースモデルの内容をフィルタリングするために使用される正規表現が格納されています。 なお、QSortFilterProxyModel のsetFilterRegularExpression()関数を呼び出すと、モデルも更新される点に注意してください。

void Window::dateFilterChanged()
{
    proxyModel->setFilterMinimumDate(fromDateEdit->date());
    proxyModel->setFilterMaximumDate(toDateEdit->date());
}

dateFilterChanged() 関数は、ユーザーが有効な日付の範囲を変更するたびに呼び出されます。ユーザーインターフェースから新しい日付を取得し、対応する関数(カスタムプロキシモデルによって提供される)を呼び出して、プロキシモデルの最小日付と最大日付を設定します。前述したように、これらの関数を呼び出すと、モデルも更新されます。

Main() 関数

この例では、main () 関数内でモデルを作成することで、アプリケーションとソースモデルを分離しています。まずアプリケーションを作成し、次にソースモデルを作成します:

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    Window window;
    window.setSourceModel(new MailModel(&window));
    window.show();
    return QApplication::exec();
}

クラス `MailModel` を作成します。このクラスは `QRangeModel ` を継承し、正しいセクション見出しを返すように `QAbstractItemView::headerData()` を再実装しています。このクラスは、構造体 `MailHeader` の配列からデータが設定されます。`MailHeader ` は、`QRangeModel` で使用されるプロパティを持つ `Q_GADGET ` として宣言されています:

struct MailHeader
{
private:
    Q_GADGET
    Q_PROPERTY(QString subject MEMBER subject)
    Q_PROPERTY(QString sender MEMBER sender)
    Q_PROPERTY(QDateTime date MEMBER date)

public:
    QString subject;
    QString sender;
    QDateTime date;
};
class MailModel : public QRangeModel
{
public:
    explicit MailModel(QObject *parent = nullptr) : QRangeModel(mails, parent) {}

    QVariant headerData(int section, Qt::Orientation orientation,
                        int role = Qt::DisplayRole) const override
    {
        if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
            switch (section) {
            case 0:
                return Window::tr("Subject");
            case 1:
                return Window::tr("Sender");
            case 2:
                return Window::tr("Date");
            default:
                break;
            }
        }
        return QRangeModel::headerData(section, orientation, role);
    }

private:
    static const std::array<MailHeader, 10> mails;
};

const std::array<MailHeader, 10> MailModel::mails = {
    MailHeader{ u"RE: Sports"_s, u"Petra Schmidt <petras@nospam.com>"_s,
                QDateTime(QDate(2007, 01, 05), QTime(12, 01)) },
    MailHeader{ u"AW: Sports"_s, u"Rolf Newschweinstein <rolfn@nospam.com>"_s,
                QDateTime(QDate(2007, 01, 05), QTime(12, 00)) },
    MailHeader{ u"Sports"_s, u"Linda Smith <linda.smith@nospam.com>"_s,
                QDateTime(QDate(2007, 01, 05), QTime(11, 33)) },
    MailHeader{ u"Re: Accounts"_s, u"Andy <andy@nospam.com>"_s,
                QDateTime(QDate(2007, 01, 03), QTime(14, 26)) },
    MailHeader{ u"Re: Accounts"_s, u"Joe Bloggs <joe@bloggs.com>"_s,
                QDateTime(QDate(2007, 01, 03), QTime(14, 18)) },
    MailHeader{ u"Re: Expenses"_s, u"Andy <andy@nospam.com>"_s,
                QDateTime(QDate(2007, 01, 02), QTime(16, 05)) },
    MailHeader{ u"Expenses"_s, u"Joe Bloggs <joe@bloggs.com>"_s,
                QDateTime(QDate(2006, 12, 25), QTime(11, 39)) },
    MailHeader{ u"Accounts"_s, u"pascale@nospam.com"_s,
                QDateTime(QDate(2006, 12, 31), QTime(12, 50)) },
    MailHeader{ u"Radically new concept"_s, u"Grace K. <grace@software-inc.com>"_s,
                QDateTime(QDate(2006, 12, 22), QTime(9, 44)) },
    MailHeader{ u"Happy New Year!"_s, u"Grace K. <grace@software-inc.com>"_s,
                QDateTime(QDate(2006, 12, 31), QTime(17, 03)) }
};

サンプルプロジェクト @ code.qt.io

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.