사용자 정의 정렬/필터 모델 예제

사용자 지정 정렬/필터 모델 예제에서는 QSortFilterProxyModel 클래스를 서브클래스화하여 고급 정렬 및 필터링을 수행하는 방법을 보여 줍니다.

Screenshot of the Custom Sort/Filter Model Example

QSortFilterProxyModel 클래스는 다른 모델과 뷰 간에 전달되는 데이터의 정렬 및 필터링을 지원합니다.

이 모델은 뷰가 사용할 수 있도록 제공하는 모델 인덱스를 다른 위치에 해당하는 새 인덱스에 매핑하여 소스 모델의 구조를 변환합니다. 이 접근 방식을 사용하면 기초 데이터를 변환하지 않고 메모리에 데이터를 복제하지 않고도 뷰에 관한 한 주어진 소스 모델을 재구성할 수 있습니다.

사용자 지정 정렬/필터 모델 예제는 두 개의 클래스로 구성됩니다:

  • 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() 함수를 구현합니다. QSortFilterProxyModelfilterAcceptsRow() 함수를 다시 구현하여 유효한 날짜가 있는 행만 허용하고 QSortFilterProxyModel::lessThan() 함수를 구현하여 발신자를 이메일 주소별로 정렬할 수 있도록 합니다. 마지막으로, 날짜가 유효한지 확인하는 데 사용할 dateInRange() 편의 함수를 구현합니다.

MySortFilterProxyModel 클래스 구현

MySortFilterProxyModel 생성자는 부모 매개 변수를 기본 클래스 생성자에게 전달하는 간단한 구조입니다:

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

MySortFilterProxyModel 구현에서 가장 흥미로운 부분은 QSortFilterProxyModelfilterAcceptsRow() 및 lessThan() 함수를 재구현한 부분입니다. 먼저 사용자 정의한 lessThan() 함수를 살펴보겠습니다.

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

발신자를 이메일 주소별로 정렬하고 싶습니다. lessThan () 함수는 정렬할 때 < 연산자로 사용됩니다. 기본 구현은 QDateTime 및 문자열을 포함한 유형 컬렉션을 처리하지만 발신자를 이메일 주소별로 정렬하려면 먼저 주어진 문자열 내에서 주소를 식별해야 합니다:

    if (leftData.userType() == QMetaType::QDateTime) {
        return leftData.toDateTime() < rightData.toDateTime();
    } else {
        static const QRegularExpression emailPattern("[\\w\\.]*@[\\w\\.]*");

        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()는 참을 반환합니다. 일치 결과는 QRegularExpressionMatchcaptured() 함수를 사용하여 검색할 수 있습니다. 전체 일치 항목의 인덱스는 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() 함수는 주어진 행이 모델에 포함되어야 하는 경우 참을 반환합니다. 이 예에서는 제목 또는 발신자에 지정된 정규식이 포함되어 있고 날짜가 유효한 경우 행이 허용됩니다.

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

사용자 지정 dateInRange() 함수를 사용하여 날짜가 유효한지 확인합니다.

주어진 기간을 지정하여 데이터를 필터링할 수 있도록 최소 및 최대 날짜를 가져오고 설정하는 함수도 구현했습니다:

void MySortFilterProxyModel::setFilterMinimumDate(QDate date)
{
    minDate = date;
    invalidateFilter();
}

void MySortFilterProxyModel::setFilterMaximumDate(QDate date)
{
    maxDate = date;
    invalidateFilter();
}

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;

    QGroupBox *sourceGroupBox;
    QGroupBox *proxyGroupBox;
    QTreeView *sourceView;
    QTreeView *proxyView;
    QLabel *filterPatternLabel;
    QLabel *fromLabel;
    QLabel *toLabel;
    FilterWidget *filterWidget;
    QDateEdit *fromDateEdit;
    QDateEdit *toDateEdit;
};

필터 패턴, 대소문자 구분 또는 날짜를 변경하는 사용자에 응답하기 위해 textFilterChanged()dateFilterChanged() 라는 두 개의 비공개 슬롯을 구현합니다. 또한 모델/뷰 관계를 설정하기 위해 공개 setSourceModel() 편의 기능을 구현합니다.

창 클래스 구현

이 예제에서는 main () 함수에서 소스 모델을 만들고 설정하도록 선택했습니다(나중에 다시 설명하겠습니다). 따라서 기본 애플리케이션 창을 구성할 때 소스 모델이 이미 존재한다고 가정하고 사용자 지정 프록시 모델의 인스턴스를 만드는 것으로 시작합니다:

