MFC学习

1 MFC框架组成

  1. 解决方案相关文件

.sln文件和.suo文件为MFC自动生成的解决方案文件,它包含当前解决方案中的工程信息,存储解决方案的设置。

  1. 工程相关文件

.vcxproj文件是MFC生成的工程文件,它包含当前工程的设置和工程所包含的文件等信息。.vcxproj.filters文件存放工程的虚拟目录信息,也就是在解决方案浏览器中的目录结构信息。

  1. 应用程序头文件和源文件

应用程序向导会根据应用程序的类型(单文档、多文档或基于对话框的程序)自动生成一些头文件和源文件,这些文件是工程的主体部分,用于实现主框架、文档、视图等。
HelloWorld.h:应用程序的主头文件。主要包含由CWinAppEx类派生的CHelloWorldApp类的声明,以及CHelloWorldApp类的全局对象theApp的声明。

   HelloWorld.cpp:应用程序的主源文件。主要包含CHelloWorldApp类的实现,CHelloWorldApp类的全局对象theApp的定义等。

   MainFrm.h和MainFrm.cpp:通过这两个文件从CFrameWndEx类派生出CMainFrame类,用于创建主框架、菜单栏、工具栏和状态栏等。

   HelloWorldDoc.h和HelloWorldDoc.cpp:这两个文件从CDocument类派生出文档类CHelloWorldDoc,包含一些用来初始化文档、串行化(保存和装入)文档和调试的成员函数。

   HelloWorldView.h和HelloWorldView.cpp:它们从CView类派生出名为CHelloWorldView的视图类,用来显示和打印文档数据,包含了一些绘图和用于调试的成员函数。

   ClassView.h和ClassView.cpp:由CDockablePane类派生出CClassView类,用于实现应用程序界面左侧面板上的Class View。

   FileView.h和FileView.cpp:由CDockablePane类派生出CFileView类,用于实现应用程序界面左侧面板上的File View。

   OutputWnd.h和OutputWnd.cpp:由CDockablePane类派生出COutputWnd类,用于实现应用程序界面下侧面板Output。

   PropertiesWnd.h和PropertiesWnd.cpp:由CDockablePane类派生出CPropertiesWnd类,用于实现应用程序界面右侧面板Properties。

   ViewTree.h和ViewTree.cpp:由CTreeCtrl类派生出CViewTree类,用于实现出现在ClassView和FileView等中的树视图。
  1. 资源文件

一般我们使用MFC生成窗口程序都会有对话框、图标、菜单等资源,应用程序向导会生成资源相关文件:res目录、HelloWorld.rc文件和Resource.h文件。

   res目录:工程文件夹下的res目录中含有应用程序默认图标、工具栏使用图标等图标文件。

   HelloWorld.rc:包含默认菜单定义、字符串表和加速键表,指定了默认的About对话框和应用程序默认图标文件等。

   Resource.h:含有各种资源的ID定义。

通过对比可以发现,MFC应用程序的运行流程与SDK程序是类似的,都是先进行一些初始化过程,再注册并创建窗口,然后显示、更新窗口,最后进入消息循环,消息都由窗口过程函数处理。
实际上在前面自动生成的框架中比较重要的类包括以下几个:CHelloWorldApp、CMainFrame、CHelloWorldDoc和CHelloWorldView,至于其他的类比如CClassView、CFileView等都是在框架窗口(CMainFrame)上创建的面板等,不是必要的。就四个主要类的关系简单讲下:

  • CHelloWorldApp类处理消息,将收到的消息分发给相应的对象。
  • CMainFrame是视图CHelloWorldView的父窗口,视图CHelloWorldView就显示在CMainFrame的客户区中。
  • 视图类CHelloWorldView用来显示文档类CHelloWorldDoc中的数据,并根据对视图类的操作修改文档类的数据。
  • 一个视图类只能跟一个文档类相联系,而一个文档类可以跟多个视图类相联系。

2 消息 wParam lParam

在Win32中,wParam lParam是用来传递消息数据的最常用的手段.
比如,对按键消息来说,鼠标的X和Y的坐标被压缩进lParam中
对MFC来说,消息可以用多样的类型参数来传递,对用户自定义消息
来说,只能用wParam和lParam来传递。

