qt之菜单项定制

    qt实现菜单,简单的界面QMenu+QAction完全可以实现,在加上qss的支持,可以定制出比较美观的菜单,qt的菜单一般用在托盘、按钮和工具栏上。

    当然啦,也有很多软件有比较美观的托盘菜单,比如360、电脑管家等软件,效果图如图1所示,其实qt在4.2之后也提供了定制菜单的功能,使用QWidgetAction可以定制出自己想要的菜单来,接下来是我定制菜单栏的步骤。

图1 360图盘菜单

 

    实现效果如下图2所示,菜单是由单个条目组成的,每一个条目又由左右两部分组成,左边是一个图标,并伴有底色,右边是一个label,上边有文字描述,当有鼠标移动到项上时,项整个背景色变成红色,并且图标会替换,文字颜色也会有相应的变化。

图2 定制菜单

 

    首先拿到这个功能,我们可以先考虑功能的拆分,既然qt支持菜单项的窗口定制功能,那我们不防把每一个项目定制成一个QWidget,这样就问题就变成了一个窗口的定制,这样看起来是不是简单多了。

    首先我们来看下QSystemTrayIcon类,该类实现了windows托盘的功能,activated信号表示托盘图标有事件,我们需要处理这个信号,当messageClicked信号触发时,说明我们点击了托盘提示信息。下面是我重写的托盘类

 1 class CSystemTrayIcon : public QSystemTrayIcon
 2 {
 3     Q_OBJECT
 4 
 5 signals:
 6     void ShowMainWidget();
 7     void ShowMiniWidget();
 8     void AppQuit();
 9 
10 public:
11     CSystemTrayIcon(const QIcon & icon, QObject * parent = nullptr);
12     ~CSystemTrayIcon();
13 
14 public:
15     void SetWaverable(bool waver);//托盘图标是否闪动
16     void ShowMessage(const QString & title
17         , const QString & message
18         , QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::Information
19         , int millisecondsTimeoutHint = 5000);//托盘弹出气泡提示
20 
21     QAction * AddAction(const QString & actName, const QIcon & icon);
22 
23 protected:
24     virtual bool event(QEvent *) Q_DECL_OVERRIDE;
25     virtual void timerEvent(QTimerEvent *) Q_DECL_OVERRIDE;
26     virtual bool eventFilter(QObject * watched, QEvent * event) Q_DECL_OVERRIDE;
27 
28 private slots:
29     void TrayActivateSlot(QSystemTrayIcon::ActivationReason);
30     void MessageClickedSlot();
31 
32 private:
33     void CreateMenu();
34 
35 private:
36     bool m_MouseLeave = true;
37     int m_TimeID = 0;
38     QIcon m_IconPath;
39     CTrayMenu * m_Menu = nullptr;
40 #ifdef CustomAction
41     //添加定制菜单项
42     CActionItem * mainAct = nullptr;
43     CActionItem * miniAct = nullptr;
44     CActionItem * exitAct = nullptr;
45 #else
46     QAction * mainAct = nullptr;
47     QAction * miniAct = nullptr;
48     QAction * exitAct = nullptr;
49 #endif
50 };
View Code

    通过查看CSystemTrayIcon类的接口,可以发现该类中有其他的接口,但是我在本文中不打算对其一一作出解释,因为和菜单项定制无关,如果非要追问,那我只能接单的说明下,SetWaverable接口使用类设置托盘图标是否闪动,就类似qq一样的效果。

    接下来我们需要了解下QWidgetAction类,这个类继承自QAction,他拥有QAction的所有信号和槽,因此我们可以把他当QAction一样的用,不仅仅如此,我们还可以为其提供自定义的QWidget,实现代码如下

 1 class CActionItem : public QWidgetAction
 2 {
 3     Q_OBJECT
 4     Q_PROPERTY(bool m_Hover READ IsMHover WRITE SetMHover)
 5 
 6 public:
 7     CActionItem(const QString & text = "", QWidget * parent = nullptr);
 8     ~CActionItem();
 9 
10 public:
11     void SetContentText(const QString & text);
12     void SetItemIcon(const QString & icon);
13     void SetItemIcon(const QString & icon, const QString & hover);
14     void SetItemIcon(const QString & icon, const QString & hover, const QString & press);
15 
16     QWidget * contentWidget() const;//获取中心窗口
17     void SetToolTip(const QString & toolTip);
18 
19 public:
20     bool IsMHover(){ return m_Hover; }
21     void SetMHover(bool hover){ m_Hover = hover; }
22 
23 protected:
24     virtual QWidget * createWidget(QWidget * parent) Q_DECL_OVERRIDE;
25     virtual void deleteWidget(QWidget * widget) Q_DECL_OVERRIDE;
26     
27 private:
28     bool m_Hover = false;
29     CActionContentWidget * m_ContentWidget = nullptr;
30 };
View Code

    当有QWidgetAction被创建时,首先在构造函数中初始化我们定制的窗口,并将其设置为缺省的窗口

