再看mfc

一.前言

       很久不用mfc,曾几何时以为以后会很少接触它了,以前总是嫌弃它,老古董,做mfc的没前途。。。

最近又用它做东西时,有了不少感悟,mfc是属于界面类库,而且是属于比较成熟,有历史的库,相较于目前做界面比较华丽多彩的duilib、BCG、QT等从外观上略显逊色,但是都了解过之后会有一些想法界面类库实现原理大致类同,类似于同一锅里蒸出的馒头,大小形态不一,本质差别不大,精通于任意一种都会对此领域有较深刻的理解,所以现在我用起mfc没有以前那种偏见了,而且现在使用对它的理解也逐渐准确了许多,本文旨在对mfc基础的用法以及整体结构做一次总结(有一些基础用法已经忘记了),对基础东西理解的不透彻,是不会有更深的体会的,大脑在不断接触一些基础知识的时候,它会自己融合创新,当基础知识量达到一定层次,就会产生质变,引发爆炸式的增长反应。

二.MFC消息

1.消息结构

      在WinUser.h中有MSG的结构定义:

typedef struct tagMSG {
    HWND        hwnd;         //句柄
    UINT        message;      //消息
    WPARAM      wParam;       //参数1
    LPARAM      lParam;       //参数2
    DWORD       time;         //产生消息的事件
    POINT       pt;           //产生消息时鼠标的位置
#ifdef _MAC
    DWORD       lPrivate;
#endif
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;

关于结构体中的每个字段的含义,注释中已经简单提到。

2.消息类型

1.windows消息分为以下几类:

2.消息ID范围

系统定义消息ID范围:[0x0000, 0x03ff]
用户自定义的消息ID范围: 
WM_USER: 0x0400-0x7FFF (例:WM_USER+10) 
WM_APP(winver> 4.0):0x8000-0xBFFF (例:WM_APP+4) 
RegisterWindowMessage:0xC000-0xFFFF【用来和其他应用程序通信,为了ID的唯一性,使用::RegisterWindowMessage来得到该范围的消息ID 】

3.窗口消息

即与窗口的内部运作有关的消息,如创建窗口,绘制窗口,销毁窗口等。

 可以是一般的窗口,也可以是MainFrame,Dialog,控件等。 

 如:WM_CREATE, WM_PAINT, WM_MOUSEMOVE, WM_CTLCOLOR, WM_HSCROLL等

4.命令消息与控件通知消息

当用户从菜单选中一个命令项目、按下一个快捷键或者点击工具栏上的一个按钮,都将发送WM_COMMAND命令消息。LOWORD(wParam)表示菜单项,工具栏按钮或控件的ID;如果是控件, HIWORD(wParam)表示控件消息类型。

     #define LOWORD(l) ((WORD)(l))

     #define HIWORD(l) ((WORD)(((DWORD)(l) >> 16) & 0xFFFF))

随着控件的种类越来越多,越来越复杂(如列表控件、树控件等),仅仅将wParam,lParam将视为一个32位无符号整数,已经装不下太多信息了。

    为了给父窗口发送更多的信息,微软定义了一个新的WM_NOTIFY消息来扩展WM_COMMAND消息。

    WM_NOTIFY消息仍然使用MSG消息结构,只是此时wParam为控件ID,lParam为一个NMHDR指针,

    不同的控件可以按照规则对NMHDR进行扩充,因此WM_NOTIFY消息传送的信息量可以相当的大。

:Window 9x 版及以后的新控件通告消息不再通过WM_COMMAND 传送,而是通过WM_NOTIFY 传送,
      但是老控件的通告消息, 比如CBN_SELCHANGE 还是通过WM_COMMAND 消息发送。

5.自定义消息

 windwos也允许程序员定义自己的消息,使用SendMessage或PostMessage来发送消息。

6.windows消息还可以分为:

(1) 队列消息(Queued Messages) 
消息会先保存在消息队列中,消息循环会从此队列中取出消息并分发到各窗口处理 
如:WM_PAINT,WM_TIMER,WM_CREATE,WM_QUIT,以及鼠标,键盘消息等。
其中,WM_PAINT,WM_TIMER只有在队列中没有其他消息的时候才会被处理,
WM_PAINT消息还会被合并以提高效率。其他所有消息以先进先出(FIFO)的方式被处理。

(2) 非队列消息(NonQueued Messages) 
消息会绕过系统消息队列和线程消息队列,直接发送到窗口过程进行处理 
如:WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR,WM_WINDOWPOSCHANGED

7.Windows系统的整个消息系统分为3个层级:

    ① Windows内核的系统消息队列

    ② App的UI线程消息队列

    ③ 处理消息的窗体对象

Windows内核维护着一个全局的系统消息队列;按照线程的不同,系统消息队列中的消息会分发到应用程序的UI线程的消息队列中;

应用程序的每一个UI线程都有自己的消息循环,会不停地从自己的消息队列取出消息,并发送给Windows窗体对象;

每一个窗体对象都使用窗体过程函数(WindowProc)来处理接收到的各种消息。

三.MFC各种类型消息使用示例

在程序示例中资源包括一个对话框,一个菜单,一个图标,一个版本信息。对话框中包含了一个list control控件(用于演示控件向主窗口发送消息即控件通知类消息WM_NOTIFY),一个按钮(用于测试程序自定义消息WM_USER响应),菜单用于测试命令消息WM_COMMAND,一个鼠标右键弹窗操作用来测试窗口消息WM_RBUTTONDOWN。

为了方便理解我把窗口类代码全部粘贴上来,代码如下:

MFC_list-controlDlg.h


// MFC_list-controlDlg.h : 头文件
//

#pragma once
#include "afxcmn.h"
#include "afxwin.h"

// CMFC_listcontrolDlg 对话框
class CMFC_listcontrolDlg : public CDialogEx
{
// 构造
public:
	CMFC_listcontrolDlg(CWnd* pParent = NULL);	// 标准构造函数

// 对话框数据
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_MFC_LISTCONTROL_DIALOG };
#endif

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV 支持


// 实现
protected:
	HICON m_hIcon;

	// 生成的消息映射函数
	virtual BOOL OnInitDialog();
	afx_msg void OnPaint();
	afx_msg HCURSOR OnQueryDragIcon();
	DECLARE_MESSAGE_MAP()
public:
	CListCtrl m_listcontrol;
	CEdit m_Edit;
public:
	void AddCols(UINT id);

	void AddNewRols();
	afx_msg void OnDeleteList();
	afx_msg void OnAddList();
	afx_msg void OnEditList();
	afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnNMDblclkList(NMHDR *pNMHDR, LRESULT *pResult); 
	afx_msg LRESULT  OnUserTest(WPARAM wParam, LPARAM lParam);
	afx_msg void OnBnClickedButton1();
};

