如何将Qt pro工程文件 改成CMakeLists.txt

Qt pro工程管理文件,本人认为是很好用的,语法简洁易懂,但是只能在QtCreator中使用,想用使用其它IDE比如Clion或者vs,CMakeLists是种通用的选择,另外QtCreator的调试功能跟粑粑一样。

一,思路

C++ 中编译,无外乎代码本身的头文件,源文件。三方库的头文件,库文件。Qt本身自带了UI文件和qrc资源文件。再就是宏定义或者路径的配置。

二,Qt pro文件

#需要的Qt模块
QT       += core gui concurrent

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

#C++版本
CONFIG += c++17

#源文件
SOURCES += \
    main.cpp \
    util/DarkStyle.cpp 

#头文件
HEADERS += \
    3rd/snap7/include/snap7.h \
    util/DarkStyle.h 

#UI文件
FORMS += \
    widget.ui \
    widget/configwidget.ui

#翻译文件
TRANSLATIONS += \
    consistency_zh_CN.ts

CONFIG += lrelease
CONFIG += embed_translations

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

#第三方库
INCLUDEPATH += $$PWD/3rd/snap7/include
LIBS += -L$$PWD/3rd/snap7/lib -lsnap7

win32:LIBS += -luser32

#资源文件
RESOURCES += \
    app.qrc \
    resources/darkstyle.qrc

#图标文件
RC_ICONS = app.ico

三,CMakeLists 文件

cmake_minimum_required(VERSION 3.20)
project(Consistency VERSION 1.0.0)

#C++ 版本
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_INCLUDE_CURRENT_DIR ON)

#用来编译ui文件
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)

#指定输出目录
set(OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${OUTPUT_PATH})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${OUTPUT_PATH})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${OUTPUT_PATH})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${OUTPUT_PATH})

#需要加载的Qt模块
set(CMAKE_PREFIX_PATH "D:\\Qt\\5.15.2\\msvc2019_64\\lib\\cmake\\Qt5")
find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets Network Concurrent)

#头文件
set(HEADERS
    3rd/snap7/include/snap7.h 
    util/DarkStyle.h 
)

#源文件
set(SOURCES
    main.cpp 
    util/DarkStyle.cpp 
)

#UI文件
set(FORMS
    widget.ui 
    widget/configwidget.ui
)

#资源文件
set(QRC_FILES
    app.qrc
	resources/darkstyle.qrc
)
qt5_add_resources(QRC_HEADERS ${QRC_FILES})