CActionItem::CActionItem(const QString & text, QWidget * parent /*= nullptr*/) : QWidgetAction(parent)
{
	setEnabled(true);

	m_ContentWidget = new CActionContentWidget();
	connect(m_ContentWidget, &CActionContentWidget::IconClicked, this, [this]{this->triggered(); });
	m_ContentWidget->SetContentText(text);

	setDefaultWidget(m_ContentWidget);
}

     createWidget接口会被自动调用,因此我们可以在此接口中创建我们自己定制的QWidget。代码如下,记得把定制的窗口设置为参数所给窗口的子窗口

QWidget * CActionItem::createWidget(QWidget * parent)
{
	m_ContentWidget->setParent(parent);
	return m_ContentWidget;
}

     QWidgetActoin只是一个QAction,想要美观的菜单项,还是需要我们自己去定制窗口的,接下来就是我自己定制的窗口

 1 class CActionContentWidget : public QWidget
 2 {
 3     Q_OBJECT
 4 signals:
 5     void IconClicked();
 6 
 7 public:
 8     CActionContentWidget(QWidget * parent = nullptr);
 9     ~CActionContentWidget();
10 
11 public:
12     void SetContentText(const QString & text);
13     void SetItemIcon(const QString & icon, const QString & hover);
14 
15 public:
16     void SetBackgroundRole(bool hover);
17 
18 protected:
19     virtual void enterEvent(QEvent *) Q_DECL_OVERRIDE;
20     virtual void leaveEvent(QEvent *) Q_DECL_OVERRIDE;
21     virtual bool eventFilter(QObject *, QEvent *) Q_DECL_OVERRIDE;
22 
23 private:
24     void InitializeUI();
25 
26 private:
27     QWidget * m_ContentWidget = nullptr;
28     QPushButton * m_ActIcon = nullptr;
29     QLabel * m_ActText = nullptr;
30     QString m_NormalIcon, m_HoverIcon, m_PressedIcon;
31 };
View Code

    最后就是菜单的定制啦,为什么要重写菜单呢,因为我需要在指定时刻,修改菜单项的位置,因此菜单项的定制也比较简单,就是在关键时刻跑出一个信号,表示需要修改菜单位置了,代码如下:

 1 class CTrayMenu : public QMenu
 2 {
 3     Q_OBJECT
 4 
 5 signals:
 6     void FixedPostion();//移动菜单位置
 7 
 8 public:
 9     CTrayMenu(QWidget * parent = nullptr);
10     ~CTrayMenu();
11 
12 protected:
13     virtual bool event(QEvent *) Q_DECL_OVERRIDE;
14 };
View Code

    因为菜单是一个QWidget,在构造函数中,拿不到width和height,而在show的时候可以拿到相关信息,代码如下:

bool CTrayMenu::event(QEvent * e)
{
	if (e->type() == QEvent::Show)
	{
		emit FixedPostion();
	}

	return QMenu::event(e);
}

     讲到这儿,qt菜单定制功能就讲完了,在菜单定制的过程中我自己也遇到了一些问题,在此记录下,希望看到并知道原因的留下您的脚印。

问题:

1、定制的QWidget中的鼠标事件异常

2、qss中的属性判断异常,例如QLabel[IsCheck=true]{border:1 solid #ff0000;},这种方式设置的鼠标变化不起作用,为了实现这个功能,我是在CSystemTrayIcon类中把定制窗口事件都注册到父类中,然后通过eventFilter来判断鼠标位置,进一步重新设置qss来到达鼠标移动换背景色的功能。代码如下:

bool CSystemTrayIcon::eventFilter(QObject * watched, QEvent * event)
{
	if (watched == this)
	{
		m_MouseLeave = false;
	}
	if (watched->inherits("QWidget") && event->type() == QEvent::Paint)
	{
		if (CActionContentWidget * actionItem = static_cast<CActionContentWidget *>(watched))
		{
			if (actionItem->rect().contains(actionItem->mapFromGlobal(QCursor::pos())))
			{
				actionItem->SetBackgroundRole(true);
			}
			else
			{
				actionItem->SetBackgroundRole(false);
			}
		}
	}
	return QSystemTrayIcon::eventFilter(watched, event);
}

 

如果您觉得文章不错,不妨给个打赏,写作不易,感谢各位的支持。您的支持是我最大的动力,谢谢!!! 

 

  


很重要--转载声明

  1. 本站文章无特别说明,皆为原创,版权所有,转载时请用链接的方式,给出原文出处。同时写上原作者:朝十晚八 or Twowords
  2. 如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时通过修改本文达到有利于转载者的目的。 

posted @ 2016-07-02 01:01  朝十晚八  阅读(6759)  评论(0编辑  收藏  举报

返回顶部