You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在不使用QML的情况下修改QItemDelegate的x/y/z属性

QtWidgets实现QML GridView的点击放大居中效果

在QtWidgets的模型视图架构中,QItemDelegate仅负责单个项的绘制逻辑,项的位置和布局完全由视图(如QTableView/QListView)管控,无法像QML那样直接修改delegate的坐标和层级。要实现点击项放大居中、再次点击恢复的效果,需要换两种可行思路:


方案一:通过视图自定义绘制顺序实现

重载视图的paintEvent,先绘制所有非选中项,再单独将选中项放大绘制到视图中央,利用绘制顺序天然实现层级覆盖(后绘制的内容在上层)。

自定义GridView实现

#include <QTableView>
#include <QItemDelegate>
#include <QMouseEvent>
#include <QPainter>

class MyGridView : public QTableView
{
    Q_OBJECT
public:
    MyGridView(QWidget *parent = nullptr) 
        : QTableView(parent), m_selectedIndex(QModelIndex()), m_maxItemsPerLine(4)
    {
        setItemDelegate(new MyCustomDelegate(this));
        setSelectionMode(QAbstractItemView::NoSelection); // 禁用默认选中逻辑,自行处理
        setShowGrid(false);
        updateRowColumnCount();
    }

    void setModel(QAbstractItemModel *model) override
    {
        QTableView::setModel(model);
        updateRowColumnCount();
    }

protected:
    void mousePressEvent(QMouseEvent *event) override
    {
        QModelIndex index = indexAt(event->pos());
        if (index.isValid())
        {
            m_selectedIndex = (m_selectedIndex == index) ? QModelIndex() : index;
            update(); // 触发重绘
        }
        QTableView::mousePressEvent(event);
    }

    void paintEvent(QPaintEvent *event) override
    {
        if (m_selectedIndex.isValid())
        {
            // 1. 先绘制所有非选中项
            QPainter painter(viewport());
            for (int row = 0; row < rowCount(); ++row)
            {
                for (int col = 0; col < columnCount(); ++col)
                {
                    QModelIndex idx = model()->index(row, col);
                    if (idx == m_selectedIndex) continue;
                    QStyleOptionViewItem option = viewOptions();
                    option.rect = visualRect(idx);
                    delegate()->paint(&painter, option, idx);
                }
            }

            // 2. 绘制选中项:放大1.5倍并居中
            QStyleOptionViewItem option = viewOptions();
            QSize originalSize = delegate()->sizeHint(option, m_selectedIndex);
            QSize scaledSize = originalSize * 1.5;
            QRect scaledRect(
                (viewport()->width() - scaledSize.width()) / 2,
                (viewport()->height() - scaledSize.height()) / 2,
                scaledSize.width(), scaledSize.height()
            );
            option.rect = scaledRect;
            delegate()->paint(&painter, option, m_selectedIndex);
        }
        else
        {
            // 无选中项时正常绘制
            QTableView::paintEvent(event);
        }
    }

private:
    void updateRowColumnCount()
    {
        if (!model()) return;
        setColumnCount(m_maxItemsPerLine);
        int totalItems = model()->rowCount();
        setRowCount((totalItems + m_maxItemsPerLine - 1) / m_maxItemsPerLine);
    }

    QModelIndex m_selectedIndex;
    int m_maxItemsPerLine;
};

自定义委托实现

class MyCustomDelegate : public QItemDelegate
{
    Q_OBJECT
public:
    using QItemDelegate::QItemDelegate;

    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
    {
        painter->save();
        // 绘制背景
        QColor bgColor = (option.state & QStyle::State_MouseOver) ? option.palette.light() : option.palette.base();
        painter->fillRect(option.rect, bgColor);
        // 绘制内容(示例为文本,可替换为自定义控件内容)
        painter->drawText(option.rect, Qt::AlignCenter, index.data(Qt::DisplayRole).toString());
        painter->restore();
    }

    QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
    {
        MyGridView *view = qobject_cast<MyGridView*>(parent());
        if (view)
        {
            int cellWidth = view->viewport()->width() / view->columnCount();
            int cellHeight = view->viewport()->height() / view->rowCount();
            return QSize(cellWidth, cellHeight);
        }
        return QItemDelegate::sizeHint(option, index);
    }
};

方案二:通过独立上层控件实现

创建一个独立的QWidget承载选中项的放大内容,显示在视图中央,通过raise()保证层级优先级。这种方式更适合添加平滑缩放/移动动画。

核心修改(在MyGridView中添加)

private:
    QWidget* m_zoomWidget = nullptr;

// mousePressEvent中替换选中逻辑:
void mousePressEvent(QMouseEvent *event) override
{
    QModelIndex index = indexAt(event->pos());
    if (index.isValid())
    {
        if (m_selectedIndex == index)
        {
            // 取消选中:销毁放大控件
            delete m_zoomWidget;
            m_zoomWidget = nullptr;
            m_selectedIndex = QModelIndex();
        }
        else
        {
            m_selectedIndex = index;
            // 创建放大控件(需自定义MyZoomItemWidget实现内容绘制)
            delete m_zoomWidget;
            m_zoomWidget = new MyZoomItemWidget(this);
            m_zoomWidget->setItemData(index.data());
            
            // 设置尺寸和位置
            QSize originalSize = delegate()->sizeHint(viewOptions(), index);
            QSize scaledSize = originalSize * 1.5;
            m_zoomWidget->resize(scaledSize);
            m_zoomWidget->move(
                (viewport()->width() - scaledSize.width())/2,
                (viewport()->height() - scaledSize.height())/2
            );
            
            m_zoomWidget->raise(); // 确保在视图内容上层
            m_zoomWidget->show();
        }
    }
    QTableView::mousePressEvent(event);
}

关键说明

  1. 层级问题:方案一通过绘制顺序实现层级,方案二通过raise()将控件置于视图上层,均替代QML中的z属性效果。
  2. 位置管控:QtWidgets中视图全权负责项的布局,因此无法通过委托修改位置,必须通过视图重绘或额外控件绕开原有布局逻辑。

内容的提问来源于stack exchange,提问作者Fred Vulliet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 07:44:55