Qt之右鍵菜單實現

最近在看C++ GUI  Qt4部分其中有個例子對於擴展一個應用程序提供一個上下文菜單。

Qt中的部件的彈出菜單(ContextMenu),依據ContextMenuPolicy的值的不同,有四種形式:
一、默認菜單
此時,ContextMenuPolicy的值爲Qt::DefaultContextMenu,這是默認值,其將顯示部件定義的默認菜單
二、無菜單
此時,ContextMenuPolicy的值爲Qt::NoContextMenu
三、由Action定義菜單
此時,ContextMenuPolicy的值爲Qt::ActionsContextMenu,要爲此部件定義這種菜單,很簡單,只要把已經定義好的Action部件插入到要顯示此菜單的部件中,部件將自動按順序顯示菜單。
QWidget::addAction(QAction *action);
四、自定義菜單
此時,ContextMenuPolicy的值爲Qt::CustomContextMenu,這回,有兩種方式來定義菜單,一種是響應
customContextMenuRequested()這個signal,在響應的槽中顯示菜單(QMenu的exec()方法)。第二種是需要從這個部件的類中派生一個類,重寫contextMenuEvent()這個函數顯示菜單(QMenu的exec()方法顯示)。


第三種很簡單:

void MainWidget::createContextMenu()
{
    this->addAction(m_exitAction);    
    this->setContextMenuPolicy(Qt::ActionsContextMenu);
}

下面介紹 第四種Qt::CustomContextMenu:

 

#ifndef WIDGET_H
#define WIDGET_H

#include <QtGui>

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);
    ~Widget();
private:
    QMenu* m_menu;
    QAction* m_exitAction;
    QAction* m_aboutAction;

private:
    void createAction();
    void createMenu();
    void createContextMenu();
private slots:
    void contextMenuSlot(QPoint p);
};

#endif // WIDGET_H

 

 

#include "widget.h"
#include <QDebug>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    createAction();
    createMenu();
    createContextMenu();
    connect(this,SIGNAL(customContextMenuRequested(QPoint)),this,SLOT(contextMenuSlot(QPoint)));
}

Widget::~Widget()
{

}
void Widget::createAction()
{
    m_exitAction = new QAction("exit",this);
    connect(m_exitAction,SIGNAL(triggered()),qApp,SLOT(quit()));

    m_aboutAction = new QAction("about",this);
    connect(m_aboutAction,SIGNAL(triggered()),qApp,SLOT(aboutQt()));
}
void Widget::createMenu()
{
    m_menu = new QMenu(this);
    m_menu->addAction(m_exitAction);
    m_menu->addSeparator();
    m_menu->addAction(m_aboutAction);
}
void Widget::createContextMenu()
{

    this->setContextMenuPolicy(Qt::CustomContextMenu);//將contextMenuPolicy屬性要設置爲:CustomContextMenu,這步不能忘記,否則右鍵無反應。
}
void Widget::contextMenuSlot(QPoint p)
{
    m_menu->exec(this->mapToGlobal(p));
}


以QTreeWidget爲例:

在Qt+VS2010下,使用slot函數customContextMenuRequested(QPointpos)實現Treewidget的右鍵菜單欄。

1、要在某一種窗體內添加右鍵菜單欄,比如在QTreeWidget中添加,可以用到slot函數customContextMenuRequested(QPointpos)。

在新建的customContextMenuRequested(QPointpos)函數中便可以實現具體的菜單欄了。

爲了判斷右鍵點擊的位置,即點即不同的item節點,會出現不同的右鍵菜單,我們可以用API中itemAt()函數實現。

而爲了給每個節點賦予不同的鍵值,可以通過setData()來實現。

在屬性設置中,將contextMenuPolicy屬性要設置爲:CustomContextMenu,這步不能忘記,否則右鍵無反應。

我在TreeWidget中添加右鍵菜單的實現代碼:

2、爲Treewidget添加節點,通過setData()來爲每個節點賦予不同的鍵值。

QTreeWidgetItem *root;    

root = new QTreeWidgetItem(ui->treeWidget, QStringList(QString("Connection")));

QVariant var0(0);

root->setData(0,Qt::UserRole,var0);

3、在slot函數中使用itemAt()函數獲取當前被點擊的節點,然後爲不同的節點添加不同的菜單。

void MainWindow::on_treeWidget_customContextMenuRequested(QPoint pos)