MFC_list-controlDlg.cpp


// MFC_list-controlDlg.cpp : 实现文件
//

#include "stdafx.h"
#include "MFC_list-control.h"
#include "MFC_list-controlDlg.h"
#include "afxdialogex.h"
using namespace std;
#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#define WM_USER_TEST WM_USER + 100
// CMFC_listcontrolDlg 对话框



CMFC_listcontrolDlg::CMFC_listcontrolDlg(CWnd* pParent /*=NULL*/)
	: CDialogEx(IDD_MFC_LISTCONTROL_DIALOG, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

}

void CMFC_listcontrolDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_LIST1, m_listcontrol);
}

BEGIN_MESSAGE_MAP(CMFC_listcontrolDlg, CDialogEx)
	ON_WM_PAINT()           //窗口消息
	ON_WM_QUERYDRAGICON()   //窗口消息
	ON_COMMAND(ID_32771, &CMFC_listcontrolDlg::OnDeleteList) //菜单命令消息
	ON_COMMAND(ID_32772, &CMFC_listcontrolDlg::OnAddList)    //菜单命令消息
	ON_COMMAND(ID_32773, &CMFC_listcontrolDlg::OnEditList)   //菜单命令消息
	ON_WM_RBUTTONDOWN()     //窗口消息
	ON_WM_LBUTTONDOWN()     //窗口消息
	ON_NOTIFY(NM_RCLICK, IDC_LIST1, &CMFC_listcontrolDlg::OnNMDblclkList)  //子控件通知父窗口响应鼠标右键按下
	ON_MESSAGE(WM_USER_TEST,OnUserTest) // 自定义消息
	ON_BN_CLICKED(IDC_BUTTON1, &CMFC_listcontrolDlg::OnBnClickedButton1)   //按钮消息(关于控件特有的消息宏定义,可以查看afxmsg_.h了解更多不同控件的宏)
