自定义一个QAbstractItemView控件

本文是模仿某些软件里的控件样式。在组合框里选择具有两级结构的数据,比如选某省->某市类似的数据。下述代码在VS2015和Qt5.9中测试通过。其运行效果如图。为了展示更多的功能,我还特地实现了滚动条的功能:

头文件:

class MItemView : public QAbstractItemView
{
    Q_OBJECT

public:
    MItemView(QWidget* parent = 0);

public:
    void setModel(QAbstractItemModel *model) override;
    void paintEvent(QPaintEvent* event) override;
    QRect visualRect(const QModelIndex &index) const override;
    void scrollTo(const QModelIndex &index, ScrollHint hint) override;
    QModelIndex indexAt(const QPoint &point) const override;
    QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override;
    int horizontalOffset() const override;
    int verticalOffset() const override;
    bool isIndexHidden(const QModelIndex &index) const override;
    void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) override;
    QRegion visualRegionForSelection(const QItemSelection &selection) const override;
    void resizeEvent(QResizeEvent* event) override;
    void verticalScrollbarValueChanged(int value) override;

private:
    void updateRegion();

private:
    int offy;
    int preIndex;
    int subIndex;
    QVector<QRect> titleRegion;
    QVector<QRect> nameRegion;
};

CPP文件。下面只实现了基本功能,鼠标悬停在某一项上的悬浮效果没有实现,如需要可自行重写QAbstractItemView::mouseMoveEvent(...)事件。另外,QAbstractItemView默认是可以多选和编辑的,如不需要可以禁用这些功能。本例虽不支持上述功能但未禁用:

const int titleWidth = 80;
const int titleHeight = 26;
const int nameWidth = 64;
const int lineHeight = 2;

MItemView::MItemView(QWidget* parent) :
    QAbstractItemView(parent)
{
    setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
    preIndex = 0;
    subIndex = -1;
    offy = 0;
}

void MItemView::setModel(QAbstractItemModel *model)
{
    QAbstractItemView::setModel(model);
    updateRegion();
}

void MItemView::updateRegion()
{
    titleRegion.clear();
    nameRegion.clear();

    QAbstractItemModel* myModel = model();
    QRect rect(0, offy, titleWidth, titleHeight);
    for (int i = 0; i < myModel->rowCount(); i++)
    {
        titleRegion.append(rect);
        rect.moveTo(rect.left() + rect.width(), rect.top());
    }
    QModelIndex focus = myModel->index(preIndex, 0);
    rect = QRect(0, rect.bottom() + lineHeight, nameWidth, titleHeight);
    for (int i = 0; i < myModel->rowCount(focus); i++)
    {
        nameRegion.append(rect);
        rect.moveTo(rect.left() + rect.width(), rect.top());
        if (rect.right() > width())
        {
            rect = QRect(0, rect.bottom(), rect.width(), rect.height());
        }
    }
}

void MItemView::paintEvent(QPaintEvent* event)
{
    QPainter painter(viewport());
    QAbstractItemModel* myData = model();
    painter.setPen(Qt::black);
    for (int i = 0; i < myData->rowCount(); i++)
    {
        QString title = myData->data(myData->index(i, 0), Qt::DisplayRole).toString();
        if (i == preIndex)
        {
            painter.fillRect(titleRegion[i], QColor(232, 174, 59));
        }
        painter.drawText(titleRegion[i], Qt::AlignCenter, title);
    }

    QRect line(0, titleRegion[0].bottom(), width(), lineHeight);
    painter.fillRect(line, QColor(52, 114, 240));

    QModelIndex focus = myData->index(preIndex, 0);
    for (int i = 0; i < myData->rowCount(focus); i++)
    {
        QString name = myData->data(myData->index(i, 0, focus), Qt::DisplayRole).toString();
        if (subIndex == i)
        {
            painter.fillRect(nameRegion[i], QColor(255, 204, 51));
        }
        painter.drawText(nameRegion[i], Qt::AlignCenter, name);
    }
}

QRect MItemView::visualRect(const QModelIndex &index) const
{
    if (!index.parent().isValid())
    {
        return titleRegion[index.row()];
    }
    return nameRegion[index.row()];
}

void MItemView::scrollTo(const QModelIndex &index, ScrollHint hint)
{
}

QModelIndex MItemView::indexAt(const QPoint &point) const
{
    for (int i = 0; i < titleRegion.size(); i++)
    {
        if (titleRegion[i].contains(point))
        {
            return model()->index(i, 0);
        }
    }
    for (int i = 0; i < nameRegion.size(); i++)
    {
        if (nameRegion[i].contains(point))
        {
            return model()->index(i, 0, model()->index(preIndex, 0));
        }
    }
    return QModelIndex();
}