Window::Window()
{
    proxyModel = new MySortFilterProxyModel(this);

프록시 모델이 동적으로 정렬 및 필터링되는지 여부를 보유하는 dynamicSortFilter 속성을 설정합니다. 이 속성을 true로 설정하면 소스 모델의 콘텐츠가 변경될 때마다 모델이 정렬 및 필터링되도록 합니다.

기본 애플리케이션 창에는 소스 모델과 프록시 모델 모두의 보기가 표시됩니다. 소스 보기는 매우 간단합니다:

sourceView = new QTreeView;
sourceView->setRootIsDecorated(false);
sourceView->setAlternatingRowColors(true);

QTreeView 클래스는 트리 보기의 기본 모델/보기 구현을 제공합니다. 이 보기는 애플리케이션의 소스 모델에 있는 항목의 트리 표현을 구현합니다.

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

QTreeView 클래스는 트리 보기의 기본 모델/보기 구현을 제공하며, 저희 보기는 애플리케이션의 소스 모델에 있는 항목의 트리 표현을 구현합니다. 해당 그룹 상자에 설치하는 레이아웃에 보기 위젯을 추가합니다.

반면에 프록시 모델 뷰에는 소스 모델의 데이터 구조를 변환하는 다양한 측면을 제어하는 여러 위젯이 포함되어 있습니다:

filterWidget = new FilterWidget;
filterWidget->setText(tr("Grace|Sports"));
connect(filterWidget, &FilterWidget::filterChanged, this, &Window::textFilterChanged);

filterPatternLabel = new QLabel(tr("&Filter pattern:"));
filterPatternLabel->setBuddy(filterWidget);

fromDateEdit = new QDateEdit;
fromDateEdit->setDate(QDate(1970, 01, 01));
fromLabel = new QLabel(tr("F&rom:"));
fromLabel->setBuddy(fromDateEdit);

toDateEdit = new QDateEdit;
toDateEdit->setDate(QDate(2099, 12, 31));
toLabel = new QLabel(tr("&To:"));
toLabel->setBuddy(toDateEdit);

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

사용자가 필터링 옵션 중 하나를 변경할 때마다 명시적으로 필터를 다시 적용해야 한다는 점에 유의하세요. 이는 다양한 편집기를 프록시 모델을 업데이트하는 함수에 연결하여 수행됩니다.

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

QGridLayout *proxyLayout = new QGridLayout;
proxyLayout->addWidget(proxyView, 0, 0, 1, 3);
proxyLayout->addWidget(filterPatternLabel, 1, 0);
proxyLayout->addWidget(filterWidget, 1, 1);
proxyLayout->addWidget(fromLabel, 3, 0);
proxyLayout->addWidget(fromDateEdit, 3, 1, 1, 2);
proxyLayout->addWidget(toLabel, 4, 0);
proxyLayout->addWidget(toDateEdit, 4, 1, 1, 2);

proxyGroupBox = new QGroupBox(tr("Sorted/Filtered Model"));
proxyGroupBox->setLayout(proxyLayout);

정렬은 뷰에서 처리됩니다. QTreeView::sortingEnabled 속성을 설정하여 프록시 뷰에 정렬을 사용하도록 설정하기만 하면 됩니다(기본적으로 거짓임). 그런 다음 모든 필터링 위젯과 프록시 보기를 해당 그룹 상자에 설치하는 레이아웃에 추가합니다.

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

setWindowTitle(tr("Custom Sort/Filter Model"));
resize(500, 450);
}

마지막으로 두 개의 그룹 상자를 기본 애플리케이션 위젯에 설치하는 다른 레이아웃에 넣은 후 애플리케이션 창을 사용자 지정합니다.

위에서 언급했듯이 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() 함수는 사용자가 필터 패턴이나 대소문자 구분을 변경할 때마다 호출됩니다.

먼저 기본 설정 구문을 검색한 다음(필터 위젯::패턴 구문 열거형은 주어진 패턴의 의미를 해석하는 데 사용됨), 기본 설정 대/소문자 구분을 결정합니다. 이러한 기본 설정과 현재 필터 패턴을 기반으로 프록시 모델의 filterRegularExpression 속성을 설정합니다. filterRegularExpression 속성에는 소스 모델의 콘텐츠를 필터링하는 데 사용되는 정규식이 저장됩니다. QSortFilterProxyModelsetFilterRegularExpression() 함수를 호출하면 모델도 업데이트됩니다.

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(createMailModel(&window));
    window.show();
    return app.exec();
}

createMailModel() 함수는 생성자를 단순화하기 위해 제공되는 편의 함수입니다. 이 함수는 이메일 컬렉션을 설명하는 모델을 생성하고 반환하기만 하면 됩니다. 이 모델은 QStandardItemModel 클래스의 인스턴스, 즉 일반적으로 표준 Qt 데이터 유형의 저장소로 사용되는 사용자 정의 데이터를 저장하기 위한 일반 모델입니다. 각 메일 설명은 또 다른 편의 기능인 addMail() 을 사용하여 모델에 추가됩니다. 자세한 내용은 itemviews/customsortfiltermodel/main.cpp 을 참조하세요.

예제 프로젝트 @ code.qt.io

© 2025 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.