VS2015之博大精深的MFC项目开发(一)

VS2015之博大精深的MFC项目开发(一)



第一章 MFC基础篇

1、MFC01-2:Win32程序资源管理

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


1.1 讲解MessageBox(在windows中如何输出)

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述


1.2 给我们的软件插入一个图标

在这里插入图片描述

把多余不需要的尺寸大小的图标删除掉,然后打开外部编辑器对图标进行编辑:
在这里插入图片描述

在这里插入图片描述

如上图所示,我们自己编辑的图标很难看,我们可以导入一个现成的图标:
在这里插入图片描述

把找到的图标粘贴在自己这个项目的目录下,然后再导入这个图标:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们发现图标没有变化,这是怎么回事呢?
因为还有一个旧的图标(IDI_ICON1)在资源里面,如果工程里有不止一个图标,它会缺省选第一个图标去插入,也就是ID最小的图标作为应用程序的图标
在这里插入图片描述

所以,我们把IDI_ICON1这个图标资源给删掉,重新编译生成,如果目录中该exe文件的图标还没有发生变化,把它拷贝到外面也许就变了,可能这是由于windows系统还没识别出来。


1.3 对话框程序是如何建立的(在windows中如何输入)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


2、MFC01-3:基于对话框的Win32程序开发

2.1 如何在对话框中实现输入、实现关闭呢

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

IDCANCEL和IDOK都是系统的按钮,你按ESC或者Alt+F4或者右上角关闭按钮,都会走IDCANCEL这条通道;
你在某个编辑框中,按回车键的话,系统会默认你点击的是IDOK这个缺省按钮。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

上述这两个函数的最后一个参数,意思是否支持负数(signed和unsigned)。

在这里插入图片描述

在这里插入图片描述


3、MFC02-1:四则运算的Win32程序开发

在这里插入图片描述

在这里插入图片描述

我们给对话框插入一个位图,在工具栏里有一个图片控件:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


3.1 添加菜单

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

只是用来显示、弹出的菜单项(例如上图的系统)它的属性Popup是True,我只负责弹出,我并不负责执行命令;
而真实的菜单项(例如退出)它的Popup是False,它是会执行命令的,也就是说它会执行消息传递的。

还可以给菜单添加快捷键:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

编译运行这个软件,结果我们发现没有这个菜单,我们要设置一下对话框的属性,选择这个菜单:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


3.2 Dialog消息处理函数中的返回值

在这里插入图片描述

就是说你在编程的时候,已经对这个消息处理好了,不需要系统在管了,你就return TRUE;
缺省的时候有很多不需要你去处理的消息,就可以return FALSE,让系统去处理。

在这里插入图片描述
返回TRUE就是说我都做好了,系统你就别再管了,哪个按钮处理好了,你就在那个按钮的地方返回TRUE;
凡是你处理过的你就return TRUE;
返回FALSE,就是说编程的人没有对这个按钮做任何的处理,让系统代劳,执行对话框默认的消息处理函数。

在这里插入图片描述

凡是你处理过的地方就返回TRUE,没有处理过的缺省都要返回FALSE。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

如果你进行除零操作,软件就会崩溃:
在这里插入图片描述

所以我们要对输入的数据进行校验:
在这里插入图片描述

在这里插入图片描述


3.3 让四则运算支持double类型

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

C语言有将整数转换为字符串的函数itoa,但是它没有将浮点数转换为字符串的函数,那怎么办呢?
C语言是用什么函数来将各种类型的数字转为字符串的呢?


3.4 格式化函数

在这里插入图片描述

printf函数的打印目标是控制台,sprintf函数的打印目标是字符串(缓冲区),也就是说给printf加上s的话,参数里就必须再加一个字符串的地址(缓冲区),打印的目标要送到这个缓冲区里面:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


4、MFC02-2:Windows消息处理机制

在这里插入图片描述


4.1 windows消息列表

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


4.2 测试其他的消息种类

插入一个对话框,测试一下对话框里都有哪些消息可用:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们如何把鼠标左键按下的位置显示在对话框窗口上呢?
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

一般在case区域你要定义变量,编译是会不通过的,所以要给这个case区域加个大括号,在这个区域内使用变量x和y,这是C语言case语句的语法要求。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

将变量的类型改为short就可以显示负数了:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


5、MFC02-3:Windows类型介绍

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

not captured,就是当鼠标移出了对话框窗口客户区之外的时候,标题栏上的文字就不响应了(不响应鼠标移动消息了),离开窗口的话光标就无效了,光标在窗口内的话就响应鼠标移动消息,也就是说包含光标的窗口才能收到这个消息;
the window that has captured the mouse,也就是把这个光标绑到这个窗口,离开窗口之后光标仍然有效。

在这里插入图片描述

继续研究wParam参数,如何把按下左键拖动也能显示出来呢?
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

按下左键拖动的同时按下ctrl键:
在这里插入图片描述


5.1 windows的变量类型

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


解析DECLARE_HANDLE宏

在这里插入图片描述

我们可以看到,任何一个句柄都是一个结构体指针变量,指向一个不让你知道的内存空间,比方说一个窗口的句柄,它可能指向的内存空间里实际上有20多个变量,包括窗口的位置,窗口的类型等等,可能有很多种数据,但是它不让你知道,这是一种故意不让你看到真实内容的结构体,隐藏了它实际的内容。

类似于我们在C语言当中学的FILE*指针类型:
在这里插入图片描述

每一种句柄都是被隐藏了实际内容的一种指针变量,都是4个字节。