END_MESSAGE_MAP()


// CMFC_listcontrolDlg 消息处理程序

BOOL CMFC_listcontrolDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// 设置此对话框的图标。  当应用程序主窗口不是对话框时,框架将自动
	//  执行此操作
	SetIcon(m_hIcon, TRUE);			// 设置大图标
	SetIcon(m_hIcon, FALSE);		// 设置小图标

	

	AddCols(IDC_LIST1);

	AddNewRols();
	return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

// 如果向对话框添加最小化按钮,则需要下面的代码
//  来绘制该图标。  对于使用文档/视图模型的 MFC 应用程序,
//  这将由框架自动完成。

void CMFC_listcontrolDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // 用于绘制的设备上下文

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// 使图标在工作区矩形中居中
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// 绘制图标
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialogEx::OnPaint();
	}
}

//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CMFC_listcontrolDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}


void CMFC_listcontrolDlg::AddCols(UINT id)
{
	CRect rect;
	CWnd *wnd = NULL;
	wnd = GetDlgItem(id);
	if (NULL == wnd)
	{
		MessageBox(_T("相应控件不存在"));
	}
	wnd->GetWindowRect(&rect);
	ScreenToClient(&rect);
	int width = ((int)rect.right - (int)rect.left) / 5;
	m_listcontrol.InsertColumn(0, _T("   "), LVCFMT_CENTER, width, -1);// 插入列 
	m_listcontrol.InsertColumn(1, _T("参数名称"), LVCFMT_CENTER, width, -1);
	m_listcontrol.InsertColumn(2, _T("变量类型"), LVCFMT_CENTER, width, -1);
	m_listcontrol.InsertColumn(3, _T("变量值"), LVCFMT_CENTER, width, -1);
	m_listcontrol.InsertColumn(4, _T("变量代号"), LVCFMT_CENTER, width, -1);

}

void CMFC_listcontrolDlg::AddNewRols()
{
	vector <CString>* m_vecMessage = NULL;
	m_vecMessage = new vector <CString>;//定义一个容器,用来存放每一条记录
										//查找数据库,获得数据类表
	m_vecMessage->push_back(_T("项目序号"));
	m_vecMessage->push_back(_T("台宽"));
	m_vecMessage->push_back(_T("公式"));
	m_vecMessage->push_back(_T("M+100"));
	m_vecMessage->push_back(_T("tdb"));
	//NormalMessageVector(A2W(sTempVaule.c_str()), m_vecMessage); 
	for (int i = 0; i < 10; ++i)
	{
		m_listcontrol.InsertItem(i, m_vecMessage->at(0));//插入行
		m_listcontrol.SetItemText(i, 1, m_vecMessage->at(1));
		m_listcontrol.SetItemText(i, 2, m_vecMessage->at(2));
		m_listcontrol.SetItemText(i, 3, m_vecMessage->at(3));
		m_listcontrol.SetItemText(i, 4, m_vecMessage->at(4));
	}
}


void CMFC_listcontrolDlg::OnDeleteList()
{
	// TODO: 在此添加命令处理程序代码
	int count = m_listcontrol.GetSelectedCount();
	if (count > 0)
	{
		POSITION pos = m_listcontrol.GetFirstSelectedItemPosition();
		while (pos != NULL)
		{
			int delId = m_listcontrol.GetNextSelectedItem(pos);
			m_listcontrol.DeleteItem(delId);
		}
	}

}


