C语言模拟QT的信号与槽功能(qt信号与槽详解)
ccwgpt 2024-10-21 03:56 23 浏览 0 评论
Part1前言
使用过QT的朋友,应该都对QT的信号与槽机制深有体会,它可以非常方便的实现类与类之间的解耦合、实现对象与对象之间的解耦合、实现两个cpp文件之间的解耦合。
既然信号槽如此好用,嵌入式开发也想拥有,下边就开始用C语言一步步实现它吧...
Part2一、Qt信号与槽的实现机理
在Qt中实现信号与槽最重要的就是通过元对象系统(MOS)的元对象编译器(MOC)将我们定义的需要使用到信号与槽的类中的信号及信号调用槽函数的方法进行定义(这一步就会生成与源文件对应的moc_xx.cpp文件),然后通过系统提供的关联方法(connect)将信号与槽建立一一对应关系,当发射信号(其实就是调用信号函数)时就会通过信号与槽的对应关系找到对应槽函数进行调用。
这样的好处就是对于使用者而言不必去关心函数指针回调函数这些对于初学者比较不太容易搞清晰的东西,简化了使用者的操作。当然就像我们在享受幸福生活的时候,就一定有人在我们背后默默付出砥砺前行!这里也一样,对于我们使用者简化了操作,那为了实现这样的效果就需要在后台提供更多的支持。
QT Creator官方帮助文档对信号槽使用方法做了详细的介绍,接下来我们就依照官方的使用方法,依葫芦画瓢,用C语言的宏模拟出山寨版的信号和槽。
嵌入式物联网需要学的东西真的非常多,千万不要学错了路线和内容,导致工资要不上去!
无偿分享大家一个资料包,差不多150多G。里面学习内容、面经、项目都比较新也比较全!某鱼上买估计至少要好几十。
点击这里找小助理0元领取:加微信领取资料
Part3二、简化后的实现步骤
11. 定义一些必要的宏
先无脑定义一些与QT中一模一样的宏,然后再思考如何实现它的功能。
#define signals //定义信号
#define emit //发射信号
#define slots //定义槽
#define connect //链接信号与槽
#define SIGNAL(x)
#define SLOT(x)
信号槽的核心机制是当发射信号时会通过信号与槽的对应关系找到对应槽函数进行调用。我们所要模拟的正是这个核心机制。任务明确了,就开始实现这些宏吧!
22. 实现声明信号的宏
QT中定义信号是在类中使用signals声明一个函数,不需要自己实现信号函数,在生成的moc文件中,代替你实现了你声明的信号函数,所以发射信号的本质就是通过调用信号函数,再调用槽函数。
既然调用发射信号,就是调用槽函数,那么理论上,只需要把槽函数的地址赋值给一个发射信号的函数指针,就完成偷梁换柱了。
想要定义函数指针,首先需要知道函数类型,那么使用signals宏声明信号,就可以用来声明一个函数类型,然后用这个函数类型,去定义函数指针。
实现定义信号的宏:
#define signals(__NAME,...) \
typedef void __NAME( __VA_ARGS__);
33. 实现发射信号的宏
发射信号就是,利用声明好的函数类型,定义一个函数指针,然后把槽函数地址赋给这个函数指针,然后执行这个函数指针的一段代码:
#define emit(__NAME,__OBJ,...) \
do{ \
sig_slot_t *ptObj = &((__OBJ)->tObject); \
__NAME *__RecFun = ptObj->ptRecFun; \
执行__RecFun((__OBJ)->ptRecObj,__ARG1,__ARG2, __ARG3,...) \
}while(0) \
其中__NAME是信号名称,__OBJ是信号所在对象的地址,...是可变参数。 这里重点来了,由于执行__RecFun这个函数的时候,我们并不知道有多少个参数,所以无法直接如上边代码那样带入参数,我们必须知道...代表的是几个参数,那么就可以知道使用几个__ARG了。
这个时候,如果看过上篇文章C语言变参函数和可变参数宏,应该就能立马想到我们其实已经实现了一个可以获得可变参数宏中参数数量的宏了:#define VA_NUM_ARGS(...) ,利用它,宏的重载就可以信手捏来了。先来它8个参数的,应该够用了
#define __RecFun_0(__OBJ) \
__RecFun((__OBJ))
#define __RecFun_1(__OBJ, __ARG1) \
__RecFun((__OBJ),(__ARG1))
#define __RecFun_2(__OBJ, __ARG1, __ARG2) \
__RecFun((__OBJ),(__ARG1), (__ARG2))
#define __RecFun_3(__OBJ, __ARG1, __ARG2, __ARG3) \
__RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3))
#define __RecFun_4(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4) \
__RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4))
#define __RecFun_5(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4, __ARG5) \
__RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4), (__ARG5))
#define __RecFun_6(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4, __ARG5, __ARG6) \
__RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4), (__ARG5), (__ARG6))
#define __RecFun_7(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4, __ARG5, __ARG6, __ARG7) \
__RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4), (__ARG5), (__ARG6), (__ARG7))
#define __RecFun_8(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4, __ARG5, __ARG6, __ARG7, __ARG8) \
__RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4), (__ARG5), (__ARG6), (__ARG7), (__ARG8))
直接使用开源的面向对象模块PLOOC中已经封装好的__PLOOC_EVAL,来自动选择正确的函数即可,重新实现emit
#define __emit(__OBJ,...) \
__PLOOC_EVAL(__RecFun_,##__VA_ARGS__) \
((__OBJ)->ptRecObj,##__VA_ARGS__);
#define emit(__NAME,__OBJ,...) \
do{ \
sig_slot_t *ptObj = &((__OBJ)->tObject); \
__NAME *__RecFun = ptObj->ptRecFun; \
__emit(ptObj, __VA_ARGS__) \
}while(0)
这样发射信号的时候,执行的就是槽函数了,槽函数的第一个参数始终为槽所在的对象地址,从而就完成了对象与对象之间的解耦。
44. 取代QObject类
实现发射信号的宏时候,我们需要知道槽函数的地址,和槽函数所在对象的地址,我们定义一个类:
typedef struct sig_slot_t sig_slot_t;
typedef struct sig_slot_t{
char chSenderName[SIG_NAME_MAX];//信号名称
void * ptSenderObj; //信号所在对象的地址
void * ptRecObj; //槽所在对象的地址
void * ptRecFun; //槽函数的地址
}sig_slot_t;
然后重新定义一个宏,来取代QObject的地位:
#define SIG_SLOT_OBJ sig_slot_t tObject;
然后用法就和QT中一样了,在需要信号的地方,给结构体中,加入SIG_SLOT_OBJ 。
55. 实现connect函数
接下来就只剩一个把信号和槽连接起来的宏了
void connect(void *SenderObj, const char *ptSender,void *RecObj,void *RecFun)
{
if(SenderObj == NULL || ptSender == NULL || RecObj == NULL || RecFun == NULL){
return;
}
sig_slot_t * ptMetaObj = SenderObj;
ptMetaObj->ptRecFun = RecFun;
ptMetaObj->ptRecObj = RecObj;
memcpy(ptMetaObj->chSenderName,ptSender,strlen(ptSender));
}while(0);
}
66. 可有可无的slots
因为槽函数实际上就只是个函数而已,为了与信号遥相呼应,也实现一下:
#define __slots(__NAME,...) \
void __NAME(__VA_ARGS__);
#define slots(__NAME,__OBJ,...) \
__slots(__NAME,_args(__OBJ,##__VA_ARGS__))
Part4三、完整的代码实现
以上代码只是展示核心部分,并且仅实现了一个信号对应一个槽,不能一个信号对应多个信号和槽,还有诸多类型检查,空指针检查等需要优化的地方,以下是完整的代码实现:
signals_slots.h文件
#ifndef __SIGNALS_SLOTS_H_
#define __SIGNALS_SLOTS_H_
#include ".\app_cfg.h"
#if USE_SERVICE_SIGNALS_SLOTS == ENABLED
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#define __PLOOC_CLASS_USE_STRICT_TEMPLATE__
#if defined(__SIGNALS_SLOTS_CLASS_IMPLEMENT__)
#define __PLOOC_CLASS_IMPLEMENT__
#elif defined(__SIGNALS_SLOTS_CLASS_INHERIT__)
#define __PLOOC_CLASS_INHERIT__
#endif
#include "plooc_class.h"
#define SIG_NAME_MAX 20
#define SIGNAL(x) "sig_"#x
#define SLOT(x) x
#define SIG_SLOT_OBJ sig_slot_t tObject;
#define args(...) ,__VA_ARGS__
#define _args(...) __VA_ARGS__
#define __RecFun_0(__OBJ) \
__RecFun((__OBJ))
#define __RecFun_1(__OBJ, __ARG1) \
__RecFun((__OBJ),(__ARG1))
#define __RecFun_2(__OBJ, __ARG1, __ARG2) \
__RecFun((__OBJ),(__ARG1), (__ARG2))
#define __RecFun_3(__OBJ, __ARG1, __ARG2, __ARG3) \
__RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3))
#define __RecFun_4(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4) \
__RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4))
#define __RecFun_5(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4, __ARG5) \
__RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4), (__ARG5))
#define __RecFun_6(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4, __ARG5, __ARG6) \
__RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4), (__ARG5), (__ARG6))
#define __RecFun_7(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4, __ARG5, __ARG6, __ARG7) \
__RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4), (__ARG5), (__ARG6), (__ARG7))
#define __RecFun_8(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4, __ARG5, __ARG6, __ARG7, __ARG8) \
__RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4), (__ARG5), (__ARG6), (__ARG7), (__ARG8))
#define __signals(__NAME,...) \
typedef void PLOOC_CONNECT2(__NAME,_fun_t)( __VA_ARGS__);
#define signals(__NAME,__OBJ,...) \
__signals(__NAME,_args(__OBJ __VA_ARGS__))
#define __emit(__OBJ,...) \
__PLOOC_EVAL(__RecFun_,##__VA_ARGS__) \
((__OBJ)->ptRecObj,##__VA_ARGS__);
#define emit(__NAME,__OBJ,...) \
do {sig_slot_t *ptObj = &((__OBJ)->tObject); \
do{if(__OBJ == NULL || ptObj == NULL ) break; \
PLOOC_CONNECT2(__NAME,_fun_t) *__RecFun = ptObj->ptRecFun; \
if(__RecFun != NULL) __emit(ptObj __VA_ARGS__); \
ptObj = ptObj->ptNext; \
}while(ptObj != NULL); \
}while(0)
#define __slots(__NAME,...) \
void __NAME(__VA_ARGS__);
#define slots(__NAME,__OBJ,...) \
__slots(__NAME,_args(__OBJ,##__VA_ARGS__))
#define connect(__SIG_OBJ,__SIG_NAME,__SLOT_OBJ,__SLOT_FUN) \
direct_connect(__SIG_OBJ.tObject,__SIG_NAME,__SLOT_OBJ,__SLOT_FUN)
#define disconnect(__SIG_OBJ,__SIG_NAME) \
auto_disconnect(__SIG_OBJ.tObject,__SIG_NAME)
typedef struct sig_slot_t sig_slot_t;
typedef struct sig_slot_t{
char chSenderName[SIG_NAME_MAX];
void * ptSenderObj;
void * ptRecObj;
void * ptRecFun;
sig_slot_t *ptNext;
sig_slot_t *ptPrev;
}sig_slot_t;
void direct_connect(sig_slot_t *ptSenderObj, const char *ptSender,void *ptRecObj,void *ptRecFun);
void auto_disconnect(sig_slot_t *ptSenderObj, const char *ptSender);
#undef __SIGNALS_SLOTS_CLASS_INHERIT__
#undef __SIGNALS_SLOTS_CLASS_IMPLEMENT__
#endif
#endif /* QUEUE_QUEUE_H_ */
signals_slots.c文件
#include "signals_slots.h"
void direct_connect(sig_slot_t *SenderObj, const char *ptSender,void *RecObj,void *RecFun)
{
if(SenderObj == NULL || ptSender == NULL || RecObj == NULL || RecFun == NULL){
return;
}
sig_slot_t * ptMetaObj = SenderObj;
do{
if(strstr(RecFun,"sig_")){
memcpy(ptMetaObj->chSenderName,ptSender,strlen(ptSender));
while(ptMetaObj->ptNext != NULL){
ptMetaObj = ptMetaObj->ptNext;
}
ptMetaObj->ptNext = RecObj;
ptMetaObj->ptNext->ptPrev = ptMetaObj;
ptMetaObj = ptMetaObj->ptNext;
memcpy(ptMetaObj->chSenderName,RecFun,strlen(RecFun));
break;
}
if(strcmp(ptMetaObj->chSenderName,ptSender) == 0){
sig_slot_t * ptSenderObj = malloc(sizeof(sig_slot_t));
while(ptMetaObj->ptNext != NULL){
ptMetaObj = ptMetaObj->ptNext;
}
ptMetaObj->ptNext = ptSenderObj;
ptMetaObj->ptNext->ptPrev = ptMetaObj;
ptMetaObj = ptMetaObj->ptNext;
}
ptMetaObj->ptRecFun = RecFun;
ptMetaObj->ptRecObj = RecObj;
memcpy(ptMetaObj->chSenderName,ptSender,strlen(ptSender));
}while(0);
}
void auto_disconnect(sig_slot_t *ptSenderObj, const char *ptSender)
{
if(ptSenderObj == NULL || ptSender == NULL){
return;
}
sig_slot_t * ptMetaObj = ptSenderObj;
if(strcmp(ptMetaObj->chSenderName,ptSender) == 0){
while(ptMetaObj->ptNext != NULL){
ptMetaObj = ptMetaObj->ptNext;
}
while(ptMetaObj != NULL){
ptMetaObj->ptNext = NULL;
memset(ptMetaObj->chSenderName,0,sizeof(ptMetaObj->chSenderName));
if(ptMetaObj->ptRecFun != NULL){
ptMetaObj->ptRecObj = NULL;
ptMetaObj->ptRecFun = NULL;
sig_slot_t * ptObj = ptMetaObj;
free(ptObj);
}
ptMetaObj = ptMetaObj->ptPrev;
}
}
}
代码下载地址:https://gitee.com/Aladdin-Wang/signals_slots
Part5四、使用方法与QT中的区别
71. SIG_SLOT_OBJ取代QObject
SIG_SLOT_OBJ取代QObject,且只需要在信号所在的类中定义。
82. 定义信号不同
QT在类里面声明信号,signals宏是在结构体外声明信号,并且要指定信号名称,信号所在的对象地址,和一些自定义的参数:
signals(__NAME,__OBJ,...)
example:
signals(send_sig,can_data_msg_t *ptThis,
args(
uint8_t *pchByte,
uint16_t hwLen
));
93. 发射信号不同
emit宏的括号内需要指定信号名称,信号所在的对象地址,和自定义的参数的数据:
emit(__NAME,__OBJ,...)
example:
emit(send_sig,&tCanMsgObj,
args(
tCanMsgObj.CanDATA.B,
tCanMsgObj.CanDLC
));
104. 定义槽不同
与定义信号语法类似
slots(__NAME,__OBJ,...)
example:
slots(enqueue_bytes,byte_queue_t *ptObj,
args(
void *pchByte,
uint16_t hwLength
));
115. 连接信号与槽
与QT一样一个信号可以连接多个信号或者槽,但是QT支持五种连接属性,目前仅实现了其中的Qt::DirectConnection属性,也就是同步调用方式,异步方式正在持续完善中。
#define connect(__SIG_OBJ,__SIG_NAME,__SLOT_OBJ,__SLOT_FUN) \
direct_connect(__SIG_OBJ.tObject,__SIG_NAME,__SLOT_OBJ,__SLOT_FUN)
example:
connect(&tCanMsgObj,SIGNAL(send_sig),&s_tFIFOin,SLOT(enqueue_bytes));
#define disconnect(__SIG_OBJ,__SIG_NAME) \
auto_disconnect(__SIG_OBJ.tObject,__SIG_NAME)
example:
connect(&tCanMsgObj,SIGNAL(send_sig));
Part6五、信号与槽使用示例
玩信号与槽,少不了要与面向对象打交道,众所周知,C语言不是面向对象的语言,对于面向对象的特性不是很友好,不过不用担心,福利来了,裸机思维公众号作者开源了一套面向对象的C语言框架,可以轻松助你在C语言中零代价的愉快玩耍面向对象。
信号与槽的实现正是依赖了PLOOC中诸多的宏,所以首先需要下载安装PLOOC,具体方法参考:OOPC开发从未如此简单
接下来实现一个将CAN接收的数据,存储到环形队列ringbuf的例子:
can.h文件
#include "signals_slots/signals_slots.h"
typedef struct
{
SIG_SLOT_OBJ;
uint8_t CanDLC;
union {
uint8_t B[8];
uint16_t H[4];
uint32_t W[2];
} CanDATA;
}can_data_msg_t;
signals(send_sig,can_data_msg_t *ptThis,
args(
uint8_t *pchByte,
uint16_t hwLen
));
extern can_data_msg_t tCanMsgObj;
can.c文件
#include "can.h"
can_data_msg_t tCanMsgObj;
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
HAL_StatusTypeDef status;
CAN_RxHeaderTypeDef rxheader = {0};
status = HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rxheader, (uint8_t *)tCanMsgObj.CanDATA.B);
if (HAL_OK != status)
return ;
/* get id */
if (CAN_ID_STD == rxheader.IDE)
{
tCanMsgObj.CanID_t.CanID = rxheader.StdId;
}
else
{
tCanMsgObj.CanID_t.CanID = rxheader.ExtId;
}
/* get len */
tCanMsgObj.CanDLC = rxheader.DLC;
emit(send_sig,&tCanMsgObj,
args(
tCanMsgObj.CanDATA.B,
tCanMsgObj.CanDLC
));
}
main.c文件
#include "./signals_slots/signals_slots.h"
#include "can.h"
static uint8_t s_cFIFOinBuffer[1024];
static byte_queue_t s_tFIFOin;
MX_CAN1_Init();
HAL_CAN_Start(&hcan1);
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
QUEUE_INIT(&s_tFIFOin, s_cFIFOinBuffer, sizeof(s_cFIFOinBuffer));
int main(void)
{
connect(&tCanMsgObj,SIGNAL(send_sig),&s_tFIFOin,SLOT(enqueue_bytes));
while(1){
//do something
}
}
Part7六、彩蛋
下篇文章将利用前文,实现一个支持任意类型的循环队列,敬请期待...
/* example:
#include "./queue/byte_queue.h"
uint8_t data1 = 0XAA;
uint16_t data2 = 0X55AA;
uint32_t data3 = 0X55AAAA55;
uint16_t data4[] = {0x1234,0x5678};
typedef struct data_t{
uint32_t a;
uint32_t b;
uint32_t c;
}data_t;
data_t data5 = {
.a = 0X11223344,
.b = 0X55667788,
.c = 0X99AABBCC,
};
uint8_t data[100];
static uint8_t s_hwQueueBuffer[100];
static byte_queue_t my_queue;
QUEUE_INIT(&my_queue,s_hwQueueBuffer,sizeof(s_hwQueueBuffer));
QUEUE_INIT(&my_queue,s_hwQueueBuffer,sizeof(s_hwQueueBuffer));
ENQUEUE(&my_queue,data1);//根据变量的类型,自动计算对象的大小
ENQUEUE(&my_queue,data2);
ENQUEUE(&my_queue,data3);
// 一下三种方式都可以正确存储数组
ENQUEUE(&my_queue,data4,2);//可以不指名数据类型
ENQUEUE(&my_queue,data4,uint16_t,2);//也可以指名数据类型
ENQUEUE(&my_queue,data4,uint8_t,sizeof(data4));//或者用其他类型
//一下两种方式都可以正确存储结构体类型
ENQUEUE(&my_queue,data5);//根据结构体的类型,自动计算对象的大小
ENQUEUE(&my_queue,&data5,uint8_t,sizeof(data5));//也可以以数组方式存储
ENQUEUE(&my_queue,(uint8_t)0X11); //常量默认为int型,需要强制转换数据类型
ENQUEUE(&my_queue,(uint16_t)0X2233); //常量默认为int型,需要强制转换数据类型
ENQUEUE(&my_queue,0X44556677);
ENQUEUE(&my_queue,(char)'a');//单个字符也需要强制转换数据类型
ENQUEUE(&my_queue,"bc");//字符串默认会存储空字符\0
ENQUEUE(&my_queue,"def");
// 读出全部数据
DEQUEUE(&my_queue,data,GET_QUEUE_COUNT(&my_queue));//DEQUEUE(&my_queue,data,uint8_t,GET_QUEUE_COUNT(&my_queue))
*/
六、彩蛋
下篇文章将利用前文,实现一个支持任意类型的循环队列,敬请期待...
/* example:
#include "./queue/byte_queue.h"
uint8_t data1 = 0XAA;
uint16_t data2 = 0X55AA;
uint32_t data3 = 0X55AAAA55;
uint16_t data4[] = {0x1234,0x5678};
typedef struct data_t{
uint32_t a;
uint32_t b;
uint32_t c;
}data_t;
data_t data5 = {
.a = 0X11223344,
.b = 0X55667788,
.c = 0X99AABBCC,
};
uint8_t data[100];
static uint8_t s_hwQueueBuffer[100];
static byte_queue_t my_queue;
QUEUE_INIT(&my_queue,s_hwQueueBuffer,sizeof(s_hwQueueBuffer));
QUEUE_INIT(&my_queue,s_hwQueueBuffer,sizeof(s_hwQueueBuffer));
ENQUEUE(&my_queue,data1);//根据变量的类型,自动计算对象的大小
ENQUEUE(&my_queue,data2);
ENQUEUE(&my_queue,data3);
// 一下三种方式都可以正确存储数组
ENQUEUE(&my_queue,data4,2);//可以不指名数据类型
ENQUEUE(&my_queue,data4,uint16_t,2);//也可以指名数据类型
ENQUEUE(&my_queue,data4,uint8_t,sizeof(data4));//或者用其他类型
//一下两种方式都可以正确存储结构体类型
ENQUEUE(&my_queue,data5);//根据结构体的类型,自动计算对象的大小
ENQUEUE(&my_queue,&data5,uint8_t,sizeof(data5));//也可以以数组方式存储
ENQUEUE(&my_queue,(uint8_t)0X11); //常量默认为int型,需要强制转换数据类型
ENQUEUE(&my_queue,(uint16_t)0X2233); //常量默认为int型,需要强制转换数据类型
ENQUEUE(&my_queue,0X44556677);
ENQUEUE(&my_queue,(char)'a');//单个字符也需要强制转换数据类型
ENQUEUE(&my_queue,"bc");//字符串默认会存储空字符\0
ENQUEUE(&my_queue,"def");
// 读出全部数据
DEQUEUE(&my_queue,data,GET_QUEUE_COUNT(&my_queue));//DEQUEUE(&my_queue,data,uint8_t,GET_QUEUE_COUNT(&my_queue))
*/
转载自:AIoT开源项目分享
文章来源于C语言模拟QT的信号与槽功能
原文链接:https://mp.weixin.qq.com/s/3BcMHY71lH3WPgcPwxb2LQ
相关推荐
- MFC、Qt、WPF?该用哪个?(mfc和wpf区别)
-
MFC、Qt和WPF都是流行的框架和工具,用于开发图形用户界面(GUI)应用程序。选择哪个框架取决于你的具体需求和偏好。MFC(MicrosoftFoundationClass)是微软提供的框架,...
- 一款WPF开发的通讯调试神器(支持Modbus RTU、MQTT调试)
-
我们致力于探索、分享和推荐最新的实用技术栈、开源项目、框架和实用工具。每天都有新鲜的开源资讯等待你的发现!项目介绍Wu.CommTool是一个基于C#、WPF、Prism、MaterialDesign...
- 关于面试资深C#、WPF开发工程师的面试流程和问题
-
一、开场(2-3分钟)1.欢迎应聘者,简单介绍公司和面试流程。2.询问应聘者是否对公司或岗位有初步的问题。二、项目经验与技术应用(10-20分钟)1.让应聘者详细介绍几个他参与过的C#、...
- C# WPF MVVM模式Prism框架下事件发布与订阅
-
01—前言处理同模块不同窗体之间的通信和不同模块之间不同窗体的通信,Prism提供了一种事件机制,可以在应用程序中低耦合的模块之间进行通信,该机制基于事件聚合器服务,允许发布者和订阅者之间通过事件进行...
- WPF 机械类组件动画制作流程简述(wps上怎么画机械结构简图)
-
WPF机械类组件动画制作流程简述独立观察员2025年3月4日一、创建组件创建组件用户控件,将组件的各部分“零件”(图片)拼装在一起,形成组件的默认状态:二、给运动部分加上Rend...
- C#上位机WinForm和WPF选哪个?工控老油条的"血泪史"
-
作为一个从互联网卷进工控坑的"跨界难民",在这会摸鱼的时间咱就扯一下上位机开发选框架这档子破事。当年我抱着WPF的酷炫动画一头扎进车间,结果被产线老师傅一句"你这花里胡哨的玩意...
- 【一文扫盲】WPF、Winform、Electron有什么区别?
-
近年来,随着软件开发的不断发展,开发人员面临着选择适合他们项目的各种框架和工具的挑战。在桌面应用程序开发领域,WPF、Winform和Electron是三个备受关注的技术。本文将介绍这三者的区别,帮助...
- 一个开源、免费、强大且美观的WPF控件库
-
我们致力于探索、分享和推荐最新的实用技术栈、开源项目、框架和实用工具。每天都有新鲜的开源资讯等待你的发现!项目介绍HandyControl是一套基于WPF(WindowsPresentationF...
- WPF 根据系统主题自动切换浅色与深色模式
-
WPF根据系统主题自动切换浅色与深色模式控件名:Resources作者:WPFDevelopersOrg-驚鏵原文链接[1]:https://github.com/WPFDevelopers...
- WPF与WinForm的本质区别(wpf与maui)
-
在Windows应用程序开发中,WinForm和WPF是两种主要的技术框架。它们各自有不同的设计理念、渲染机制和开发模式。本文将详细探讨WPF与WinForm的本质区别,并通过示例进行说明。渲染机制W...
- Win10/Win11效率神器再进化:微软发布PowerToys 0.90.0版本
-
IT之家4月1日消息,微软今天(4月1日)更新PowerToys,在最新发布的0.90.0版本中,修复多个BUG之外,引入多项功能更新,为Windows10、Windows...
- 一款非常漂亮的WPF管理系统(wpf架构及特性)
-
我们致力于探索、分享和推荐最新的实用技术栈、开源项目、框架和实用工具。每天都有新鲜的开源资讯等待你的发现!WPFManager项目介绍该项目是一款WPF开发的管理系统,数据库采用的MSSqlserv...
- WPF 实现描点导航(wpf按钮的点击事件)
-
WPF实现描点导航控件名:NavScrollPanel作者:WPFDevelopersOrg-驚鏵原文链接[1]:https://github.com/WPFDevelopersOrg/WPF...
- 微软更新基于Win11的Validation OS 2504:增强 .NET与WPF
-
IT之家5月1日消息,科技媒体NeoWin今天(5月1日)发布博文,报道称微软公司更新基于Windows11的ValidationOS,增强支持.NET和WPF,并优...
- WPF的技术架构与优势(wpf的前景)
-
WindowsPresentationFoundation(WPF)是一个现代化的用户界面框架,专为构建Windows应用程序而设计。它通过分层的技术架构和丰富的功能集,提供了全面的应用程...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- MVC框架 (46)
- spring框架 (46)
- 框架图 (58)
- bootstrap框架 (43)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- jpa框架 (47)
- laravel框架 (46)
- express框架 (43)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (52)
- java框架spring (43)
- grpc框架 (55)
- orm框架有哪些 (43)
- ppt框架 (48)
- 内联框架 (52)
- winform框架 (46)
- gui框架 (44)
- cad怎么画框架 (58)
- ps怎么画框架 (47)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)
- oracle提交事务 (47)