在Win32 SDK中消息本身是作为一个结构体记录传递给应用程序的,这个记录中包含了消息的类型以及其他信息。这个记录类型叫做MSG,它在window中是这样声明的:

typedef   struct   tagMSG   {           //   msg     
      HWND   hwnd;       //窗口句柄 
      UINT   message;       //消息常量标识符 
      WPARAM   wParam;     //32位消息的特定附加信息,具体表示什么处决于message 
      LPARAM   lParam;     //32位消息的特定附加信息,具体表示什么处决于message 
      DWORD   time;       //消息创建时的时间 
      POINT   pt;               //消息创建时的鼠标位置 
}   MSG; 

hwnd 接收消息的32位窗口句柄。窗口可以是任何类型的屏幕对象,
因为Win32能够维护大多数可视对象的句柄(窗口、对话框、按钮、编辑框等)。
message 用于区别其他消息的常量值,这些常量可以是Windows单元中预定义的常量,也可以是自定义的常量。
wParam 通常是一个与消息有关的常量值,也可能是窗口或控件的句柄。
lParam 通常是一个指向内存中数据的指针。
由于wParam,lParam和指针都是32位的,需要时可以强制类型转换。具体表示什么,与message相关,他们是事先定义好的。
如果自定义消息:#define WM_MYMESSAGE WM_USER+100,需确定wParam,lParam的意义
(假设wParam=0时发送数据,wParam=1时接收数据,lParam为CMyClass* 指针,指向一个CMyClass对象,准备要发送的数据或接收数据
发送WM_MYMESSAGE时 SendMessage(hwnd,WM_MYMESSAGE,0,pMyClassObject)
接收消息的窗口,接收WM_MYMESSAGE中(CMyClass*)lParam参数即pMyClassObject传过来的数据

2.1 MFC消息映射机制

窗口消息一般由三个部分组成:

  1. 一个无符号整数,是消息值;
  2. 消息附带的WPARAM类型的参数;
  3. 消息附带的LPARAM类型的参数。

其实我们一般所说的消息是狭义上的消息值,也就是一个无符号整数,经常被定义为宏。

Windows消息分类:

1.标准Windows消息。除WM_COMMAND外以WM_开头的消息是标准消息。例如,WM_CREATEWM_CLOSE2.命令消息。消息名为WM_COMMAND,消息中附带了标识符ID来区分是来自哪个菜单、工具栏按钮或加速键的消息。
3.通知消息。通知消息一般由列表框等子窗口发送给父窗口,消息名也是WM_COMMAND,其中附带了控件通知码来区分控件。

CWnd的派生类都可以接收到标准Windows消息、通知消息和命令消息。命令消息还可以由文档类等接收。

用户自定义消息是实际上就是用户定义一个宏作为消息,此宏的值应该大于等于WM_USER,然后此宏就可以跟系统消息一样使用,窗口类中可以定义它的处理函数。

除了一些没有基类的类或CObject的直接派生类外,其他的类都可以自动生成消息映射表。下面的讲解以HelloWorld的CMainFrame为例:

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWndEx)   
    ON_WM_CREATE()   
    ON_COMMAND(ID_VIEW_CUSTOMIZE, &CMainFrame::OnViewCustomize)   
    ON_REGISTERED_MESSAGE(AFX_WM_CREATETOOLBAR, &CMainFrame::OnToolbarCreateNew)   
    ON_COMMAND_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_WINDOWS_7, &CMainFrame::OnApplicationLook)   
    ON_UPDATE_COMMAND_UI_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_WINDOWS_7, &CMainFrame::OnUpdateApplicationLook)   
    ON_WM_SETTINGCHANGE()   
END_MESSAGE_MAP()

BEGIN_MESSAG_MAPEND_MESSAGE_MAP之间的内容成为消息映射入口项。消息映射除了在CMainFrame的实现文件中添加消息映射表外,在类的定义文件MainFrm.h中还会添加一个宏调用:

 	DECLARE_MESSAGE_MAP()

一般这个宏调用写在类定义的结尾处。

添加消息处理函数