void CMFC_listcontrolDlg::OnAddList()
{
	// TODO: 在此添加命令处理程序代码
}


void CMFC_listcontrolDlg::OnEditList()
{
	// TODO: 在此添加命令处理程序代码
}


void CMFC_listcontrolDlg::OnRButtonDown(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	//添加菜单
	CMenu m_menu,*pm;
	m_menu.LoadMenuW(IDR_MENU1);
	pm = m_menu.GetSubMenu(0); //获取子对话框
	CPoint pot;
	GetCursorPos(&pot);//获取鼠标当前位置
	pm->TrackPopupMenu(TPM_LEFTALIGN, pot.x, pot.y, this);//在鼠标位置弹出菜单

	CDialogEx::OnRButtonDown(nFlags, point);
}

void CMFC_listcontrolDlg::OnLButtonDown(UINT nFlags, CPoint point)
{

}

void  CMFC_listcontrolDlg::OnNMDblclkList(NMHDR *pNMHDR, LRESULT *pResult)
{
	CMenu m_menu, *pm;
	m_menu.LoadMenuW(IDR_MENU1);
	pm = m_menu.GetSubMenu(0); //获取子对话框
	CPoint pot;
	GetCursorPos(&pot);//获取鼠标当前位置
	pm->TrackPopupMenu(TPM_LEFTALIGN, pot.x, pot.y, this);//在鼠标位置弹出菜单

	
 }

LRESULT  CMFC_listcontrolDlg::OnUserTest(WPARAM wParam, LPARAM lParam)
{
	AfxMessageBox(_T("自定义消息"));
	return 0;
}

void CMFC_listcontrolDlg::OnBnClickedButton1()
{
	// TODO: 在此添加控件通知处理程序代码
	SendMessage(WM_USER_TEST, 0, 0);
}

四.windows消息宏定义结构

头文件:afxmsg_.h

1.窗口类消息宏:

太长了不贴出来,可以自己查看本机文件,类似于:

#define ON_WM_PAINT() \
	{ WM_PAINT, 0, 0, 0, AfxSig_vv, \
		(AFX_PMSG)(AFX_PMSGW) \
		(static_cast< void (AFX_MSG_CALL CWnd::*)(void) > ( &ThisClass :: OnPaint)) },

#define ON_WM_SYNCPAINT() \
	{ WM_SYNCPAINT, 0, 0, 0, AfxSig_vv, \
		(AFX_PMSG)(AFX_PMSGW) \
		(static_cast< void (AFX_MSG_CALL CWnd::*)(void) > ( &ThisClass :: OnSyncPaint)) },

#define ON_WM_CLOSE() \
	{ WM_CLOSE, 0, 0, 0, AfxSig_vv, \
		(AFX_PMSG)(AFX_PMSGW) \
		(static_cast< void (AFX_MSG_CALL CWnd::*)(void) > ( &ThisClass :: OnClose)) },

2.控制通知消息的消息映射表

Message map tables for Control Notification messages

包含各种按钮,列表,richedit等的宏定义

// Message map tables for Control Notification messages

// Static control notification codes
#define ON_STN_CLICKED(id, memberFxn) \
	ON_CONTROL(STN_CLICKED, id, memberFxn)
#define ON_STN_DBLCLK(id, memberFxn) \
	ON_CONTROL(STN_DBLCLK, id, memberFxn)
#define ON_STN_ENABLE(id, memberFxn) \
	ON_CONTROL(STN_ENABLE, id, memberFxn)
#define ON_STN_DISABLE(id, memberFxn) \
	ON_CONTROL(STN_DISABLE, id, memberFxn)


// Edit Control Notification Codes
#define ON_EN_SETFOCUS(id, memberFxn) \
	ON_CONTROL(EN_SETFOCUS, id, memberFxn)
