사용자 정의 정렬/필터 모델 예제
'사용자 정의 정렬/필터 모델' 예제는 QSortFilterProxyModel 클래스를 상속하여 고급 정렬 및 필터링을 수행하는 방법을 보여줍니다.

QSortFilterProxyModel 클래스는 다른 모델과 뷰 간에 전달되는 데이터의 정렬 및 필터링을 지원합니다.
이 모델은 소스 모델이 제공하는 모델 인덱스를 뷰에서 사용할 수 있도록 서로 다른 위치에 해당하는 새로운 인덱스로 매핑하여 소스 모델의 구조를 변환합니다. 이 접근 방식을 사용하면 기본 데이터에 대한 변환을 수행하거나 메모리에서 데이터를 복제할 필요 없이, 뷰의 관점에서 주어진 소스 모델의 구조를 재구성할 수 있습니다.
'사용자 정의 정렬/필터링 모델(Custom Sort/Filter Model)' 예제는 다음 두 클래스로 구성됩니다.
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를 반환해야 합니다. 이 예제에서, 주체(subject)나 발신자(sender) 중 하나에 주어진 정규 표현식이 포함되어 있고 날짜가 유효한 경우 해당 행이 허용됩니다.
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);
}get 함수인 ` 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;
};사용자가 필터 패턴, 대소문자 구분 여부 또는 날짜를 변경할 때 이에 대응하기 위해 두 개의 비공개 슬롯인 ` 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);마지막으로, 두 개의 그룹 박스를 메인 애플리케이션 위젯에 설치된 또 다른 레이아웃에 배치한 후, 애플리케이션 창을 사용자 정의합니다.
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();
}QRangeModel 를 상속받고, 올바른 섹션 제목을 반환하도록 QAbstractItemView::headerData()를 재구현한 MailModel 클래스를 생성합니다. 이 클래스는 struct 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)) }
};© 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.