#第三方库
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/3rd/snap7/include)
file(GLOB Snap7_LIB ${CMAKE_CURRENT_SOURCE_DIR}/3rd/snap7/lib/*.lib)

#生成可执行文件
add_executable(${PROJECT_NAME}
	${HEADERS}
	${SOURCES}
	${FORMS}
	${QRC_HEADERS}
		logo.rc
)

#链接对应的库
target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network Qt5::Concurrent)
target_link_libraries(${PROJECT_NAME} User32.Lib)
target_link_libraries(${PROJECT_NAME}  ${Snap7_LIB})

四,备注

CMake 无法像pro那样处理图标文件,需要先新建一个rc文件,rc文件中指明icon文件,然后加载rc文件。

#logo.rc 文件
IDI_ICON1           ICON   DISCARDABLE  "app.ico"

利用pybullet 实现ur5 机械臂仿真

想做一个机械臂仿真软件,以前利用C++ 结合FCL和OSG实现了一个半成品,但是这种方案需要根据机械臂DH参数,解析出机械臂各关节位姿然后利用OSG渲染,并且需要自己实现逆解算法,过于麻烦。而使用pybullet,上述得大部分功能都有现成的API接口。

仿真软件得几个要素:

(1),渲染引擎 ,需要将模型渲染可视化出来。VTK,OSG或者原生OpenGL。

(2),动力学引擎,根据物体的物理属性计算运动、旋转和碰撞。Bullet或者ODE,或者只用于碰撞检测的FCL。

(3),模型描述,负责机器人模型的建模,描述各机器人的连杆和关节,还有质量属性,颜色等。比如常用的URDF文件和SDF文件。

pybullet 具备了上述所有的要素,动力学引擎使用bullet,渲染使用OpenGL,并可以直接加载URDF文件,而且提供了 正逆算法。

实现逻辑:

1,利用URDF文件,加载模型。

2,给出目标,也就是抓取点位置。

3,利用逆解算法,求出各关节角度。

4,设置各关节角度,并判判断否碰撞。

5,理想情况下,将此组角度过滤,选择下一组不碰撞的数据 发送真实的给机械臂。

代码:

import pybullet as p
import pybullet_data
import time
import math
from collections   import namedtuple
import numpy as np

# 启动仿真引擎的GUI
p.connect(p.GUI)

# 设置重力加速度
p.setGravity(0, 0, -9.81)

# 加载URDF模型路径
p.setAdditionalSearchPath(pybullet_data.getDataPath())

# 设置视角
p.resetDebugVisualizerCamera(cameraDistance=3,
                                    cameraYaw=120,
                                    cameraPitch=-45,
                                    cameraTargetPosition=[0,0,0])

# 加载平面模型作为地面
planeId = p.loadURDF("plane.urdf")

#托盘
trayId=p.loadURDF("tray/traybox.urdf",basePosition=[0.6,0,0.63])

#桌子
tableId=p.loadURDF("table/table.urdf",basePosition=[0.5,0,0])

# 加载ur5 机械臂
robotId  = p.loadURDF("D:\\keiler\\sim\\urdfs\\ur5.urdf",useFixedBase=True )  #basePosition=[0.0,0,0.62]


initial_position = [0, 0, 0.62]  # 设置初始位置为 [0, 0, 0.62] 米
initial_orientation = p.getQuaternionFromEuler([0, 0, 0])  # 设置初始方向为欧拉角 [0, 0, 0] 对应的四元数
p.resetBasePositionAndOrientation(robotId, initial_position, initial_orientation)

# 获取机械臂末端执行器的索引
endEffectorIndex =6

# 获取机械臂的关节数量
numJoints = p.getNumJoints(robotId)
print("关节数量:"+str(numJoints))

# 打印每个关节的信息
for joint_index in range(numJoints):
    joint_info = p.getJointInfo(robotId, joint_index)
    print(f"Joint {joint_index}: {joint_info}")

# 机械臂的初始位置
restingPosition = [0,3.14, -1.57, 1.57, 1.57, 1.57, -1.57, 0]
for jointNumber in range(numJoints):
    p.resetJointState(robotId, jointNumber, restingPosition[jointNumber])
    
    
# 设置圆形的参数
circle_radius = 3
circle_center = [0, 0]
numPoints = 50
angles = [2 * math.pi * float(i) / numPoints for i in range(numPoints)]

#调参控件
target_x = p.addUserDebugParameter("Target X", -10, 10, 0)
target_y = p.addUserDebugParameter("Target Y", -10, 10, 0)
target_z = p.addUserDebugParameter("Target Z", -10, 10, 0)

#开始按钮
button_start = p.addUserDebugParameter("Start", 1, 0, 1)
# 初始状态变量
button_state_prev = 0


#画个球
sphere_visual = p.createVisualShape(shapeType=p.GEOM_SPHERE, radius=0.03, rgbaColor=[1, 0, 0, 1])
sphere_id = p.createMultiBody(baseMass=0, baseCollisionShapeIndex=-1, baseVisualShapeIndex=sphere_visual, basePosition=[0, 0, 1])


#多组解
def calculate_ik_multiple_solutions(robot_id, end_effector_index, target_position, target_orientation, num_solutions=5):
    solutions = []
    for i in range(num_solutions):
        # 生成一个随机的初始关节状态
        random_joint_positions = [np.random.uniform(-np.pi, np.pi) for _ in range(numJoints)]
        # 计算逆运动学
        ik_solution = p.calculateInverseKinematics(robot_id, end_effector_index, target_position, target_orientation, jointDamping=[0.01]*numJoints, lowerLimits=[-np.pi]*numJoints, upperLimits=[np.pi]*numJoints, jointRanges=[2*np.pi]*numJoints, restPoses=random_joint_positions)
        solutions.append(ik_solution)
    return solutions


try:
    while True:
        x = p.readUserDebugParameter(target_x)
        y = p.readUserDebugParameter(target_y)
        z = p.readUserDebugParameter(target_z)
    
        # 更新球的位置
        p.resetBasePositionAndOrientation(sphere_id, [x, y, z], [0, 0, 0, 1])
        
        #判断按钮状态
        button_state = p.readUserDebugParameter(button_start)
        if button_state != button_state_prev:
            print("Button pressed")
            # 使用逆向运动学计算关节角度
            jointPoses = p.calculateInverseKinematics(robotId, endEffectorIndex, [x, y, z],[0, 0, 0, 1])
            
            # 移动机械臂
            p.setJointMotorControl2(bodyIndex=robotId,jointIndex=1,controlMode=p.POSITION_CONTROL,targetPosition=jointPoses[0],targetVelocity=0,force=500,positionGain=0.03,velocityGain=1)
            p.setJointMotorControl2(bodyIndex=robotId,jointIndex=2,controlMode=p.POSITION_CONTROL,targetPosition=jointPoses[1],targetVelocity=0,force=500,positionGain=0.03,velocityGain=1)
            p.setJointMotorControl2(bodyIndex=robotId,jointIndex=3,controlMode=p.POSITION_CONTROL,targetPosition=jointPoses[2],targetVelocity=0,force=500,positionGain=0.03,velocityGain=1)
            p.setJointMotorControl2(bodyIndex=robotId,jointIndex=4,controlMode=p.POSITION_CONTROL,targetPosition=jointPoses[3],targetVelocity=0,force=500,positionGain=0.03,velocityGain=1)
            p.setJointMotorControl2(bodyIndex=robotId,jointIndex=5,controlMode=p.POSITION_CONTROL,targetPosition=jointPoses[4],targetVelocity=0,force=500,positionGain=0.03,velocityGain=1)
            p.setJointMotorControl2(bodyIndex=robotId,jointIndex=6,controlMode=p.POSITION_CONTROL,targetPosition=jointPoses[5],targetVelocity=0,force=500,positionGain=0.03,velocityGain=1)
        button_state_prev = button_state

        
            
        contact_points  = p.getContactPoints(bodyA=trayId,bodyB=robotId)
        isColl =  bool(contact_points)
        if isColl:
            print("撞上了!")
        
                

        p.stepSimulation()
        time.sleep(0.01)

except KeyboardInterrupt:
    # 用户中断程序时,退出循环
    print("Circle drawing interrupted by user.")
# 断开与仿真引擎的连接
p.disconnect()

问题:

1,逆解 只能返回一组关节角度,没法根据碰撞,去规划路径。

2,返回的各关节角度 貌似有偏差。

3,界面二次开发有点困难,控件太少,想封装成一个产品需要结合其它界面框架,比如Qt,有个想法是用pybullet当服务器,PyQt+VTK当客户端,因为这套东西可以无UI运行。

效果: ​

源码:

https://gitcode.com/keiler20181/sim.git

pyqt集成vtk框架

C++环境下,Qt+VTK是一个比较成熟的方案,那python下也可以使用同样的方式也就是PyQt+VTK。

1,VTK安装

pip install vtk

2,Qt安装

参考:

http://qthello.com/index.php/2024/05/01/pyqt/

3,代码

#!/usr/bin/env python3
 
import sys
import vtkmodules.vtkInteractionStyle
import vtkmodules.vtkRenderingOpenGL2
import vtk
from PyQt5 import QtCore, QtGui, QtWidgets
from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
from vtkmodules.vtkFiltersSources import vtkCubeSource
from vtkmodules.vtkRenderingCore import (
    vtkActor,
    vtkPolyDataMapper,
    vtkRenderer
)
class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        QtWidgets.QMainWindow.__init__(self, parent)
        self.frame = QtWidgets.QFrame()
        self.vl = QtWidgets.QVBoxLayout()
        self.vtkWidget = QVTKRenderWindowInteractor(self.frame)
        self.vl.addWidget(self.vtkWidget)
        self.ren = vtkRenderer()
        self.vtkWidget.GetRenderWindow().AddRenderer(self.ren)
        self.iren = self.vtkWidget.GetRenderWindow().GetInteractor()
        # Create source
        source = vtkCubeSource()
        source.SetCenter(0, 0, 0)
        source.SetXLength(1.0)
        source.SetYLength(1.0)
        source.SetZLength(1.0)
        # Create a mapper
        mapper = vtkPolyDataMapper()
        mapper.SetInputConnection(source.GetOutputPort())
        # Create an actor
        actor = vtkActor()
        actor.SetMapper(mapper)
        self.ren.AddActor(actor)
        self.ren.ResetCamera()
        self.frame.setLayout(self.vl)
        self.setCentralWidget(self.frame)
        self.show()
        self.iren.Initialize()

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec_())

4,效果

Qt 在windows下显示中文

Qt在windows平台上显示中文,简直是一门玄学,经过测试,有如下发现:

1,

环境:Qt 5.15.2  vs2019 64位  win11系统

默认用Qt 创建的文件使用utf-8编码格式,此环境下 中文没有问题

ui->textEdit->append("中文测试");

2

某些  低于此版本的qt,上述方式会出现乱码的情况,此时需要将utf-8编码方式的文件转成utf-8 带bom 。比如使用txt进行转换,但是win7 系统的某些版本 txt 不带此格式。

3,总结

1,简单起见,在windows平台,如果只是windwos平台系统,直接将所有文件转成utf-8 带bom 格式,然后中文使用QStringLiteral(“中文”) 即可。

2,全部使用英文 如,tr(“en”),然后再去翻译文件中翻译。

4,转码工具

分享一个自己写得utf-8 和utf-8 带bom 得转码工具。

源码:

https://gitcode.com/keiler20181/codeConvert/overview

QML 本地存储

QML 原生的储存方有两种:

1,Settings

跟QWidget 中的QSettings 一样,可以简单的存储一些配置。

2,SQLite

sqlite数据库。可以存储一些复杂的数据。

一,Settings

我们以一个按钮的位置为例,进行讲解。

按钮移动时将x y 保存到Settings 中。

下次加载页面时 从Settings 读取位置。

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import Qt.labs.settings 1.0

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    Settings {
        id: settings
        property int x: 0
        property int y: 0
    }

    Button{
        id:btn
        text: "hello"
        x:settings.x
        y:settings.y
    }

    Button{
        anchors.centerIn: parent
        text: "move"
        onClicked: {
            btn.x+=10
            btn.y+=10

            settings.x = btn.x
            settings.y = btn.y
        }
    }
}

main.cpp 中需要 定义 应用程序名称,公司名称,域名称。

效果:

二,SQLite

1,将数据库操作 单独封装成一个js文件。

var db;

function initDatabase() {
    db = LocalStorage.openDatabaseSync("Test", "1.0", "", 100000);
    try {
        db.transaction( function(tx) {
            tx.executeSql('CREATE TABLE IF NOT EXISTS data(name TEXT, desc TEXT, value TEXT)');
        })
    } catch (err) {
           console.log("Error creating table in database: " + err)
       };
}

function readData(name) {
    var res="";
    if(!db) { return; }
    db.transaction( function(tx) {
        var result = tx.executeSql('select value from data where name=?', [name]);
        if (result.rows.length > 0) {
             res = result.rows.item(0).value;
        } else {
            res = "Unknown";
        }
    })
    return res
}

function insertData(name, desc,value) {
    var res = "";
    if(!db) { return; }
    db.transaction( function(tx) {
        var result = tx.executeSql('INSERT OR REPLACE INTO data VALUES (?,?,?);', [name,desc, value]);
        if (result.rowsAffected > 0) {
          res = "OK";
        } else {
          res = "Error";
        }
    })
    return res
}

2,使用的时候 引入js 文件,之后直接调用接口就可以了。

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

import QtQuick.LocalStorage 2.0
import "db.js" as DB

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    Component.onCompleted: {
        //初始化数据库
        DB.initDatabase()
    }

    Column{
        anchors.centerIn: parent
        spacing: 10
        Row{
            spacing: 10
            Button{
                text: "添加"
                onClicked: {
                    //添加数据
                    DB.insertData(name.text,desc.text,value.text)
                }
            }
            TextField{
                id:name
            }

            TextField{
                id:desc
            }

            TextField{
                id:value
            }

        }
        Row{
            spacing: 10
            Button{
                text: "查询"
                onClicked: {
                    //查询数据
                    result.append(DB.readData(queryName.text))
                }
            }

            TextField{
                id:queryName
            }

            TextEdit{
                id:result
            }
        }
    }
}

3,main.cpp 中 指定下数据库的保存路径,比如当前路径。 不指定的话,会默认存到系统路径,不太好找。

4,效果

PyQt程序的打包

记录下PyQt程序的打包。

一,安装

pip3 install PyInstaller

二,打包

pyinstaller -w -n app app.py 
根据需要选择打包参数,例如:

-F表示生成单文件模式,即只有一个可执行文件,比如windows下的exe。执行会比较慢。
-w表示生成窗口模式,即不显示控制台窗口
-i表示指定图标文件,例如-i icon.ico
-n表示指定输出文件名,例如-n app.exe

三,执行

打包后的程序位于 dist文件下。

具体程序 参考:

PyQt 入门

PyQt 入门

Python体系下GUI框架也多了去了,PyQt算是比较受欢迎的一个。如果对Qt框架熟悉,那掌握这套框架是很简单的。

一,安装

1.PyQt5

pip3 install PyQt5

2.Designer UI工具

注:这个工具对python版本有要求,貌似只支持到3.9 所以如果python高于这个版本,可以单独安装 QtDesigner工具 (pip3 install PyQt5Designer)

pip3 install PyQt5-tools

3.UI文件转py文件工具。

python下UI文件无法直接使用,需要使用这个工具转成py文件。

注:windows 下我的python版本为3.12 ,自带了这个工具,无需单独安装。

sudo apt-get install pyqt5-dev-tools

二,使用

1.创建UI文件。

使用designer工具创建ui文件。随便放几个控件,然后保存到工程目录下,取名为widget.ui。

我的designer工具位于这里:

 /home/keiler/.local/lib/python3.11/site-packages/qt5_applications/Qt/bin/designer 

2.UI文件转py文件。

pyuic5 -o widget.py   widget.ui

3,主程序加载py文件。

import sys
from PyQt5.QtWidgets import QApplication, QWidget

from widget import Ui_Form

#Ui_Form 为 Ui文件中的类
class MyApp(Ui_Form, QWidget):
    def __init__(self):
        super().__init__()
        self.setupUi(self)

        #信号槽的连接
        self.pushButton.clicked.connect(lambda:print("hello"))
        self.pushButton_2.clicked.connect(self.fun)
        self.show()

    def fun(self):
        self.textEdit.append("good")
 
# 应用程序入口
if __name__ == "__main__":
    app = QApplication(sys.argv)
    my_app = MyApp()
    sys.exit(app.exec_())

4,效果

QML model-view 框架

在开发用户界面时,最好的办法是保持数据与可视化的分离。比如一个通讯录,可以用list展示,也可以用grid展示,这都属于view部分,可以不同。但是数据都是相同的,都有名称,头像,手机号等元素。QML针对这种场景提供了model-view框架。其实同样的模式在传统的QWidget 框架中也有,比如QListView 和QTableView。

一,实现起来,记住三个关键点:

1,model。 数据来源,一般是通过网络请求获取。

2,view。负责可视化显示,是ListView 还是GridView。

3,delegate。代理,就是怎么展示数据。它负责将model和view连接起来,将model中的每条数据通过具体的控件,渲染到view中。

下面通过一个例子,来说明代码的写法,这个例子类似一个通讯录,数据我们写死,真实情况应该是从接口获取。利用ListView进行可视化。代理是一个Rectangle 内部一个Image展示头像,一个Text 显示文本。

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Layouts 1.15

Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")

//可视化view
ListView{
anchors.fill: parent
width: 100
delegate: addressDelegate
model: model
}

//list 模型
ListModel{
id:model
}

//代理
Component{
id:addressDelegate
Rectangle{
width: 140
height: 120

RowLayout{
anchors.fill: parent
anchors.margins: 10
//头像
Image {
source: img
Layout.preferredWidth: 100
Layout.preferredHeight: 100
}
//名称
Text {
text: name
verticalAlignment: Text.AlignVCenter
}
}
}
}

//构造模型数据
Component.onCompleted: {
model.append({"img":"https://img2.baidu.com/it/u=607192830,2624468444&fm=253&fmt=auto&app=120&f=JPEG?w=1025&h=684","name":"刘备"})
model.append({"img":"https://img2.baidu.com/it/u=1662844506,2024543724&fm=253&fmt=auto&app=120&f=JPEG?w=800&h=1052","name":"关羽"})
model.append({"img":"https://img2.baidu.com/it/u=2512544330,2343544095&fm=253&fmt=auto&app=138&f=JPEG?w=400&h=300","name":"张飞"})
model.append({"img":"https://img2.baidu.com/it/u=4186203074,1390758742&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=618","name":"赵云"})
}
}

二,总结

这套模型还有很多用法,比如数据量特别大的时候,可以使用Flickable元素,同时可以限制代理数量。还可以动态的增加,删除模型。添加页眉页脚等。但是底层逻辑还是那三条,model 负责加载数据,view 负责显示,delegate负责具体每条数据如何展示。

windows下其它软件的自动打开与点击

一,需求

项目要用到第三方软件,这个软件没有提供SDK,只提供了两个exe,每次开机后需要启动这两个exe,并且还要点击上边的两个按钮。这样的用户体验怎么能让人接受呢,如果查资料,发现windows提供了接口,可以自动化操作。

二,逻辑

三,实现

窗体标题 或者句柄 可以使用 Spy++ 这个工具获取。


#include <iostream>
#include <Windows.h>

//杀死窗口所在的进程
void killEXE(LPCWSTR title) {
HWND hwnd = FindWindow(NULL, title);
if (hwnd != NULL) {
DWORD processId;
GetWindowThreadProcessId(hwnd, &processId);

HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, processId);
if (hProcess != NULL) {
if (TerminateProcess(hProcess, 0)) {
std::cout << "Process terminated successfully!" << std::endl;
}
else {
std::cerr << "Failed to terminate process! Error code: " << GetLastError() << std::endl;
}
CloseHandle(hProcess);
}
else {
std::cerr << "Failed to open process handle!" << std::endl;
}
}
else {
std::cerr << "Window not found!" << std::endl;
}
}

//打开可执行文件
void openEXE(LPCWSTR applicationName) {
LPWSTR commandLine = NULL;
STARTUPINFOW startupInfo;
PROCESS_INFORMATION processInfo;
ZeroMemory(&startupInfo, sizeof(startupInfo));
startupInfo.cb = sizeof(startupInfo);

if (CreateProcessW(applicationName, commandLine, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo)) {
std::cout << "Process created successfully!" << std::endl;
}
else {
std::cerr << "Failed to create process! Error code: " << GetLastError() << std::endl;
}
}

//最小化窗口
void minimizeDialog(LPCWSTR title) {
HWND hwnd = FindWindow(NULL, title);
if (hwnd != NULL) {
ShowWindow(hwnd, SW_MINIMIZE);
std::cout << "Window minimized successfully!" << std::endl;
}
else {
std::cerr << "Window not found!" << std::endl;
}
}

//点击窗口上的按钮
void clickBtn(LPCWSTR title, LPCWSTR btnName) {
HWND hwnd = FindWindow(NULL, title);
if (hwnd != NULL) {
HWND btnHandle = FindWindowEx(hwnd, NULL, L"Button", btnName);
if (btnHandle != NULL) {
SendMessage(btnHandle, BM_CLICK, 0, 0);
SendMessage(btnHandle, BM_CLICK, 0, 0);
}
else {
std::cerr << "Button not found!" << std::endl;
}
}
else {
std::cerr << "Window not found!" << std::endl;
}
}
int main()
{
//关闭进程
killEXE(L"Geomagic Control X Automation (Client)");
killEXE(L"Geomagic Control X Automation (Server)");

//打开客户端进程
openEXE(L"D:\\Program Files\\Oqton\\Geomagic Control X 2024.1\\Geomagic Control X Automation (Client).exe");
Sleep(1000);
//最小化客户端
minimizeDialog(L"Geomagic Control X Automation (Client)");

//打开服务器进程
openEXE(L"D:\\Program Files\\Oqton\\Geomagic Control X 2024.1\\Geomagic Control X Automation (Server).exe");
Sleep(1000);
//点击OK按钮
clickBtn(L"Geomagic Control X Automation (Server) - Mode Select",L"OK");
Sleep(1000);
//点击 Start Server按钮
clickBtn(L"Geomagic Control X Automation (Server)", L"Start Server");
Sleep(1000);
//最小化服务器客户端
minimizeDialog(L"Geomagic Control X Automation (Server)");


}

三,效果(需要管理员权限运行程序)

树莓派下开机自启的Qt程序配置

一,需求

Qt开发的GUI程序,部署到树莓派中,并设置开机自启。

二,步骤

1,制作启动脚本和停止脚本。

我们需要编写两个脚本,来启动和停止我们的应用。这里假设我们的应用名称为test。

#启动脚本
#!/usr/bin/env bash

#设置环境变量
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/test

cd /usr/test

#启动主程序
./test
#停止脚本
#!/usr/bin/env bash

ID=` ps -ef | grep test | grep -v grep | awk '{print $2}'`

for id in $ID
do
kill -9 $id
done

2,制作安装脚本

我们需要编写一个脚本用于安装qt打包后的程序,也就是整个文件夹。脚本的作用就是将文件夹拷贝到指定的位置。

比如我们的程序在test文件夹内,内部含有完整的Qt应用程序及依赖库,我们在同级目录下创建一个安装脚本 install.sh。

#!/usr/bin/env bash

Install_App() {
echo "installing test ..."
target=$1
if [ x$target == x'' ]; then
echo "please input parameter 1 for target path"
exit
fi

sudo /usr/test/stop.sh
sleep 1
if [ ! -d "$target" ]; then
sudo mkdir $target
fi
cp -a test/* $target/
#sudo chown -R root:root $target

}

if [ $UID -ne 0 ]; then
echo "Is not superuser,please use sudo "
exit
else
ROOT_PATH=/usr/test
Install_App $ROOT_PATH
echo "Install completed !"
reboot
fi

3,系统的设置

这里有两种方式。

(1),sudo vim /etc/xdg/lxsession/LXDE-pi/autostart

#@lxpanel --profile LXDE-pi
#@pcmanfm --desktop --profile LXDE-pi
#@xscreensaver -no-splash
@sudo /usr/test/start.sh

(2),

  1. 在/home/用户/.config下创建autostart 的文件夹
  2. 在 autostart 目录下新建my.desktop
  3. 编辑 my.desktop
[Desktop Entry]
Name=AICounter
Comment=AICounter
Exec=sudo /usr/test/start.sh
Terminal=false
MultipulArgs=false
Type=Application
Categories=Application;Development;
StartupNotify=ture

三,树莓派的一些配置

1,屏蔽开机警告

在/boot/config.txt末尾添加语句
avoid_warnings=2

2,修改欢迎界面

替换 /usr/share/plymouth/themes/pix/splash.png

3,更换软件源

sudo nano /etc/apt/sources.list
deb https://mirrors.ustc.edu.cn/debian/ bullseye main contrib non-free
deb https://mirrors.ustc.edu.cn/debian/ bullseye-updates main contrib non-free
deb https://mirrors.ustc.edu.cn/debian/ bullseye-backports main contrib non-free
deb https://mirrors.ustc.edu.cn/debian-security/ bullseye-security main contrib non-free
sudo apt-get update

4,时间相关

1,关掉网络 时间同步服务  sudo systemctl stop systemd-timesyncd.service
2,启动网络 同步时间服务 sudo systemctl start systemd-timesyncd.service
3,时间同步服务 禁止自启 sudo systemctl disable systemd-timesyncd.service
4,开机自启 时间同步服务 sudo systemctl enable systemd-timesyncd.service
5,手动修改时间 sudo date -s "2021-10-31 20:18:00"
6,date 检查时间
7,修改时区:sudo dpkg-reconfigure tzdata 亚洲 上海

要想改时间,先要修改为正确的时区

5,pcf85063a时钟芯片

/boot/configini
dtparam=i2c_arm=on
dtparam=i2c_vc=on
dtoverlay=i2c-rtc,pcf85063a,i2c_csi_dsi

6,中文支持

sudo apt-get install scim-pinyin

QWidget qss

QML 中风格和主题的设计可以通过配置文件选择现有几种中的一种,或者直接在控件定义时,指定其属性,如背景颜色或者字体大小。在QWidget框架中,则通过了一种叫做qss样式表的东西来进行描述,跟CSS逻辑上类似。

这个qss抽象出来就两个元素,一个叫做选择器,一个叫做属性。简单的说就是谁长什么样子。那这个谁,有好多种写法,比如所有按钮,某个按钮,按下的按钮。而这个样子又有好多,比如背景色,边框,圆角等。

一,实现

1,新建一个qss文件,并添加到资源里。然后在main函数中,进行全局的设置。

2,字体 一般是一个全局的设置,所以可以在此处设置全局字体。微软雅黑 是一个常用的字体

3,我们以如下的窗口来举例说明。

原始窗口如下:

qss 如下:

//所有QPushButton 背景色为蓝色,宽高为 180 80
QPushButton{
background-color: rgb(0, 0, 255);
width:180px;
height:80px;
}

//所有QPushButton 鼠标hover时 背景为黄色
QPushButton:hover {
background-color: rgb(255, 255, 0);
}

//所有QPushButton flat属性为true 时 背景为绿色
QPushButton[flat="true"] {
background-color: rgb(0, 255, 0);
}

//指定这两个按钮 背景为红色
#pushButton_5,#pushButton_6{
background-color: rgb(255, 0, 0);
}

效果如下:

4,还可以用自己的图片 替换子控件。

/*combobox 的下拉箭头*/
QComboBox::down-arrow {
image: url(:/img/axis_return.png);
}

二,常用属性

三,QSS支持的类型、属性、伪状态、子控件

https://doc.qt.io/qt-5/stylesheet-reference.html

四,总结

属性和控件很多,全背下来没有必要。书写时记住一个逻辑,谁 长什么样子。这个谁可以是全部控件,也可以是某个控件,或者控件的某个状态。哪些样子可以设置,参考文档就行了。

代码:

https://gitcode.com/keiler20181/WidgetStyle.git

五,qt帮助查询

1、“Qt Style Sheets Reference” Qt样式表的用法。

2、“Qt Style Sheets Examples” Qt样式表的实例

3、“The Style Sheet Syntax” Qt样式表语法