使用qml编写桌面悬浮窗
程序员文章站
2022-03-09 13:35:25
...
1、前言
跨平台的桌面桌面应用有Qt、Electron等,但是Electron这些的视觉效果感觉不太好,网页质感,而且安装包大(Chromium这浏览器来展示),使用qt的话,其他质感强一点和安装包小点。而qt中,对于qWidget和qml,qml没用过,所以选择qml来写,事实证明,这是错误的,因为这在linux下展示很卡,并且抖动严重。所以还加了个动画让过度好点。而使用qWidget,完美兼容,看看qt自带案例Shaped Clock Example,完全不是一个级别。这是在linux下如此,windows下很流畅。
2、实现
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.4 as Controls
import Qt.labs.platform 1.0
//包含系统托盘的导包
Window {
id: mainWindow
visible: true
//大小根据屏幕计算,宽高比为6:14
minimumHeight: 50
minimumWidth: 120
width: Screen.desktopAvailableWidth / 14
height: width * 3 / 7
title: qsTr("tiny monitor")
//无边框的window flags
flags: Qt.FramelessWindowHint | Qt.WindowSystemMenuHint
| Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint
//灰色0.9透明度
color: Qt.rgba(0.5, 0.5, 0.5, 0.9)
Rectangle {
id: rectangle
x: 0
y: 0
width: mainWindow.height
height: width
color: Qt.rgba(0.2, 1.0, 0.0, 0.7)
}
//混合动画效果(这里混合x和y轴平移
ParallelAnimation {
id: moveAnimation
running: false
PropertyAnimation {
target: mainWindow
property: 'x'
easing.type: Easing.Linear
duration: 100
}
PropertyAnimation {
target: mainWindow
property: 'y'
easing.type: Easing.Linear
duration: 100
}
}
//鼠标可控制区域
MouseArea {
property point clickPos: "0,0"
id: dragRegion
anchors.fill: parent
drag.minimumX: 0
drag.maximumX: Screen.desktopAvailableWidth - mainWindow.width
drag.minimumY: 0
drag.maximumY: Screen.desktopAvailableHeight - mainWindow.heigh
onPressed: {
mainWindow.requestActivate()
clickPos = Qt.point(mouseX, mouseY)
}
onPositionChanged: {
moveAnimation.stop()
//鼠标偏移量
var delta = Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y)
console.log(delta.x + " " + delta.y)
mainWindow.x += delta.x
mainWindow.y += delta.y
moveAnimation.start()
}
//添加右键菜单
acceptedButtons: Qt.LeftButton | Qt.RightButton // **右键(别落下这个)
onClicked: {
if (mouse.button === Qt.RightButton) {
// 右键菜单
contentMenu.popup()
}
}
}
//不是托盘的菜单类
Controls.Menu {
id: contentMenu
// 右键菜单
Controls.MenuItem {
id:hideItem
text: qsTr("隐藏")
onTriggered: {
if(trayIcon==null){
console.log("系统托盘不存在");
contentMenu.removeItem(hideItem);
return;
}else{
if(trayIcon.available){
console.log("系统托盘存在");
}else{
console.log("系统托盘不存在");
contentMenu.removeItem(hideItem)
}
}
mainWindow.hide()
}
}
Controls.MenuItem {
text: qsTr("退出")
onTriggered: Qt.quit()
}
}
//使用系统托盘的菜单组件
Menu {
id: systemTrayMenu
// 右键菜单
MenuItem {
text: qsTr("隐藏")
shortcut: "Ctrl+z"
onTriggered: mainWindow.hide()
}
MenuItem {
text: qsTr("退出")
onTriggered: Qt.quit()
}
}
//系统托盘
SystemTrayIcon {
id:trayIcon
visible: true
iconSource: "qrc:/images/TraffickingIn.svg"
tooltip: "tiny-流量监控软件"
onActivated: {
mainWindow.show()
mainWindow.raise()
mainWindow.requestActivate()
}
menu: systemTrayMenu
}
}
效果如此:
做了如下功能:
- 1、使用flag实现无菜单栏的悬浮窗:
//无边框的window flags
flags: Qt.FramelessWindowHint | Qt.WindowSystemMenuHint
| Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint
其中添加Qt.X11BypassWindowManagerHint这个flag可以明显加强linux下的流畅度。
- 2、设置大小根据分辨率计算。
minimumHeight: 50
minimumWidth: 120
width: Screen.desktopAvailableWidth / 14
height: width * 3 / 7
根据屏幕的长、宽根据屏幕宽度计算,防止高分屏。最小为宽120、高50,反之屏幕太小还如此计算悬浮窗太小。
3、使用ParallelAnimation动画来是过度自然。
4、MouseArea设置如下属性,使悬浮窗在屏幕内:
drag.minimumX: 0
drag.maximumX: Screen.desktopAvailableWidth - mainWindow.width
drag.minimumY: 0
drag.maximumY: Screen.desktopAvailableHeight - mainWindow.heigh
- 5、悬浮窗移动,通过每次位置变化来加减悬浮窗的x,y绝对位置来实现::
onPressed: {
mainWindow.requestActivate()
clickPos = Qt.point(mouseX, mouseY)
}
onPositionChanged: {
moveAnimation.stop()
//鼠标偏移量
var delta = Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y)
console.log(delta.x + " " + delta.y)
mainWindow.x += delta.x
mainWindow.y += delta.y
moveAnimation.start()
}
- 6、添加右键菜单:
//添加右键菜单
acceptedButtons: Qt.LeftButton | Qt.RightButton // **右键(别落下这个)
onClicked: {
if (mouse.button === Qt.RightButton) {
// 右键菜单
contentMenu.popup()
}
}
contentMenu是下面菜单的id名。
- 7、区别托盘菜单和旧的菜单,因为同名类,所以会有bug,所以需要起别名:
import QtQuick.Controls 1.4 as Controls
import Qt.labs.platform 1.0 //包含系统托盘的包
Qt.labs.platform这个包的系统托盘是实验用的,可能在未来的系统版本中去除,我为了方便,先用用,这个不是标准实现所以对于linux有兼容性问题,可能会不显示系统托盘,但是用旧的系统托盘不用qml方法还是可以正常使用的。使用qml的系统托盘需要使用QGuiApplication加载,旧的必须使用QApplication来加载qml。
- 8、在常规菜单中我们加了个没有系统托盘就去除隐藏菜单的操作:
Controls.MenuItem {
id:hideItem
text: qsTr("隐藏")
onTriggered: {
if(trayIcon==null){
console.log("系统托盘不存在");
contentMenu.removeItem(hideItem);
return;
}else{
if(trayIcon.available){
console.log("系统托盘存在");
}else{
console.log("系统托盘不存在");
contentMenu.removeItem(hideItem)
}
}
mainWindow.hide()
}
}
这是隐藏菜单项功能。trayIcon是系统托盘的id名,如果系统托盘对象不存在旧去除(remove)。或者系统托盘不可用野去除(remove)。
界面全去qml实现。
顺便贴一下main.cpp:
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);//支持高分屏
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
int state = app.exec();
return state;
}