如何添加消息处理函数呢?不管是自动还是手动添加都有三个步骤:

   1.在类定义中加入消息处理函数的函数声明,注意要以afx_msg打头。例如MainFrm.h中WM_CREATE的消息处理函数的函数声明:afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);。

   2.在类的消息映射表中添加该消息的消息映射入口项。例如WM_CREATE的消息映射入口项:ON_WM_CREATE()。

   3.在类实现中添加消息处理函数的函数实现。例如,MainFrm.cpp中WM_CREATE的消息处理函数的实现:
   int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
   {
       ......
   }

通过以上三个步骤以后,WM_CREATE等消息就可以在窗口类中被消息处理函数处理了。

3 对话框

MFC学习教程第一个程序——加法运算

一、创建基于对话框的应用程序框架
在Application type下选择Dialog based(基于对话框),其他使用默认设置,点“Finish”。
在这里插入图片描述
在Resource View视图(资源视图)中可以看到工程Addition的资源树,展开Addition.rc,下面有四个子项:Dialog(对话框)、Icon(图标)、String Table(字符串表)和Version(版本)。

二、 双击IDD_ADDITION_DIALOG,对经常使用的几个属性作简单说明

   1.ID:对话框ID,唯一标识对话框资源,可以修改。此处为IDD_ADDITION_DIALOG,我们不修改它。

   2.Caption:对话框标题。我们将其修改为“加法计算器”。

   3.Border:边框类型。有四种类型:None、Thin、Resizing和Dialog Frame。我们使用默认的Dialog Frame。

   4.Maximize:是否使用最大化按钮。我们使用默认的False。

   5.Minimize:是否使用最小化按钮。同样我们使用默认的False。

   6.Style:对话框类型。有三种类型:Overlapped(重叠窗口)、Popup(弹出式窗口)和Child(子窗口)。
   弹出式窗口比较常见。我们使用默认的Popup类型。

   7.System Menu:是否带有标题栏左上角的系统菜单,包括移动、关闭等菜单项。我们使用默认的True。

   8.Title Bar:是否带有标题栏。我们使用默认的True。

   9.Font(Size):字体类型和字体大小。如果将其修改为非系统字体,则Use System自动改为False。
   而如果Use System原来为False,将其修改为True,则Font(Size)自动设置为系统字体。这里我们使用默认的系统字体。

在这里插入图片描述
程序自动创建了对话框模板IDD_ADDITION_DIALOG,并自动生成了对话框类CAdditionDlg,它是从CDialogEx类派生的。

   首先为被加数的编辑框IDC_SUMMAND_EDIT(被加数)添加变量。

   1.在编辑框上点右键,在右键菜单中选择“添加变量”。弹出添加成员变量的向导对话框。

   2.我们想为其添加值变量而不是控件变量,所以对话框中“Category”(类别)下的组合框中选择Value(值)

   3.在“Variable name”中写入自定义的变量名。为其取名m_editSummand。

在这里插入图片描述
注意,类的成员变量名一般以m_打头,以标识它是一个成员变量。

参照此方法,再分别为加数的编辑框IDD_ADDEND_EDIT添加double型变量m_editAddend、和的编辑框IDD_SUM_EDIT添加double型变量m_editSum。

三、 双击“计算”按钮,MFC会自动为其在CAdditionDlg类中添加BN_CLICKED消息的处理函数OnBnClickedAddButton()。
(此处可以使用其他方法: 1. “Add Event Handler…” 2. 在“计算”按钮的“属性”,点“Control Events”按钮【类似闪电标志】)
在这里插入图片描述

void CAdditionDlg::OnBnClickedAddButton()   
{   
    // TODO: Add your control notification handler code here   
	CString strEDIT1, strEDIT2, strEDIT3;
	GetDlgItem(IDC_SUMMAND_EDIT)->GetWindowText(strEDIT1);//从IDC_SUMMAND_EDIT控件获取被加数
	GetDlgItem(IDC_ADDEND_EDIT)->GetWindowText(strEDIT2);
	int a, b, c;
	a = _ttol(strEDIT1);//将CString转换为int,double,float等数据类型
	b = _ttol(strEDIT2);
	c = a + b;
	strEDIT3.Format(_T("%d"), c);
	SetDlgItemText(IDC_SUM_EDIT, strEDIT3); //在ID为IDC_SUM_EDIT的控件上显示转换为CString字符串的数据
}
  • CString:MFC控件从面板上得到数据一般是CString
  • GetDlgItem(控件ID)->GetwindowText(CString字符串):获取ID号为xxx的CString字符串
  • _ttol(CString字符串):将CString转换为int,double,float等数据类型
  • CString字符串.Format(_T("%d"),类型为int、double、float的数据):将这样的数据转换为CString
  • SetDlgItemText(控件ID,CString字符串):在ID为xxx的控件上显示转换为CString字符串的数据
    在这里插入图片描述