在这里插入图片描述


5.2 简介MFC开发

今后我们在做MFC开发的时候,这个InitInstance虚函数可以当做是WinMain函数,真正的WinMain函数被它隐藏起来了。
在这里插入图片描述

上图这是向导给我们生成的MFC程序,我们把InitInstance函数中自动生成的其余代码如上图所示删除掉,这里主要是要弹出一个对话框。

在这里插入图片描述

该函数在弹出对话框的时候呢,它是把这个对话框的ID放到一个对话框类里:
在这里插入图片描述

只要定义了这个类的对象,你在需要弹出对话框的时候,因为这个对象包含了这个对话框的ID,系统就会帮你加载这个对话框,定义了这个对话框对象之后,可以调用它所继承的基类的成员函数,就可以弹出对话框、设置背景颜色、关闭对话框:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


用MFC开发四则运算计算器程序

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

拖拽复制这些控件:

在这里插入图片描述

在这里插入图片描述

在左侧标尺那里你可以打一条线,让这一行上的控件对齐。

在这里插入图片描述

双击一个按钮就会自动生成这个按钮对应的操作函数:
在这里插入图片描述

这种函数叫做消息映射函数。

在这里插入图片描述

BEGIN_MESSAGE_MAP宏做了一个消息映射,把一个ID关联到一个成员函数上,而且是点击动作的消息类型。

在这里插入图片描述

在这里插入图片描述

我们可以从上面看到,它最终还是把命令消息关联到该命令对应的函数。

在这里插入图片描述

这种消息映射机制,我们就叫做:一消息、一ID、一函数;
有的时候也有不带ID的,即一消息、一函数。

我们这里为了简单演示,先只做int类型的计算。

在这里插入图片描述

在这里插入图片描述

我们看到CDialog类是由CWnd类派生下来的,所有的窗口、控件都属于CWnd类。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们可以看到,在API中有4个参数,它必须要带入句柄,而我们这里写的程序为什么只有3个参数,不用带入句柄呢?
在这里插入图片描述

下断点,按F11进去看一下:
在这里插入图片描述

我们可以看到它仍然要调用系统API并带入句柄,只不过这个句柄m_hWnd已经缺省被保存在成员变量里了,也就是CWnd类的核心成员变量只有一个,就是这个句柄HWND:
在这里插入图片描述

既然你的对话框已经弹出来了,它把句柄已经给你保存好了,所以你每次都不用写句柄了,这个句柄都被窗口类CWnd管理好了,你就不用管句柄了,每次调用函数的时候省去了句柄的带入。

在这里插入图片描述

在这里插入图片描述

这些C++成员函数经常给有些参数常用的缺省值,从代码编写上就省了很多参数的填写。


6、MFC03-1:Win32程序界面权限登录开发

在这里插入图片描述


6.1 实现对话框启动后居中显示

在这里插入图片描述

为了实现让对话框启动后居中显示,还要获取你显示器的高度和宽度(屏幕分辨率):
在这里插入图片描述

在这里插入图片描述

梳理一下代码,将IDOK分支里的计算封装成函数OnCalc:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们可以看到全屏和屏幕的高度有所不同,全屏的不包含系统任务栏,用全屏的尺寸(以桌面客户区为主)更美观。

在这里插入图片描述

半个屏幕宽度减去半个窗口宽度,就得出了实现窗口居中所需要的x坐标;
半个屏幕高度减去半个窗口高度,就得出了实现窗口居中所需要的y坐标。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这个移动的过程中,如果这个窗口是已经显示出来了,移动的时候你最好加TRUE,让它自动刷新就可以了,边移动的时候要刷一刷窗口的显示,让移动窗口显得更平滑自然;
而我们这里是在OnInitDialog函数中,窗口还没显示出来之前,那你就不用重新刷新了,因为本来就没显示,这里该参数用FALSE就可以。


6.2 实现登录界面

添加一个对话框资源:
在这里插入图片描述

给按钮居中排列:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

现在我们这个程序是Win32的,并不是MFC,我们先来用Win32实现;
登录有自己登录的处理函数,主窗口有主窗口的处理函数,Win32的开发模式就是每一个窗口你都要给它一个消息处理函数:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

当你点击登录界面的退出按钮时,就不进入主界面,当你点击登录按钮时,主界面就出来了,但是此时我们没有对用户名、密码进行判断。


7、MFC03-2:MFC架构与原理分析1

7.1 完成登录对话框居中显示,用户名密码匹配

在这里插入图片描述

接着,我们要根据输入的用户名和密码是否正确来决定是否要执行EndDialog:
在这里插入图片描述

在这里插入图片描述


7.2 编辑控件的属性

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

如果要加一个记事本功能的编辑框的话,记得把自动横向滚动(Auto HScroll)和自动纵向滚动(Auto VScroll)改为FALSE,Multiline和Want Return改为True,以及很重要的Vertical Scroll改为True:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们添加一个只读属性的编辑框,用来显示用户名、密码输入错误的提示:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

上图的SetDlgItemText函数的位置放在OnInitDialog函数的前面试试。

在这里插入图片描述


7.3 测试各种windows消息

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们发现在处理WM_SETCURSOR消息的时候,可以把只读编辑框里面的提示信息显示出来。

在这里插入图片描述

它的意思是说,我的鼠标在什么控件上移动,我就有不同的提示;这个消息叫鼠标光标切换消息。

在这里插入图片描述

我们测试一下该消息的lParam的高2字节是否是控件的ID:
在这里插入图片描述