{

    QTreeWidgetItem* curItem=ui->treeWidget->itemAt(pos);  //獲取當前被點擊的節點

    if(curItem==NULL)return;           //這種情況是右鍵的位置不在treeItem的範圍內,即在空白位置右擊

    QVariant var = curItem->data(0,Qt::UserRole);

    if(0 == var)      //data(...)返回的data已經在之前建立節點時用setdata()設置好      {

       QMenu *popMenu =new QMenu(this);//定義一個右鍵彈出菜單

       popMenu->addAction(ui->action_newDB);//往菜單內添加QAction   該action在前面用設計器定義了

       popMenu->addAction(ui->action_openDB);

       popMenu->addAction(ui->action_delDB);

       popMenu->exec(QCursor::pos());//彈出右鍵菜單,菜單位置爲光標位置    }

    else

    {

        QMenu *popMenu =new QMenu(this);//定義一個右鍵彈出菜單

        popMenu->addAction(ui->action_newTable);//往菜單內添加QAction   該action在前面用設計器定義了

        popMenu->addAction(ui->action_openTable);

        popMenu->addAction(ui->action_designTable);

        popMenu->exec(QCursor::pos());//彈出右鍵菜單,菜單位置爲光標位置    

   }

}

 //////////////////////////////////////////////////////////////////////////

但是更高級的是重新定義事件處理函數void contextMenuEvent(QContextMenuEvent *event)。下面講解其contextMenuEvent(QContextMenuEvent *event)。

QWidget及其子類都可有右鍵菜單,因爲QWidget有以下兩個與右鍵菜單有關的函數:

Qt::ContextMenuPolicy contextMenuPolicy () const

void setContextMenuPolicy ( Qt::ContextMenuPolicy policy )

Qt::ContextMenuPolicy枚舉類型包括:Qt::DefaultContextMenu, Qt::NoContextMenu, Qt::PreventContextMenu, Qt::ActionsContextMenu, and Qt::CustomContextMenu。

使用方式如下:

1)默認是Qt::DefaultContextMenu。
它是利用右鍵菜單事件contextMenuEvent()來處理(which means the contextMenuEvent() handler is called)。就是要重寫contextMenuEvent( QContextMenuEvent * event )函數。

例子(該例子即是我改寫的)

[cpp] 

void MainWindow::contextMenuEvent(QContextMenuEvent *event)  

{  

   Context = new QMenu();  

   Context->addAction(ui->actionCut);  

   Context->addAction(ui->actionCope);  

   Context->addAction(ui->actionPase);  

   spreadsheet->setContextMenuPolicy(Qt::DefaultContextMenu);  

   Context->exec(QCursor::pos());  

}

2)使用Qt::CustomContextMenu。
它是發出QWidget::customContextMenuRequested信號,注意僅僅只是發信號,意味着要自己寫顯示右鍵菜單的slot。這個信號是QWidget唯一與右鍵菜單有關的信號(也是自有的唯一信號),同時也是很容易被忽略的signal:

void customContextMenuRequested ( const QPoint & pos )

該信號的發出條件是:用戶請求contextMenu(常規就是鼠標右擊啦)且同時被擊的widget其contextMenuPolicy又是Qt::CustomContextMenu。
注意:pos是該widget接收右鍵菜單事件的位置,一般是在該部件的座標系中。但是對於QAbstratScrollArea及其子類例外,是對應着其視口viewport()的座標系。如常用的QTableView、QHeaderView就是QAbstratScrollArea的子類。
因爲僅發信號,所以需自己寫顯示右鍵菜單的slot來響應。

例如一個表格(QTableView類型)表頭的顯示右鍵菜單槽:

datatable->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(datatable->horizontalHeader(), SIGNAL(customContextMenuRequested(const QPoint&)), 
        this, SLOT(show_contextmenu(const QPoint&)));//this是datatable所在窗口