四、 用户往往会改变控件的属性

例如,在编辑框中输入字符串,或者改变组合框的选中项,又或者改变复选框的选中状态等。控件的属性改变后MFC会相应修改控件关联变量的值。这种同步的改变是通过MFC为对话框类自动生成的成员函数DoDataExchange()来实现的,这也叫做对话框的数据交换和检验机制。

为三个编辑框添加了变量以后,多了三条DDX_Text调用语句

void CAdditionDlg::DoDataExchange(CDataExchange* pDX)   
{   
    // 处理MFC默认的数据交换   
    CDialogEx::DoDataExchange(pDX);   
    // 处理控件IDC_SUMMAND_EDIT和变量m_editSummand之间的数据交换   
    DDX_Text(pDX, IDC_SUMMAND_EDIT, m_editSummand);   
    // 处理控件IDC_ADDEND_EDIT和变量m_editAddend之间的数据交换   
    DDX_Text(pDX, IDC_ADDEND_EDIT, m_editAddend);   
    // 处理控件IDC_SUM_EDIT和变量m_editSum之间的数据交换   
    DDX_Text(pDX, IDC_SUM_EDIT, m_editSum);   
}

如果我们在程序运行界面中输入被加数,则通过CAddition的DoDataExchange()函数可以将输入的值保存到m_editSummand变量中,反之如果程序运行中修改了变量m_editSummand的值,则通过CAddition的DoDataExchange()函数也可以将新的变量值显示到被加数的编辑框中。
但是这种数据交换机制中,DoDataExchange()并不是被自动调用的,而是需要我们在程序中调用CDialogEx::UpdateData()函数,由UpdateData()函数再去自动调用DoDataExchange()的。

    CDialogEx::UpdateData()函数的原型为:
    BOOL UpdateData(BOOL bSaveAndValidate = TRUE);

// bSaveAndValidate用于指示数据传输的方向,TRUE表示从控件传给变量,FALSE表示从变量传给控件。
// 默认值是TRUE,即从控件传给变量。

返回值:CDialogEx::UpdateData()函数的返回值表示操作是否成功,成功则返回TRUE,否则返回FALSE。

4 消息对话框

MFC提供了两个函数可以直接生成指定风格的消息对话框,而不需要我们在每次使用的时候都要去创建对话框资源和生成对话框类等。这两个函数就是CWnd类的成员函数MessageBox()和全局函数AfxMessageBox()

1. CWnd::MessageBox()函数

  int MessageBox(
           LPCTSTR lpszText,
           LPCTSTR lpszCaption = NULL,
           UINT nType = MB_OK 
       );

// lpszText:需要显示的消息字符串。

//lpszCaption:消息对话框的标题字符串。默认值为NULL。取值为NULL时使用默认标题。

//nType:消息对话框的风格和属性。默认为MB_OK风格,即只有“确定”按钮。

//nType的取值可以是下面两个表中任取一个值,也可以是各取一个值的任意组合。即可以指定一个对话框类型,也可以指定一个对话框图标,还可以两者都设定。

在这里插入图片描述
2. AfxMessageBox()函数

int AfxMessageBox(
           LPCTSTR lpszText,
           UINT nType = MB_OK,
           UINT nIDHelp = 0 
       );

 //lpszText:同CWnd::MessageBox()函数

//nType:CWnd::MessageBox()函数

//nIDHelp:此消息的帮助的上下文ID。默认值为0,取0时表示要使用应用程序的默认帮助上下文。

3. CWnd::MessageBox()和AfxMessageBox()的返回值