测试效果不明显,我们再试试wParam是否包含控件的句柄:
在这里插入图片描述

在这里插入图片描述

这个测试效果明显,鼠标在不同的控件中移动的时候,wParam中就包含了对应控件的句柄。

7.4 用MFC开发计算器

能不能把已经以前工程中编辑好的对话框加入到新的工程里面去呢?
也就是说,把前面用Win32写的计算器对话框里面的控件拖到新工程中的对话框上呢?

新建一个MFC工程,把该MFC工程自动生成的对话框上面的控件全部删除,点击文件、打开、文件:
在这里插入图片描述

在这里插入图片描述

打开前面已经写好的Win32工程中的.rc文件:
在这里插入图片描述

在这里插入图片描述

ctrl+a全选,ctrl+c复制,再打开mfc工程中的对话框,粘贴上去:
在这里插入图片描述

把粘贴不过来的图片先删除掉。

在这里插入图片描述

我们对上图OnOK函数那行设置一个断点,看看它做了什么:
在这里插入图片描述

在这里插入图片描述

如果你想要点击计算后不关闭对话框,把该行注释掉或者直接删除就可以了,这个时候你回车的话就不会关闭对话框了。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

上图这是一个公用头文件区。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


8、MFC03-3:MFC架构与原理分析2

在这里插入图片描述

我们可以看到MFC框架中是有WinMain函数的,只不过给你隐藏起来了。

在这里插入图片描述

在这里插入图片描述

InitInstance是个虚函数,回调到你这个用户代码这里:
在这里插入图片描述

你可以就把自己程序当中的InitInstance当做主函数,当做WinMain函数,不用去关心真正的WinMain函数在干什么,你只要知道程序启动的时候就在这里进行调用就可以了;
可以把向导自动生成的无用的代码都删除掉:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

从上面两张图片可以看到生成的这两个类所包含的成员函数。

在这里插入图片描述

一个对话框类会跟一个对话框的模板资源,形成关联。

在这里插入图片描述

对话框类在构造函数的时候,把一个对话框模板送到它的基类,它的基类在DoModal的时候就拿这个模板去加载对话框;
另外它还加载了一个图标IDR_MAINFRAME,把这个图标在设置到对话框上面去:
在这里插入图片描述

在这里插入图片描述

如果你把这两行SetIcon函数都注释掉,就不显示左上角这个好看的图标了:
在这里插入图片描述

在这里插入图片描述

这是一个没什么用的函数,微软一直没有把它删除掉。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

把计算器程序中的输入编辑框改为右对齐,输出结果的编辑框改为只读:
在这里插入图片描述


8.1 把原始的Win32程序改造为MFC程序

先建立一个普通的Win32项目:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

此时,我们在对话框上放上计算加法的几个控件,并双击这个对话框确定按钮,发现没有自动生成响应函数,而是弹出一个MFC添加类向导对话框:
在这里插入图片描述

就是说,你光用一个基类CDialogEx去定义一个对象,它是无法处理你这里面的消息的,也就是说你无法在这个基类中去编写代码,你必须把这个基类再派生一次,也就是我们经常在开发MFC的时候看到的两个类,一个是App类,一个是Dialog类:
在这里插入图片描述

你把这个对话框,根据这个IDD_MAIN_DLG,做一个CDialogEx的派生类CMainDlg,这样的话就生成了App类与Dialog类联用的这种标准的MFC程序。
这样我们就把MFC的开发原理就搞清楚了。
这个派生类是用来做消息映射,一个消息对一个成员函数,你不可能把这个消息映射做到基类里面去,因为基类是在系统里,你不可能跑到系统里去编写代码,你肯定要自己做个派生类,在自己的类里去编写代码。

在这里插入图片描述

也可以直接右击对话框窗体,点击添加类,你双击一个按钮也会走到这一步:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们双击对话框上的任何一个按钮,就可以建立一个关联的成员函数,这里我们生成一个这个按钮的回调函数:
在这里插入图片描述

要记得在MainDlg.h开头添加resource.h头文件才能识别IDD_MAIN_DLG:
在这里插入图片描述

我们在OnBnClickedOk函数中下个断点,编译运行程序,现在肯定能弹出我们这个自定义的对话框:
在这里插入图片描述

但是点击确定按钮后,这个对话框直接关闭了,而没有进入到我们的消息映射函数,这是为什么呢?
在这里插入图片描述

原因就是说我们这里用的是基类构建的这个对话框类的对象(如上图所示),它有消息的话都会送到基类去,所以这里要改为派生类来建立对象:
在这里插入图片描述

在这个派生类创建对象的时候不需要传参了,因为在这个派生类构造函数中它会自动往基类中传参的,它会自动往基类送IDD_MAIN_DLG,所以你这里就不用传参了:
在这里插入图片描述

这次再点击确定按钮,就映射到派生类的这个消息处理函数里来了。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

那么,那些LBUTTONDOWN、SETFOCUS这些消息都是怎么创建的呢?
在这里插入图片描述

在MFC中把这个窗口的所有消息都给你列了出来:
在这里插入图片描述

在这里插入图片描述

你都可以通过类向导去截获这些消息来利用这些消息,处理这些消息。

消息映射函数有两种,一种就是直接由消息映射成函数,另一种消息还要配合ID生成函数,就是一消息加一个ID生成一个函数:
在这里插入图片描述

因为有很多个按钮,它必须配合ID才能确定你点击的是哪个按钮,而LBUTTONDOWN就不存在这个问题,我只要点击一下就行了,我不可能有5个光标点击,我只有一个光标点击,所以是一消息生成一个函数。