#define ON_EN_KILLFOCUS(id, memberFxn) \
	ON_CONTROL(EN_KILLFOCUS, id, memberFxn)
#define ON_EN_CHANGE(id, memberFxn) \
	ON_CONTROL(EN_CHANGE, id, memberFxn)
#define ON_EN_UPDATE(id, memberFxn) \
	ON_CONTROL(EN_UPDATE, id, memberFxn)
#define ON_EN_ERRSPACE(id, memberFxn) \
	ON_CONTROL(EN_ERRSPACE, id, memberFxn)
#define ON_EN_MAXTEXT(id, memberFxn) \
	ON_CONTROL(EN_MAXTEXT, id, memberFxn)
#define ON_EN_HSCROLL(id, memberFxn) \
	ON_CONTROL(EN_HSCROLL, id, memberFxn)
#define ON_EN_VSCROLL(id, memberFxn) \
	ON_CONTROL(EN_VSCROLL, id, memberFxn)

#define ON_EN_ALIGN_LTR_EC(id, memberFxn) \
	ON_CONTROL(EN_ALIGN_LTR_EC, id, memberFxn)
#define ON_EN_ALIGN_RTL_EC(id, memberFxn) \
	ON_CONTROL(EN_ALIGN_RTL_EC, id, memberFxn)

// Richedit Control Notification Codes
#define ON_EN_IMECHANGE(id, memberFxn) \
	ON_CONTROL(EN_IMECHANGE, id, memberFxn)
#define ON_EN_ALIGNLTR(id, memberFxn) \
	ON_CONTROL(EN_ALIGNLTR, id, memberFxn)
#define ON_EN_ALIGNRTL(id, memberFxn) \
	ON_CONTROL(EN_ALIGNRTL, id, memberFxn)

// Animation Control Notification Codes
#define ON_ACN_START(id, memberFxn) \
	ON_CONTROL(ACN_START, id, memberFxn)
#define ON_ACN_STOP(id, memberFxn) \
	ON_CONTROL(ACN_STOP, id, memberFxn)

// User Button Notification Codes
#define ON_BN_CLICKED(id, memberFxn) \
	ON_CONTROL(BN_CLICKED, id, memberFxn)
#define ON_BN_DOUBLECLICKED(id, memberFxn) \
	ON_CONTROL(BN_DOUBLECLICKED, id, memberFxn)
#define ON_BN_SETFOCUS(id, memberFxn) \
	ON_CONTROL(BN_SETFOCUS, id, memberFxn)
#define ON_BN_KILLFOCUS(id, memberFxn) \
	ON_CONTROL(BN_KILLFOCUS, id, memberFxn)

// old BS_USERBUTTON button notifications - obsolete in Win31
#define ON_BN_PAINT(id, memberFxn) \
	ON_CONTROL(BN_PAINT, id, memberFxn)
#define ON_BN_HILITE(id, memberFxn) \
	ON_CONTROL(BN_HILITE, id, memberFxn)
#define ON_BN_UNHILITE(id, memberFxn) \
	ON_CONTROL(BN_UNHILITE, id, memberFxn)
#define ON_BN_DISABLE(id, memberFxn) \
	ON_CONTROL(BN_DISABLE, id, memberFxn)

// Listbox Notification Codes
#define ON_LBN_ERRSPACE(id, memberFxn) \
	ON_CONTROL(LBN_ERRSPACE, id, memberFxn)
#define ON_LBN_SELCHANGE(id, memberFxn) \
	ON_CONTROL(LBN_SELCHANGE, id, memberFxn)
#define ON_LBN_DBLCLK(id, memberFxn) \
	ON_CONTROL(LBN_DBLCLK, id, memberFxn)
#define ON_LBN_SELCANCEL(id, memberFxn) \
	ON_CONTROL(LBN_SELCANCEL, id, memberFxn)
#define ON_LBN_SETFOCUS(id, memberFxn) \
	ON_CONTROL(LBN_SETFOCUS, id, memberFxn)