我们在调用了上面两个函数后,都可以弹出模态消息对话框。消息对话框关闭后,我们也都可以得到它们的返回值。两者的返回值就是用户在消息对话框上单击的按钮的ID,可以是以下值:

  IDABORT:单击“终止”按钮。
  IDCANCEL:单击“取消”按钮。
  IDIGNORE:单击“忽略”按钮。
  IDNO:单击“否”按钮。
  IDOK:单击“确定”按钮。
  IDRETRY:单击“重试”按钮。
  IDYES:单击“是”按钮

4. 应用
修改后的CAdditionDlg::OnBnClickedAddButton()函数如下:

void CAdditionDlg::OnBnClickedAddButton()   
{     
    INT_PTR nRes;      
    // 显示消息对话框   
    nRes = MessageBox(_T("您确定要进行加法计算吗?"), _T("加法计算器"), MB_OKCANCEL | MB_ICONQUESTION);   
    // 判断消息对话框返回值。如果为IDCANCEL就return,否则继续向下执行   
    if (IDCANCEL == nRes)   
        return;   
   
    // 将各控件中的数据保存到相应的变量   
    UpdateData(TRUE);   
   
    // 将被加数和加数的加和赋值给m_editSum   
    m_editSum = m_editSummand + m_editAddend;   
   
    // 根据各变量的值更新相应的控件。和的编辑框会显示m_editSum的值   
    UpdateData(FALSE);   
    // 设置属性对话框为向导对话框   
    //sheet.SetWizardMode();   
}

在这里插入图片描述

5 文件对话框

http://www.jizhuomi.com/school/c/166.html
文件对话框分为打开文件对话框保存文件对话框,相信大家在Windows系统中经常见到这两种文件对话框。例如,很多编辑软件像记事本等都有“打开”选项,选择“打开”后会弹出一个对话框,让我们选择要打开文件的路径,这个对话框就是打开文件对话框;除了“打开”选项一般还会有“另存为”选项,选择“另存为”后往往也会有一个对话框弹出,让我们选择保存路径,这就是保存文件对话框。
文件对话框类CFileDialog

 explicit CFileDialog(
   BOOL bOpenFileDialog,
   LPCTSTR lpszDefExt = NULL,
   LPCTSTR lpszFileName = NULL,
   DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
   LPCTSTR lpszFilter = NULL,
   CWnd* pParentWnd = NULL,
   DWORD dwSize = 0,
   BOOL bVistaStyle = TRUE
);

//bOpenFileDialog:指定要创建的文件对话框的类型。设为TRUE将创建打开文件对话框,否则将创建保存文件对话框。

//lpszDefExt:默认的文件扩展名。如果用户在文件名编辑框中没有输入扩展名,则由lpszDefExt指定的扩展名将被自动添加到文件名后。默认为NULL。

//lpszFileName:文件名编辑框中显示的初始文件名。如果为NULL,则不显示初始文件名。

//dwFlags:文件对话框的属性,可以是一个值也可以是多个值的组合。关于属性值的定义,可以在MSDN中查找结构体OPENFILENAME,元素Flags的说明中包含了所有属性值。默认为OFN_HIDEREADONLY和OFN_OVERWRITEPROMPT的组合,OFN_HIDEREADONLY表示隐藏文件对话框上的“Read Only”复选框,OFN_OVERWRITEPROMPT表示在保存文件对话框中如果你选择的文件存在了,就弹出一个消息对话框,要求确定是否要覆盖此文件。

// lpszFilter:文件过滤器,它是由若干字符串对组成的一个字符串序列。如果指定了文件过滤器,则文件对话框中只有符合过滤条件的文件显示在文件列表中待选择。给大家看看VS2010 MSDN中给出的一个例子:

       static TCHAR BASED_CODE szFilter[] = _T("Chart Files (*.xlc)|*.xlc|Worksheet Files (*.xls)|*.xls|Data Files (*.xlc;*.xls)|*.xlc; *.xls|All Files (*.*)|*.*||");

	//	这样设置过滤器以后,文件对话框的扩展名组合框中将有四个选项:Chart Files (*.xlc)、Worksheet Files (*.xls)、Data Files(*.xlc;*.xls)和All Files (*.*),大家可以看到每种文件的扩展名规定都是一个字符串对,例如Chart Files的过滤字符串是Chart Files(*.xlc)和*.xlc成对出现的。