在这里插入图片描述

在这里插入图片描述

有很多地方都可以打开这个类向导:
在这里插入图片描述

再给MouseMove消息做一个响应函数:
在这里插入图片描述

在这里插入图片描述

我们MFC把一个拆分好的坐标放在一个MFC对象CPoint里面:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

该消息处理函数的第一个参数nFlags其实就是Win32程序中回调函数中的wParam参数:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

留个作业,用MFC实现前面用Win32做的登录对话框那个程序,也就是再添加一个对话框作为登录对话框:
在这里插入图片描述

9、MFC04-1:用MFC应用程序向导建立MFC工程

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

上图左下角的带小箭头的东西叫做全局对象的声明,或者是成员变量的声明,双击带箭头的小图标,你会发现在App的.h头文件有这个全局变量的声明,定义只能有一份(在App的.cpp文件中),但是这种声明可以有多份,这只是一个引导性的语句,它不是一个真正的变量,让看不见这个变量的地方可以使用这个变量;
有了这个全局变量的声明,使用起来更加方便,不然的话你只能在这个全局变量定义所在的.cpp文件中使用,你出了这个文件那你如何能看到这个全局变量呢,那主要就是通过全局变量的声明:
在这里插入图片描述

当然你也可以把这个全局变量的声明放在stdafx.h这个公有区的头文件中:
在这里插入图片描述

凡是你在这个头文件放上的东西几乎在所有的.cpp中可见,因为这是一个公有的头文件。

在这里插入图片描述

编译报错,是因为该公有头文件对CCalcApp这个类不可见,所以加上该类的头文件Calc.h:
在这里插入图片描述

我们说过我们可以把InitInstance当做WinMain主函数来使用:
在这里插入图片描述

把向导给我们自动生成的InitInstance函数多余的代码删除掉。

在这里插入图片描述

我们发现,为什么在消息映射表里没有这个OnInitDialog呢?
原因就是我们有3个缺省的系统消息,被系统接管了的消息:
WM_INITDIALOG、WM_COMMAND下的IDOK和IDCANCEL消息。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们把这3个消息叫做系统接管了的对话框消息。
我们看到在消息列表里没有这3个消息,但是它们为什么能够被调用呢?
在这里插入图片描述

在这里插入图片描述

我们点击IDOK按钮断下再看看:
在这里插入图片描述

它也是一个COMMAND消息。
这是3个被系统缺省管理的消息,实际上在我们这里的MFC开发有两种回调函数,一种是虚函数回调,一种是消息映射回调。

OK按钮、CANCEL取消按钮以及初始化InitDialog,这三个是缺省的系统消息,它们被系统缺省管理了的系统消息;
并且在基类中把这3个函数做成虚函数回调。

在这里插入图片描述

比如上图的InitInstance、OnInitDialog,以及OK、Cancel被做成了虚函数回调;
我们去基类当中看看这这几个函数:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们可以看到在基类中系统有这几个函数的缺省处理,系统接管了之后就会缺省执行关闭:
在这里插入图片描述

所以,我们只要一建立了这个工程,你一点击这个取消按钮或者是OK按钮,它们就会自动关闭,这就是因为系统有对这两个按钮的缺省处理。

在这里插入图片描述

我们现在知道,CCalcDlg这是一个用来接收关于IDD_CALC_DIALOG对话框消息的派生类,有了这个类,用这个类的对象来DoModal你才能够接收到这个对话框内的所有事件返回的消息;
你点击、鼠标移动,那些消息都会反馈到这个类CCalcDlg里面来接收消息。

我们前面做过的添加以前工程中做好的对话框资源,是通过文件、打开、文件的方式把.rc资源文件打开,再把对话框上的控件全选复制到新工程资源上的,我们直接拖动.rc文件中的资源,是拖不进现有工程资源中的:
在这里插入图片描述

为了把以前工程中做好的对话框资源、位图资源添加进我们新的工程中,我们用另外一种方法添加:
在这里插入图片描述

把.rc资源文件添加进来后,我们现有工程中就有两个资源文件了:
在这里插入图片描述

在这里插入图片描述

现在就可以把以前工程TestDlg.rc中的IDD_MAIN_DLG对话框资源拖进我们当前Calc.rc资源中来了,以及IDB_TEST位图资源、IDR_MENU1资源都可以拖过来了:
在这里插入图片描述

拖完了之后就可以把刚才添加进来的以前的资源文件删掉了,不保存别人的东西:
在这里插入图片描述

在这里插入图片描述

什么东西都被拖进来了,这样的方式就使得我们开发对话框就容易了。

我们把以前向导给我们生成的对话框模板IDD_CALC_DIALOG给删掉,把向基类CDialogEx传参的对话框模板的ID修改为我们保留的IDD_MAIN_DLG:
在这里插入图片描述

在这里插入图片描述

这个时候编译运行,就会把这个对话框弹出来了。

在这里插入图片描述

如果你把这个CCalcDlg::OnCancel()这个函数整个给删掉和留在这里,效果是一样的,你把这个函数删掉,系统会自动接管这个由基类接管的函数;你不删掉摆在这里,在该函数中再去调用基类的这个OnCancel函数,效果是一样的;
如果你在这里去掉CDialog::OnCancel()这一句话,这个对话框就关闭不掉了。

我们给这个对话框把菜单加进来:
在这里插入图片描述

然后把这个对话框属性里的Menu这一项改为该Menu的ID,主界面就有菜单可用了:
在这里插入图片描述

