Qml实现自定义右键菜单

先上效果图吧:




在正式开始之前大致阐述一下思路:

结构,对于每一个菜单而言,其实就是一个菜单项的列表。而每一个菜单项都可以包含自己的子菜单项。


所以用可以这样简单的描述一个菜单:

typdef struct _item_s

{

QString text

QList<_item_s*> subItem

}Item;


typedef struct

{

QList<Item> items;

}Menu;


接下来开始设计Qml界面代码(即菜单代码)

这里主要是用一个Window和一个ListView作为主要部分,每个菜单对应一个model。

使用了递归的方式,也就是subItem 其实也就是个Menu,

具体看如下代码:


import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.4

Window
{
    id                            : popPage
    flags                         : Qt.FramelessWindowHint | Qt.WindowActive  | Qt.Popup | Qt.WindowStaysOnTopHint
    modality                      : Qt.WindowModal
    visible                       : false
    width                         : itemWidth
    height                        : itemHeight * listView.count + 10
    property var itemModel        : null                                        //数据,可以使用JSValue或JSON或QStandardItemModel或ListModel等等
    property int itemHeight       : 25
    property int itemWidth        : 100
    property int _itemCount       : listView.count
    property var _lastPopupItem   : null                                        //弹出的子菜单  (如果没有子菜单弹出过,则为null)
    property var _parentPopupItem : null                                        //自己的父菜单项(当自己是子菜单时有效,其他情况是null)

    onActiveChanged:
    {
       popPage.color = active ? "green" : "lightgray"
    }

    ScrollView
    {
        id: scrollView
        anchors.fill: parent


        ListView
        {
            id: listView
            anchors.fill: parent
            model: popPage.itemModel
            delegate: itemDelegate

            Component
            {
                id: itemDelegate
                Rectangle
                {
                    implicitWidth : listView.width
                    implicitHeight: 25
                    color: ((index % 2) == 0) ? "gray" : "lightblue"

                    Loader
                    {
                        id: subItem
                        source: (model.sub == null) ? "" : "PopupPage.qml"                       //当有子菜单时(sub不为null)的时候载入当前qml作为子菜单

                        onLoaded:                                                                //初始化子菜单项并且设置为不接受焦点的窗口
                        {
                            item.flags            = item.flags | Qt.WindowDoesNotAcceptFocus
                            item._parentPopupItem = popPage
                            item.itemModel        = model.sub
                            console.log(model.sub)

                        }
                    }

                    Text
                    {
                        id: itemLabel
                        anchors.left: parent.left
                        anchors.verticalCenter: parent.verticalCenter
                        text: (subItem.item == null) ? model.name : model.name + " --->"         //如果有子菜单项则添加箭头text
                    }
                    MouseArea                                                                    //主要用于控制子菜单的显示与否
                    {
                        id: itemControl
                        anchors.fill: parent
                        hoverEnabled: true
                        onClicked:
                        {
                            if(subItem.item == null)
                            {
                                popPage.hide()
                                var item = popPage._parentPopupItem
                                while(item != null)
                                {
                                    item.hide()
                                    item = item._parentPopupItem
                                }

                                return
                            }

                            popSubItem()
                        }

                        onEntered:
                        {
                            itemLabel.font.bold = true
                            popSubItem()
                        }
                        onExited:
                        {
                            itemLabel.font.bold = false
                        }
                    }

                    function popSubItem()
                    {
                        popPage.show()
                        popPage.requestActivate()

                        if(popPage._lastPopupItem != null &&
                           popPage._lastPopupItem != subItem.item)
                        {
                            popPage._lastPopupItem.hide()
                        }

                        if(model.sub == null)
                            return

                        console.log("pop sub")
                        popPage._lastPopupItem = subItem.item

                        subItem.item.x = popPage.x + popPage.width - 10
                        subItem.item.y = popPage.y + 25 * index
                        subItem.item.show()
                        subItem.item.raise()
                    }

                    function hideSubItem()
                    {
                        if(model.sub == null)
                            return

                        subItem.item.hide()
                        popPage.requestActivate()
                        console.log("hide")
                    }
                }
            }
        }
    }

    onVisibleChanged:                                                                        //处理细节让菜单的弹出和隐藏更加合理
    {
        if(!visible)
        {
            if(popPage._lastPopupItem != null)
            {
                popPage._lastPopupItem.hide()
            }
        }
    }


}

值得注意的是我现在菜单是一个Qt.ApplicationModal的模态框,所以我写了一些Qt代码用来控制当点击目标窗口的时候隐藏这个弹出菜单,如果没有必要用