// pParentWnd:文件对话框的父窗口的指针。

//dwSize:OPENFILENAME结构体的大小。不同的操作系统对应不同的dwSize值。MFC通过此参数决定文件对话框的适当类型(例如,创建Windows 2000文件对话框还是XP文件对话框)。默认为0,表示MFC将根据程序运行的操作系统版本来决定使用哪种文件对话框。

//bVistaStyle:指定文件对话框的风格,设为TRUE则使用Vista风格的文件对话框,否则使用旧版本的文件对话框。此参数仅在Windows Vista中编译时适用。

下面列出几个CFileDialog类的成员函数,我们可以使用它们获得文件对话框中的各种选择。

GetFileExt():获得选定文件的后缀名。 GetFileName():获得选定文件的名称,包括后缀名。
GetFileTitle():获得选定文件的标题,即不包括后缀名。 GetFolderPath():获得选定文件的目录。
GetNextPathName():获得下一个选定的文件的路径全名。 GetPathName():获得选定文件的路径全名。
GetReadOnlyPref():获得是否“以只读方式打开”。 GetStartPosition():获得文件名列表中的第一个元素的位置。

6 字体对话框

http://www.jizhuomi.com/school/c/167.html
MFC使用CFontDialog类封装了字体对话框的所有操作。字体对话框也是一种模态对话框。

CFontDialog(
   LPLOGFONT lplfInitial = NULL,
   DWORD dwFlags = CF_EFFECTS | CF_SCREENFONTS,
   CDC* pdcPrinter = NULL,
   CWnd* pParentWnd = NULL 
);

//lplfInitial:指向LOGFONT结构体数据的指针,可以通过它设置字体的一些特征。

//dwFlags:指定选择字体的一个或多个属性,详情可在MSDN中查阅。

//pdcPrinter:指向一个打印设备上下文的指针。

//pParentWnd:指向字体对话框父窗口的指针。

上面的构造函数中第一个参数为LOGFONT指针,LOGFONT结构体中包含了字体的大部分特征,包括字体高度、宽度、方向、名称等等。下面是此结构体的定义:

typedef struct tagLOGFONT {
    LONG lfHeight;
    LONG lfWidth;
    LONG lfEscapement;
    LONG lfOrientation;
    LONG lfWeight;
    BYTE lfItalic;
    BYTE lfUnderline;
    BYTE lfStrikeOut;
    BYTE lfCharSet;
    BYTE lfOutPrecision;
    BYTE lfClipPrecision;
    BYTE lfQuality;
    BYTE lfPitchAndFamily;
    TCHAR lfFaceName[LF_FACESIZE];
} LOGFONT;

获取字体对话框中所选字体

我们在字体对话框中选择了字体后,如何获取选定的字体呢?我们可以通过CFontDialog类的成员变量m_cf间接获得选定字体的CFont对象。m_cf是CHOOSEFONT类型的变量,CHOOSEFONT结构体定义如下:

typedef struct {
    DWORD lStructSize;
    HWND hwndOwner;
    HDC hDC;
    LPLOGFONT lpLogFont;
    INT iPointSize;
    DWORD Flags;
    COLORREF rgbColors;
    LPARAM lCustData;
    LPCFHOOKPROC lpfnHook;
    LPCTSTR lpTemplateName;
    HINSTANCE hInstance;
    LPTSTR lpszStyle;
    WORD nFontType;
    INT nSizeMin;
    INT nSizeMax;
} CHOOSEFONT, *LPCHOOSEFONT;

CHOOSEFON结构体中有个成员lpLogFont,它是指向LOGFONT结构体变量的指针,就像上面所说,LOGFONT中包含了字体特征,例如,我们可以通过LOGFONT的lfFaceName得知字体名。

我们最终要获得的是所选择字体的CFont对象,有了字体的LOGFONT怎样获得对应的CFont对象呢?使用CFont类的成员函数CreateFontIndirect可以达到此目的。函数原型如下:

   BOOL CreateFontIndirect(const LOGFONT* lpLogFont );

参数是LOGFONT指针类型,我们可以传入CFontDialog类成员变量m_cf的lpLogFont成员,就可以得到所选字体的CFont对象了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值