我们之前的Win32程序,不但有主对话框,而且还有登录对话框,我们看见别人有什么,我们就想拿别人的东西过来,把前面的Win32程序中的登录对话框添加进来:
在这里插入图片描述

在这里插入图片描述

看见什么就往我们工程里面拖,这样的话你在做这个项目的时候就快了;
然后再把这个TestDlg.rc按Del键给不保存删掉,不要破坏掉别人的资源。

在这里插入图片描述

我们现在有两个对话框可以使用了,但是我们却只有一个主对话框的类CCalcDlg可以用:
在这里插入图片描述

我们可以看到,在启动程序这里只有一个主对话框在DoModal,却没有第二个对话框在DoModal,那么我们这个登录对话框怎么能够出现,怎么能够影响到主对话框的弹出呢?
在这里插入图片描述

这里要给这个登录对话框添加一个关联类:
在这里插入图片描述

在这里插入图片描述


9.1 简介MFC六大关键技术

App这个类就是MFC的启动技术:
在这里插入图片描述

启动技术就是说它没有WinMain函数给你,它把WinMain函数接管到系统内部,在运行的过程中通过一个InitInstance这个虚函数回调回来,这个函数就可以认为是MFC的第一大关键技术,它是启动管理技术。

第二大关键技术,就是我们天天能看到的消息映射机制,一个是DECLARE_MESSAGE_MAP,一个是BEGIN_MESSAGE_MAP。
在这里插入图片描述

在这里插入图片描述

第三大关键技术,以后我们会介绍,就是刚才的对话框类这里看到的动态创建技术,即DECLARE_DYNAMIC:
在这里插入图片描述

现在我们怎么把这个新生成的类弹出来呢?
我们让Login对话框先弹出,主对话框后弹出:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

由于登录对话框上的用户名编辑框我们设置的属性为只支持大写字母,所以需要把大写字母转换为小写字母再进行比较。

在这里插入图片描述

在这里插入图片描述

这里的函数只对英文字母有影响,对于符号、数字以及中文字符都没有影响。

在这里插入图片描述

关于这个CString类的成员函数,大家都应该测试一下,这里还有很多拆解函数都非常好用,都非常有用:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

另外还有一种不区分大小写的比较函数:
在这里插入图片描述

Compare函数我们一般不用,用CString重载过的==或!=运算符更方便。

在这里插入图片描述

在这里插入图片描述


9.2 UNICODE编码简介

在这里插入图片描述

注意一下,上图MultiByteToWideChar后面的中文解释说反了,WideCharToMultiByte后面的也说反了。

在这里插入图片描述

各国语言的编码都各自独立,就是统一编码Unicode。

10、MFC04-2:UNICODE编码的应用

我们建立一个简单的Win32的空项目,来测试一下UNICODE编码的使用。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们现在来测试一下Unicode文字和非Unicode文件它们的区别是什么?
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

上面的两张图中的const wchar_t [4]和const wchar_t [3],这个4和3应该是字符个数,也就是多了一个看不见的结尾0字符。

在这里插入图片描述


10.1 TCHAR

当你把工程切换为多字符集的时候,你就得把每个字符串前面大写的L给去掉,很麻烦:
在这里插入图片描述

在这里插入图片描述

而当你又切换成Unicode环境的时候,你又得把每个文字前面带上大写字母L,非常麻烦。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

目前项目的字符集是Unicode字符集:
在这里插入图片描述

在这里插入图片描述

L是将字符串常量转换为Unicode,那么有没有一种东西能够让常量自动转化的?
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

为了让代码具有可移植性,在Unicode和非Unicode都能使用的话,每个字符串前面都要添加_T或者_Text,
在这里插入图片描述

MFC的函数也是这样的,字符串参数类型也是TCHAR*的类型,它就具有了可移植性,两种字符集平台都可以使用:
在这里插入图片描述

在这里插入图片描述


10.2 Unicode与多字符集字符串互相转换

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


10.3 MultiByteToWideChar函数

在这里插入图片描述

在这里插入图片描述

上图这个-1代表,这个字符串一直跑到结尾就可以了,你不用指定字节数了,有多少转换多少,有多长转换多长。

在这里插入图片描述

我们有更简单的用一个参数就能搞定转换的函数,下节课会讲。


11、MFC04-3:Unicode与多字符集字符串互相转换

在这里插入图片描述

在这里插入图片描述

注意一下,上图MultiByteToWideChar后面的中文解释说反了,WideCharToMultiByte后面的也说反了。


11.1 WideCharToMultiByte函数

在这里插入图片描述

在这里插入图片描述

这种转换都是很笨的转换,还有更加简单的Unicode转换的方法:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们不熟悉W2A的参数,可以先F12看看声明:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这几种函数平时用的比较少。

在这里插入图片描述

我们平时用的比较多的是_bstr_t类,这个类也是开发COM常用的;这个类里面没有什么函数,它里面也是模仿CString来做的,只不过它里面能够放两种指针,一条是宽的,一条是窄的,因此你既可以把p1带进来,也可以把s1带进来,它可以接纳两种字符串:
在这里插入图片描述

_bstr_t类在这里编译链接会报链接错误,因为我们现在这个项目是一个Win32项目,_bstr_t类适合MFC工程,在大型的MFC工程中用这个类就比较方便;
最好不在Win32下使用,因为它要链接一些MFC的库,这个类宽的我也能接受,窄的我也能接受:
在这里插入图片描述
在这里插入图片描述