//---------------------------------------------------------------------------------------
// 此函数是系统在用户使用键盘的上下左右等方向键操作时调用的。此例不支持键盘操作所以
// 不实现了
//---------------------------------------------------------------------------------------
QModelIndex MItemView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
{
    return QModelIndex();
}

int MItemView::horizontalOffset() const
{
    return 0;
}

int MItemView::verticalOffset() const
{
    return offy;
}

bool MItemView::isIndexHidden(const QModelIndex &index) const
{
    return false;
}

void MItemView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
{
    if ((command | QItemSelectionModel::Clear) ||
        (command | QItemSelectionModel::Select))
    {
        QModelIndex index = indexAt(rect.center());
        if (index.parent().isValid())
        {
            preIndex = index.parent().row();
            subIndex = index.row();
        }
        else /* 点击的是标题 */
        {
            preIndex = index.row();
            subIndex = -1;
        }
        updateRegion();
        viewport()->update();
    }
}

QRegion MItemView::visualRegionForSelection(const QItemSelection &selection) const
{
    return QRegion();
}

void MItemView::resizeEvent(QResizeEvent* event)
{
    QAbstractItemView::resizeEvent(event);
    updateRegion();
    if (!nameRegion.empty())
    {
        int h = nameRegion.last().bottom();
        if (h > height())
        {
            QScrollBar* vbar = verticalScrollBar();
            vbar->setMinimum(0);
            vbar->setMaximum(h - viewport()->height());
            vbar->setPageStep(viewport()->height());
            vbar->setSingleStep(10);
        }
    }
}

void MItemView::verticalScrollbarValueChanged(int value)
{
    QAbstractItemView::verticalScrollbarValueChanged(value);
    for (auto& item : titleRegion)
    {
        item.adjust(0, -value - offy, 0, -value - offy);
    }
    for (auto& item : nameRegion)
    {
        item.adjust(0, -value - offy, 0, -value - offy);
    }
    offy = -value;
}

 在主窗口的构造函数初始化组合框。下方的QtTest是主窗口类;ui.cbSelect是组合框:

QtTest::QtTest(QWidget *parent)
    : QMainWindow(parent)
{
    ui.setupUi(this);

    QStandardItemModel* model = new QStandardItemModel(this);
    QStandardItem* row0 = new QStandardItem(u8"特热突然");
    row0->appendRow(new QStandardItem(u8"合肥"));
    row0->appendRow(new QStandardItem(u8"1321"));
    row0->appendRow(new QStandardItem(u8"合肥"));
    row0->appendRow(new QStandardItem(u8"se地方"));
    row0->appendRow(new QStandardItem(u8"合肥"));
    row0->appendRow(new QStandardItem(u8"画图"));
    row0->appendRow(new QStandardItem(u8"合肥"));
    row0->appendRow(new QStandardItem(u8"士大夫"));
    row0->appendRow(new QStandardItem(u8"合肥"));
    row0->appendRow(new QStandardItem(u8"合肥"));
    row0->appendRow(new QStandardItem(u8"合肥"));
    row0->appendRow(new QStandardItem(u8"画图"));
    row0->appendRow(new QStandardItem(u8"合肥"));
    row0->appendRow(new QStandardItem(u8"合肥"));
    row0->appendRow(new QStandardItem(u8"合肥"));
    row0->appendRow(new QStandardItem(u8"看i他"));
    row0->appendRow(new QStandardItem(u8"突然"));
    row0->appendRow(new QStandardItem(u8"合肥"));
    row0->appendRow(new QStandardItem(u8"突然"));
    row0->appendRow(new QStandardItem(u8"别认识"));
    model->appendRow(row0);
    QStandardItem* row1 = new QStandardItem(u8"因为人");
    row1->appendRow(new QStandardItem(u8"口语"));
    row1->appendRow(new QStandardItem(u8"1321"));
    row1->appendRow(new QStandardItem(u8"三个"));
    row1->appendRow(new QStandardItem(u8"说额"));
    row1->appendRow(new QStandardItem(u8"合肥"));
    row1->appendRow(new QStandardItem(u8"uio额"));
    row1->appendRow(new QStandardItem(u8"合肥"));
    model->appendRow(row1);
    QStandardItem* row2 = new QStandardItem(u8"iur一天");
    row2->appendRow(new QStandardItem(u8"认为"));
    row2->appendRow(new QStandardItem(u8"1321"));
    row2->appendRow(new QStandardItem(u8"居就"));
    row2->appendRow(new QStandardItem(u8"维度"));
    row2->appendRow(new QStandardItem(u8"合肥"));
    row2->appendRow(new QStandardItem(u8"如同额"));
    row2->appendRow(new QStandardItem(u8"合肥"));
    model->appendRow(row2);
    ui.cbSelect->setModel(model);
    ui.cbSelect->setView(new MItemView());
}

 

posted @ 2024-02-03 15:12  兜尼完  阅读(176)  评论(0编辑  收藏  举报