QMenu *cmenu = NULL;
show_contextmenu(const QPoint& pos)
{
    if(cmenu)//保證同時只存在一個menu,及時釋放內存
    {
        delete cmenu;
        cmenu = NULL;
    }
    QMenu cmenu = new QMenu(datatable->horizontalHeader());
    
    QAction *ascendSortAction = cmenu->addAction("升序");
    QAction *descendSortAction = cmenu->addAction("降序");
    QAction *filterAction = cmenu->addAction("過濾");
    QAction *reshowAction = cmenu->addAction("重載");
    
    connect(ascendSortAction, SIGNAL(triggered(bool)), this, SLOT(sort_ascend()));
    connect(descendSortAction, SIGNAL(triggered(bool)), this, SLOT(sort_descend()));
    connect(filterAction, SIGNAL(triggered(bool)), this, SLOT(show_filter_dlg()));
    connect(reshowAction, SIGNAL(triggered(bool)), this, SLOT(reshow_data()));
    
    cmenu->exec(QCursor::pos());//在當前鼠標位置顯示
    //cmenu->exec(pos)是在viewport顯示
}

也可先做好cmenu,好處是始終使用一個:
    QMenu cmenu = new QMenu(datatable->horizontalHeader());
    
    QAction *ascendSortAction = cmenu->addAction("升序");
    QAction *descendSortAction = cmenu->addAction("降序");
    QAction *filterAction = cmenu->addAction("過濾");
    QAction *reshowAction = cmenu->addAction("重載");
    
    connect(ascendSortAction, SIGNAL(triggered(bool)), this, SLOT(sort_ascend()));
    connect(descendSortAction, SIGNAL(triggered(bool)), this, SLOT(sort_descend()));
    connect(filterAction, SIGNAL(triggered(bool)), this, SLOT(show_filter_dlg()));
    connect(reshowAction, SIGNAL(triggered(bool)), this, SLOT(reshow_data()));
show_contextmenu(const QPoint& pos)
{
    if(cmenu)
    {
        cmenu->exec(QCursor::pos());
    }
}

3)使用Qt::ActionsContextMenu。
把部件的所有action即QWidget::actions()作爲context menu顯示出來。
還是上面的例子,要在表格(QTableView類型)表頭顯示右鍵菜單:
        QAction *ascendSortAction = new QAction("升序", this);
        QAction *descendSortAction = new QAction("降序", this);
        QAction *filterAction = new QAction("過濾", this);
        QAction *unfilterAction = new QAction("取消過濾", this);
    
        connect(ascendSortAction, SIGNAL(triggered(bool)), this, SLOT(sort_ascend()));
        connect(descendSortAction, SIGNAL(triggered(bool)), this, SLOT(sort_descend()));
        connect(filterAction, SIGNAL(triggered(bool)), this, SLOT(filter_table()));
        connect(unfilterAction, SIGNAL(triggered(bool)), this, SLOT(unfilter_table()));
    
        datatable->horizontalHeader()->addAction(ascendSortAction);
        datatable->horizontalHeader()->addAction(descendSortAction);
        datatable->horizontalHeader()->addAction(filterAction);
        datatable->horizontalHeader()->addAction(unfilterAction);    
        datatable->horizontalHeader()->setContextMenuPolicy(Qt::ActionsContextMenu);

另外兩個就是不顯示context menu了:
Qt::NoContextMenu
    the widget does not feature a context menu, context menu handling is deferred to the widget's parent.
    
Qt::PreventContextMenu
    the widget does not feature a context menu, and in contrast to NoContextMenu, the handling is not deferred to the widget's parent. This means that all right mouse button events are guaranteed to be delivered to the widget itself through mousePressEvent(), and mouseReleaseEvent().

補充:
    使用Qt::ActionsContextMenu比較簡潔,但是如果需要根據當前菜單彈出的位置來定義不同菜單,或者像上個例子,在表格(QTableView類型)表頭顯示右鍵菜單時,我需要知道是哪一列表頭被點擊,從而在後來調用sort_ascend()排序函數時能夠根據不同列進行不同排序策略,那麼Qt::ActionsContextMenu就做不到了。
    這種需要捕捉彈出位置的情況只好用Qt::ActionsContextMenu了,customContextMenuRequested ( const QPoint & pos )信號返回點擊位置pos(在表頭視口座標系中位置),然後表頭即可調用logicalIndexAt(pos)函數得到被點擊section對應的index即被點擊部分的列號,然後存下來可供後面action激活的排序槽使用。
show_contextmenu(const QPoint& pos)
{
    //get related column of headerview
    contextmenu_column = datatable->horizontalHeader()->logicalIndexAt(pos);

    //show contextmenu
    if(cmenu)
    {
        cmenu->exec(QCursor::pos());
    }
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章