CString里面用的是TCHAR,CString它自身是有自适应的,它只能在某个字符集环境下单一的接受,_bstr_t就比较牛了,两种字符集它都能接受,它里面放点Unicode,还能再放点非Unicode,最后能统一生成一种char*格式:
在这里插入图片描述

Unicode和非Unicode合并在一起,然后会生成一个非Unicode的指针:
在这里插入图片描述

我们通过内存窗口可以观察到,这是一个窄类型的字符串,我还可以再从这个_bstr_t类的对象中取宽字符,这个类是两种字符串都能放进去,两种字符串都能取出来:
在这里插入图片描述

这个都是堆区上的东西,它里面存放了两个堆,即存放了Unicode的堆区,也存放了非Unicode的堆区,你既可以把Unicode的东西存进去,也可以把非Unicode的东西放进去;
所以_bstr_t是我们在开发中最喜欢使用的字符串转换方式,两种字符集的转换它都能做得到。


11.2 在窗口上实时显示当前时间

在这里插入图片描述

在这里插入图片描述

这个类最核心的算法是,把你的年月日时分秒合并成一个64位的变量 __time64_t 存进去。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们来把当前时间实时的显示在登录对话框的只读的提示框里:
在这里插入图片描述

我们从上图左下角可以看到,这个登录对话框类里还没有OnInitDialog初始化消息,我们可以直接用类向导:
在这里插入图片描述

在这里插入图片描述

我们发现在消息里并没有找到WM_INITDIALOG消息,这个本来是一个消息的,只不过被系统接管过去了,变成了虚函数让我们再回调回来。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

如果你想让这个时间不停的更新的话,就要用定时器消息。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

它是在调用窗口类CWND的SetTimer函数时,指定了每间隔多少时间来产生这个WM_TIMER消息,这个消息将定时的发生。

在这里插入图片描述

在这里插入图片描述

这个函数就是设置、安装定时器,你没安装的话它就不刷新;该函数的第一个参数就是定时器的编号,比如说1号定时器的时间间隔是1秒,2号定时器的时间间隔是500毫秒,而这些不同定时器的编号会在OnTimer的第一个参数nIDEvent中进行分类识别(你可以用switch给它们区分开进行处理)。

在这里插入图片描述

这个消息就会每隔1秒钟来一次。


11.3 封装一个自己的CRect类

在这里插入图片描述

新建一个MFC项目来模拟封装Win32下的RECT结构体为一个类:
在这里插入图片描述

在这里插入图片描述

我们要实现的功能是,只要你的鼠标一靠近这个同意按钮,这个同意按钮就会跑,不让你点击它。

在这里插入图片描述

在这里插入图片描述

这个WM_SETCURSOR是鼠标在各个控件之间切换的时候,产生的一个消息;也就是鼠标光标在各个控件上面移动的时候产生的。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

ID为0是对话框窗口,ID为2是取消按钮的ID,即IDCancel。

在这里插入图片描述

这个同意按钮的ID就是1000,我们可以从Resource.h中看到这个按钮的ID就是1000:
在这里插入图片描述

这就证明了只要你鼠标一移到哪个按钮(控件)上,它就有这个WM_SETCURSOR消息的发生。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

刚要点同意按钮,结果就变成移动这个对话框了,默认的MoveWindow(&rect)只指this->MoveWindow(&rect),这个this指针就是指我们这个对话框,我们现在是this移动,也就是对话框移动了;
当进来的窗口句柄的ID是IDC_TEST的时候,我们就知道了pWnd就是这个同意按钮的句柄(指针):
在这里插入图片描述

在这里插入图片描述

我们再次测试发现按钮跑到右下方来了,跑的太远了,我们不是说让它跑到右边一格么,这是怎么回事呢?


11.4 屏幕坐标系和窗口坐标系

我们把整个屏幕截图放到画图程序里进行分析:

在这里插入图片描述

我们分析可以得知,上图这个244、212其实是以屏幕左上角为坐标系原点0,0来说的,如下图画笔坐在位置即为整个屏幕的244,212坐标点:
在这里插入图片描述

这个同意按钮以屏幕坐标系来看是在这个244,212这个坐标点上的,我们本来是想要把它移动到这个坐标点右边这个按钮宽度的位置,为什么它却移动到右下方来了呢?
因为MoveWindow在移动窗口的时候,它是以TestRect对话框左上角为坐标系原点0,0,移动到332,237位置的话就跑到这个对话框窗口的右下方了,我们在画图程序中把这个截图往左上角移动,让TestRect对话框左上角作为原点,即如下图所示:
在这里插入图片描述

从上图左下角的坐标334,301可以看到,这个同意按钮移动的位置就是以TestRect对话框窗口左上角为坐标系原点的。
这就是由于坐标系不同导致的问题:
在这里插入图片描述

对于GetWindowRect来说,是以屏幕左上角为原点的坐标系。

在这里插入图片描述

对于MoveWindow来说对于子窗口(子CWnd对象),x和y是相对于父窗口工作区的左上角作为坐标系原点来说的。

在这里插入图片描述

所以这里需要有一个坐标的切换,把传进去的屏幕坐标系的rect(该参数为输入输出型参数)变为以这个this指向的对话框客户区为新原点的坐标系。

在这里插入图片描述

这就获取了一个屏幕的坐标系,转换为客户区坐标系:
在这里插入图片描述

在画图中可以看到,以这个对话框左上角为坐标系原点,这个同意按钮的左上角坐标就是12,12位置,说明确实将屏幕坐标系转换为了窗口客户区坐标系了:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这时候再去移动就是以客户区为单位的了,就是让你永远都点不到同意按钮。