#define ON_LBN_KILLFOCUS(id, memberFxn) \
	ON_CONTROL(LBN_KILLFOCUS, id, memberFxn)

// Check Listbox Notification codes
#define CLBN_CHKCHANGE (40)
#define ON_CLBN_CHKCHANGE(id, memberFxn) \
	ON_CONTROL(CLBN_CHKCHANGE, id, memberFxn)

// Combo Box Notification Codes
#define ON_CBN_ERRSPACE(id, memberFxn) \
	ON_CONTROL(CBN_ERRSPACE, id, memberFxn)
#define ON_CBN_SELCHANGE(id, memberFxn) \
	ON_CONTROL(CBN_SELCHANGE, id, memberFxn)
#define ON_CBN_DBLCLK(id, memberFxn) \
	ON_CONTROL(CBN_DBLCLK, id, memberFxn)
#define ON_CBN_SETFOCUS(id, memberFxn) \
	ON_CONTROL(CBN_SETFOCUS, id, memberFxn)
#define ON_CBN_KILLFOCUS(id, memberFxn) \
	ON_CONTROL(CBN_KILLFOCUS, id, memberFxn)
#define ON_CBN_EDITCHANGE(id, memberFxn) \
	ON_CONTROL(CBN_EDITCHANGE, id, memberFxn)
#define ON_CBN_EDITUPDATE(id, memberFxn) \
	ON_CONTROL(CBN_EDITUPDATE, id, memberFxn)
#define ON_CBN_DROPDOWN(id, memberFxn) \
	ON_CONTROL(CBN_DROPDOWN, id, memberFxn)
#define ON_CBN_CLOSEUP(id, memberFxn)  \
	ON_CONTROL(CBN_CLOSEUP, id, memberFxn)
#define ON_CBN_SELENDOK(id, memberFxn)  \
	ON_CONTROL(CBN_SELENDOK, id, memberFxn)
#define ON_CBN_SELENDCANCEL(id, memberFxn)  \
	ON_CONTROL(CBN_SELENDCANCEL, id, memberFxn)

3.消息映射扩展

比如自定义消息的消息映射宏等

// User extensions for message map entries

// for Windows messages
#define ON_MESSAGE(message, memberFxn) \
	{ message, 0, 0, 0, AfxSig_lwl, \
		(AFX_PMSG)(AFX_PMSGW) \
		(static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM) > \
		(memberFxn)) },

// for Registered Windows messages
#define ON_REGISTERED_MESSAGE(nMessageVariable, memberFxn) \
	{ 0xC000, 0, 0, 0, (UINT_PTR)(UINT*)(&nMessageVariable), \
		/*implied 'AfxSig_lwl'*/ \
		(AFX_PMSG)(AFX_PMSGW) \
		(static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM) > \
		(memberFxn)) },

// for Thread messages
#define ON_THREAD_MESSAGE(message, memberFxn) \
	{ message, 0, 0, 0, AfxSig_vwl, \
		(AFX_PMSG)(AFX_PMSGT) \
		(static_cast< void (AFX_MSG_CALL CWinThread::*)(WPARAM, LPARAM) > \
		(memberFxn)) },

// for Registered Windows messages
#define ON_REGISTERED_THREAD_MESSAGE(nMessageVariable, memberFxn) \
	{ 0xC000, 0, 0, 0, (UINT_PTR)(UINT*)(&nMessageVariable), \
		/*implied 'AfxSig_vwl'*/ \
		(AFX_PMSG)(AFX_PMSGT) \
		(static_cast< void (AFX_MSG_CALL CWinThread::*)(WPARAM, LPARAM) > \
		(memberFxn)) },

五.参考文章:

MFC消息分类与消息队列

MFC的自定义消息的定义与使用

MFC 消息类型

MFC List Control 控件

  • 0
    点赞
  • 0
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:Age of Ai 设计师:meimeiellie 返回首页
评论

打赏作者

小哈龙

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值