模态框的话请把这个标志去掉,并且在ActiveChanged的时候决定是否关闭弹出菜单(只有主菜单才会有这个事件,子菜单都被设置了不接受焦点)。








可以通过自定义 QQuickItem 实现自定义分割布局。具体实现步骤如下: 1. 创建一个自定义 QQuickItem,并在其内部添加子项,用于显示布局中的内容。 2. 为自定义 QQuickItem 添加属性,用于控制布局的分割位置和方向。 3. 在自定义 QQuickItem 中实现布局算法,根据分割位置和方向,计算子项的位置和大小。 4. 在 QML 中使用自定义 QQuickItem,设置其属性,即可实现自定义分割布局。 下面是一个简单的自定义分割布局示例(仅实现横向分割): ```cpp // SplitterItem.h #include <QQuickItem> class SplitterItem : public QQuickItem { Q_OBJECT Q_PROPERTY(QQuickItem* leftItem READ leftItem WRITE setLeftItem NOTIFY leftItemChanged) Q_PROPERTY(QQuickItem* rightItem READ rightItem WRITE setRightItem NOTIFY rightItemChanged) Q_PROPERTY(int splitPos READ splitPos WRITE setSplitPos NOTIFY splitPosChanged) public: SplitterItem(QQuickItem *parent = nullptr); QQuickItem* leftItem() const; void setLeftItem(QQuickItem *item); QQuickItem* rightItem() const; void setRightItem(QQuickItem *item); int splitPos() const; void setSplitPos(int pos); signals: void leftItemChanged(); void rightItemChanged(); void splitPosChanged(); protected: QSGNode* updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) override; private: QQuickItem *m_leftItem; QQuickItem *m_rightItem; int m_splitPos; }; ``` ```cpp // SplitterItem.cpp #include "SplitterItem.h" #include <QSGGeometryNode> #include <QSGGeometry> #include <QSGFlatColorMaterial> SplitterItem::SplitterItem(QQuickItem *parent) : QQuickItem(parent) , m_leftItem(nullptr) , m_rightItem(nullptr) , m_splitPos(0) { setFlag(ItemHasContents, true); } QQuickItem* SplitterItem::leftItem() const { return m_leftItem; } void SplitterItem::setLeftItem(QQuickItem *item) { if (m_leftItem != item) { m_leftItem = item; emit leftItemChanged(); } } QQuickItem* SplitterItem::rightItem() const { return m_rightItem; } void SplitterItem::setRightItem(QQuickItem *item) { if (m_rightItem != item) { m_rightItem = item; emit rightItemChanged(); } } int SplitterItem::splitPos() const { return m_splitPos; } void SplitterItem::setSplitPos(int pos) { if (m_splitPos != pos) { m_splitPos = pos; emit splitPosChanged(); update(); } } QSGNode* SplitterItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) { QSGGeometryNode *node = nullptr; QSGGeometry *geometry = nullptr; QSGFlatColorMaterial *material = nullptr; if (!oldNode) { node = new QSGGeometryNode; geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 4); geometry->setDrawingMode(GL_TRIANGLE_STRIP); node->setGeometry(geometry); material = new QSGFlatColorMaterial; node->setMaterial(material); } else { node = static_cast<QSGGeometryNode*>(oldNode); geometry = node->geometry(); material = static_cast<QSGFlatColorMaterial*>(node->material()); } QSGGeometry::Point2D *vertices = geometry->vertexDataAsPoint2D(); QRectF rect = boundingRect(); // 计算左侧和右侧子项的宽度和高度 qreal leftWidth = m_splitPos; qreal rightWidth = rect.width() - m_splitPos; qreal height = rect.height(); // 更新左侧子项的位置和大小 if (m_leftItem) { m_leftItem->setPos(0, 0); m_leftItem->setSize(QSizeF(leftWidth, height)); } // 更新右侧子项的位置和大小 if (m_rightItem) { m_rightItem->setPos(leftWidth + 1, 0); m_rightItem->setSize(QSizeF(rightWidth, height)); } // 更新分割线的位置和大小 vertices[0].set(leftWidth, 0); vertices[1].set(leftWidth, height); vertices[2].set(leftWidth + 1, 0); vertices[3].set(leftWidth + 1, height); // 设置分割线的颜色 material->setColor(Qt::gray); node->markDirty(QSGNode::DirtyGeometry); return node; } ``` 在 QML 中使用 SplitterItem: ```qml SplitterItem { width: 400 height: 300 leftItem: Rectangle { color: "red" } rightItem: Rectangle { color: "green" } splitPos: 200 } ``` 以上代码实现了一个横向分割的布局,左侧子项为红色矩形,右侧子项为绿色矩形,分割线位置为 200。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值