我们修改下程序,再取得一个rect,如果按钮跑到客户区界线了就让它恢复回来:
在这里插入图片描述

在这里插入图片描述

小窗口(同意按钮)的右边已经跑到大窗口(客户区)的外面了,就让它回到左边去。

如果我们采用MFC的CRect代替Rect的话,这个CRect的到处是什么呢,我们来看一看:
在这里插入图片描述

好处就是不用取地址了,直接带入这个CRect对象就可以了,这是因为CRect类里面有类型转换成员函数:
在这里插入图片描述

就是说一个对象所属的类有运算符重载了之后,你即可以把它当作对象,又可以把它当作对象的地址,等价于你取了对象的地址,对象自己会把自己的地址取出来,不用你再去写取地址符号了。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

还能进行压缩,向中间压缩,向四周扩张:
在这里插入图片描述

还能够平移:
在这里插入图片描述

在这里插入图片描述


12、MFC05-1:MFC的六大关键技术

在这里插入图片描述


12.1 MFC的六大关键技术都有哪些

  • MFC程序的初始化过程
  • 运行时类型识别(RTTI)
  • 动态创建
  • 永久保存
  • 消息映射
  • 消息传递

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


12.2 浅谈消息映射机制

MFC把每个对话框对应到一个类:
在这里插入图片描述
在这里插入图片描述

每个对话框都有一个对应的类,一个对话框资源配一个类,这样的话每一个对话框的消息会在对应的类中去接收,这样程序架构的组成就比较干净利落一些。

在这里插入图片描述

我们只要把类向导打开,在上面双击一个消息,就产生一个消息映射函数;
这就是消息映射机制对于我们程序开发带来的好处。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


12.3 再谈屏幕坐标系和窗口坐标系

在这里插入图片描述

它参考的坐标是以屏幕的左上角为原点。

在这里插入图片描述

在这里插入图片描述
这就是窗口客户区坐标系,GetClientRect每次取出来的左上角都是0,0,它主要能取出来右下角的坐标560,350,我们按照这样一个矩形区域画一个椭圆。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

13、MFC05-2:MFC基础变量类型

13.1 模仿开发CPoint类

鼠标右击类视图添加一个类:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这些基础类型在外面MFC工程下是必然会存在的,我们在MFC下有一个公用的头文件叫stdafx.h,这个头文件就把所有的公用的头文件都包含起来了。
在这里插入图片描述

其中我们最主要的头文件就是afxwin.h,凡是afx开头的都是MFC头文件,在Win32开发下的时候是没有任何一个头文件是afx开头的。
在这里插入图片描述
凡是windows.h有的东西,afxwin.h这个头文件里面都有。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们再做一个Offset成员函数:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

测试这个自定义的类:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


13.2 封装CRect类

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

我们给这个自定义的类创建一个类型转换函数,将自己的对象变换成其他的类型:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们可以参考微软的CRect的TopLeft函数,看看微软是怎么做的:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

因为这里的TopLeft返回的是一个引用,所以如果我们把返回的pt.x和pt.y修改后,请问rect会不会发生改变呀?
如果是引用的话,它等价于是一个指针变量,指向到这个rect上面,所以rect会发生改变,如下图所示:
在这里插入图片描述

这就是加引用和不加引用的区别。

在这里插入图片描述

我们还看到pt和地址和rect的地址一样,而pb的地址其实刚好指向了rect的腰,是rect的下8个字节,指向的是rect的右下角的right和bottom这两个成员变量。

如果我们去掉引用的话,这个时候的改变就不会影响到rect本身:
在这里插入图片描述

可以看到pt和pb这两个CPoint的地址,和rect的地址没有重叠。

如果我们的CRectLx类里面没有定义只读的TopLeft成员函数的话,如下这种只读的对象参数是无法调用普通成员函数的:
在这里插入图片描述

只读对象只能调用只读成员函数。

在这里插入图片描述

在这里插入图片描述

如果你把Width成员函数的只读性给去掉的话,它怀疑你会改变rect里面的成员变量;
只读成员函数是可以给只读对象使用的。

在这里插入图片描述

你定义一个临时对象并对这个临时对象进行修改这是可以的;
但是this指向的对象内部自己的数据是不可以修改的:

在这里插入图片描述

在这里插入图片描述

这就是上图这两个只读成员函数产生的原因,就是因为它要返回一个只读性质的CPoint,返回的值也不能修改这里面的数据;
不但它不能调用普通的成员函数,连它的返回值都不能修改。

在这里插入图片描述

用一个普通的CPoint来接收只读对象调用的只读函数(这个函数返回只读的引用类型的CPoint)是可以的;

你把pt改为引用的话就报错了,它不让你去挂只读的对象:
在这里插入图片描述

如果你要挂必须用只读的去挂:
在这里插入图片描述

只读函数将用于只读对象。


14、MFC05-3:简单MFC程序设计

14.1 继续模拟CRect类的常用成员函数

函数CenterPoint的实现

在这里插入图片描述
接着我们来看只读函数CenterPoint的实现,凡是这种只读函数都是给那种只读对象来使用的,不然的话只读对象就怕你在不加const的函数里面修改只读对象的成员变量,凡是const函数是禁止修改它的每一个成员变量的。

在这里插入图片描述

在这里插入图片描述


函数IsRectEmpty和IsRectNull的实现

在这里插入图片描述

接着我们来看看IsRectEmpty和IsRectNull这两个函数是什么意思,有什么区别?
在这里插入图片描述

我们来创建一个没有面积的CRect对象(因为这个矩形的宽度为0):
在这里插入图片描述

在这里插入图片描述

从上图可以知道,Empty只是面积为空,那么Null的话就不止面积为空了:
在这里插入图片描述

比方说我们创建一个左上角坐标(0,0)和右下角坐标(0,20)的矩形:
在这里插入图片描述

我们编译运行发现还是Empty,少一个0这个IsRectNull都不成立。

在这里插入图片描述

在这里插入图片描述

果然4个0的话IsRectNull就成立了。

在这里插入图片描述

在这里插入图片描述


函数InflateRect和函数DeflateRect的实现

在这里插入图片描述

Inflate的意思是说向四周膨胀。
在这里插入图片描述

deflate就是向中间压缩。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

对于两个矩形是否相等的判断,我们可以用一个memcmp函数去进行判断,把整个矩形区域的4个变量全部比较一遍,也就是把内存空间比较一遍。

在这里插入图片描述


演示一下NormalizeRect函数的功能

我们先创建一个有问题的矩形(左边大于右边,上边大于下边):
在这里插入图片描述

我们可以看到它的宽度是-28,高度是-58,执行了NormalizeRect之后呢,把左上角和右下角的坐标值对调了一下:
在这里插入图片描述

把有问题的矩形变成正常化了;
你自己实现的时候完全可以用一个if语句判断一下,如果left大于right的话给它俩对调,如果top大于bottom的话给它俩对调。

对于MFC类库中的每一个函数,你都要非常清晰的知道这个函数的用法。


14.2 继续完善计算器程序

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

你用TCHAR的话,字符串常量也要用可移植性的才行:
在这里插入图片描述

p[0]的赋值不好,也应该用_T去“修饰”字符:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

原因就是因为atof这个函数是一个窄函数,由于当前平台是Unicode字符集,所以要调用宽函数:
在这里插入图片描述

在这里插入图片描述

所以在遇到C语言函数的时候一定要看看微软有没有对这些窄字节函数做出宽字节版本的函数。

在这里插入图片描述

推荐使用_tstof或_ttof这种自适应的、可移植的函数。

在这里插入图片描述

在这里插入图片描述

当我们按F11跟进去的时候,它必然会有一个operator类型转换函数:
在这里插入图片描述

当你去用到它的指针的时候,编译器就会自动返回PCXSTR类型的指针,能够把字符串的地址取出来。

在这里插入图片描述

编译的错误就是因为必须在双引号前面加上_T才可以。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

凡是遇到上面这种错误的时候,我们最好点重试按钮,我们虽然出错了,但是我们也有机会演示当弹出这个对话框,我们应该做什么,之后你再去看调用栈,找到自己写的函数。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

大家见到这种弹出的框,你要会用重试按钮,点中止的话就错了,你要会善于利用这个弹出的错误,一定要点它的重试按钮,点了重试按钮之后,再要去看调用栈,你会找到发生错误的那一行,可以快速定位你的错误。

  • 6
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 武夷山是福建省的八大名山之一,位于福建省南部的宁德市和福州市的交界处,是中国著名的佛教圣地之一。武夷山以山水秀丽、景色秀美而闻名于世,更是中国最古老的佛教圣地之一。武夷山高达2087米,有着几千年的历史,曾是一处象征着中国传统文化的古代名山。武夷山拥有丰富的植物和动物资源,有森林、湿地、草原、河流和湖泊等多样的景观。武夷山上的景点众多,有古刹、瀑布、石窟等,是游客们野趣探索的绝佳去处。武夷山的风景迷人,历史悠久,是中国的瑰宝之地,也是中国佛教文化的发祥地。 ### 回答2: 武夷山位于中国福建省南部,被誉为华东的一颗明珠。武夷山是一座古老而神奇的山脉,有着深厚的文化底蕴和自然景观的独特魅力。 武夷山是中国五岳中的南岳,也是中国主峰最少的一座山脉,海拔最高峰是大龟山,达到2158米。这里山峰陡峭,溪流纵横,森林茂密,是一个充满迷人风光和壮丽景色的地方。武夷山地势复杂,山上的云雾缭绕,给人一种恍若仙境的感觉。 除了自然景观,武夷山还拥有丰富的人文历史。自古以来,武夷山就是武夷文化的发源地,这里是古代文人雅士庐山致仕之地,也是道教名山丹霞洞的发源地。在武夷山,可以看到许多古老的建筑、古刹和石刻,这些都是中国古代文化的珍贵遗产。 武夷山有着丰富的生物资源,这里是中国东南地区最大的森林保护区之一。武夷山是茶树的发源地之一,这里产出的武夷岩茶闻名于世。此外,武夷山还是中国最重要的林木和草药资源之一,许多珍贵的中药材就生长在这里。 武夷山是一个旅游胜地,吸引着无数国内外的游客。在这里,游客可以爬山徒步,感受大自然的魅力;还可以品尝正宗的武夷岩茶,领略茶文化的博大精深;还可以参观古老的建筑和寺庙,体验中国古代文化的魅力。无论是自然风光还是人文历史,武夷山都能让人流连忘返,令人心旷神怡。 总之,武夷山以其独特的地理位置、自然风光和人文历史而闻名于世。这是一个既充满神秘和美丽的地方,也是一个让人沉醉其中的天堂。前来武夷山旅游的游客们不仅可以欣赏到壮丽的自然景观,还可以深入了解中国古代文化的魅力。无论是登山徒步还是品茗休闲,武夷山都能给人们带来一次难忘的旅行体验。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值