转:http://www.cnblogs.com/gaojun/archive/2010/08/15/1800099.html
VC++深入详解学习笔记
Lesson1: Windows程序运行原理及程序编写流程
Lesson2: 掌握C++基本语法
Lesson3: MFC框架程序剖析
Lesson4: 简单绘图
Lesson5: 文本编程
Lesson6: 菜单编程
Lesson7: 对话框编程
Lesson9: 定制应用程序的外观
Lesson10: 绘图控制
Lesson11 图形的保存和重绘
Lesson12 文件操作
Lesson 13: 文档串行化
Lesson14 网络编程
Lesson 15 多线程
Lesson 16 线程同步与异步套接字
Lesson17进程间通信
Lesson18 Active控件
Lesson19 动态链接库DLL
Lesson20 Hook与数据编程
窗口产生过程,句柄原理,消息队列,回调函数,窗口关闭与应用程序退出的工作关系,使用VC++的若干小技巧,stdcall与Lessonecl调用规范的比较,初学者常犯错误及注意事项。
1. Windows API与Win32 SDK
操作系统提供了各种方便开发Windows应用程序的编程接口,所的函数都在Windows。h头文件中声明。Win32 SDK(Software Development Kit): 即Windows 32位平台下的软件开发包,包括API函数,帮助文档,微软提供的一些辅助开发工具。
2. 窗口与句柄
窗口是是屏幕上一块矩形区域,是Windows应用程序与用户进行交互的接口。窗口分为客户区和非客户区。
在Windows应用程序中,窗口是通过窗口句柄(HWND)来标识的,要对某个窗口进行操作,首先就要得到这个窗口的句柄。其它各种资源(窗口,图标,光标等),系统在创建这些资源时会为它们分配内在,并返回标识这些资源的标识号,即句柄。-->光标句柄(HCURSOR),图标句柄(HICON)。
3. 消息与消息队列
Windows程序设计是一种基于消息的事件驱动方式的程序设计模式。
消息:在Windows中由结构体MSG来表示,
typedef struct tagMSG{
HWND hwnd;//消息所属的窗口句柄
UINT message;//消息本身标识符,由一数值表示,系统对消息定//义为WM_XXX宏(WM为Windows Message缩写)
WPARAM wParam; //随消息的不同附加信息也不同
LPARAM lParam; //消息的附加参数
DWORD time; //消息投递的时间
POINT pt; //鼠标当前位置
}
消息队列:每当一个Windows应用程序创建后,系统都会为该程序创建一个消息队列,这个消息队列用来存放该程序一的窗口的消息,消息产生后被投递到消息队列中,应用程序通过一个消息循环不断的消息队列中取出消息进行响应。响应过程由系统向应用程序发送消息,实际就是调用应用程序的消息处理函数。
4. 创建一个完整的Win32程序,该程序实现创建一个窗口,其中主要步骤为
A. WinMain函数的定义
B. 创建一个窗口 创建一个完整的窗口的四个步骤SDK,1设计窗口类,2注册窗口类,3创建窗口,4显示窗口
C. 进行消息循环
D. 编写窗口过程函数
回调函数的实现机制:
(1) 定义一个回调函数
(2) 提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者
(3) 当特定的事件或条件发和的时候,调用使用函数指针调用回调函数对事件进行处理
针对Windows的消息处理机制,窗口过程函数被调用的过程如下:
A. 在设计窗口类的时候,将窗口赛程函数的地址赋值给lpfnWndProc成员变量
B. 调用RegisterClass(&wndclass)注册窗口类,那么系统就有了我们所编写的窗口过程函数的地址
C. 当应用程序接收到某一窗口的消息,调用DispatchMessage(&msg)将消息加值给系统。系统则利用先前注册窗口类时得到函数指针,调用窗口过程函数对消息进行处理。
HICON LoadIcon(HINSTANCE hInstance, LPCTSTR lpIconName);
//加载窗图标,返回系统分配给该图标的句柄, LPCTSTR被定义为CONST CHAR *(指向常量的字符指针),图标的ID是一个常数,要使用MAKEINTRESOUCE宏把资源ID标识转换为需要的LPCTSTR类型
5. sprintf格式化字符,其头文件为stdio。h,在MFC中格式化字符用CString。Format
6. GetDC()与ReleaseDC()要成对使用,否则会内存泄漏。同样,BeginPaint()与
EndPaint(),这两个Parint只能在影响WM_PAINT消息中调用。
7. GetStockObject()得到画笔、画刷、字体、调色板的句柄,使用时必须用类型
转换。
如:hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH )//创建空画刷
8. 什么时候用NULL,什么时候用0。答,对指针赋值时用NULL,对变量赋值
时用0。
9. 什么是野指针?答:将指针指向的变量的内存释放后,此指针即变成野指针!
如何避免野指针?答:将此指针指向NULL即可。p=NULL;
#include <stdio.h> #include <windows.h> #include <stdexcept> using namespace std;
//回调函数原型声明,返回长整形的结果码,CALLBACK是表示stdcall调用 LRESULT CALLBACK WinProc( HWND hwnd, // handle to window UINT uMsg, // message identifier WPARAM wParam, // first message parameter LPARAM lParam // second message parameter );
//(1) WinMain函数,程序入口点函数 int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // command line int nCmdShow // show state ){ //(2) //一.设计一个窗口类,类似填空题,使用窗口结构体 WNDCLASS wnd; wnd.cbClsExtra = 0; //类的额外内存 wnd.cbWndExtra = 0; //窗口的额外内存 wnd.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);//创建一个空画刷填充背景 //加载游标,如果是加载标准游标,则第一个实例标识设置为空 wnd.hCursor = LoadCursor(NULL, IDC_CROSS); wnd.hIcon = LoadIcon(NULL, IDI_ERROR); wnd.hInstance = hInstance;//实例句柄赋值为程序启动系统分配的句柄值 wnd.lpfnWndProc = WinProc;//消息响应函数 wnd.lpszClassName = "gaojun";//窗口类的名子,在注册时会使用到 wnd.lpszMenuName = NULL;//默认为NULL没有标题栏 wnd.style = CS_HREDRAW | CS_VREDRAW;//定义为水平和垂直重画 //二.注册窗口类 RegisterClass(&wnd); //三.根据定制的窗口类创建窗口 HWND hwnd;//保存创建窗口后的生成窗口句柄用于显示 //如果是多文档程序,则最后一个参数lParam必须指向一个CLIENTCREATESTRUCT结构体 hwnd = CreateWindow("gaojun", "WIN32应用程序", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, NULL, NULL, hInstance, NULL); //四.显示窗口 ShowWindow(hwnd, SW_SHOWDEFAULT); //五.更新窗口 UpdateWindow(hwnd);
//(3).消息循环 MSG msg;//消息结构体 //如果消息出错,返回值是-1,当GetMessage从消息队列中取到是WM_QUIT消息时,返回值是0 //也可以使用PeekMessage函数从消息队列中取出消息 BOOL bSet; while((bSet = GetMessage(&msg, NULL, 0, 0)) != 0){ if (-1 == bSet) { return -1; } else{ TranslateMessage(&msg); DispatchMessage(&msg); } } return 0;//程序结束,返回0 }
//消息循环中对不同的消息各类进行不同的响应 LRESULT CALLBACK WinProc( HWND hwnd, // handle to window UINT uMsg, // message identifier WPARAM wParam, // first message parameter LPARAM lParam // second message parameter ){ switch (uMsg) { case WM_CHAR://字符按键消息 char szChar[20]; sprintf(szChar, "char is %d;", wParam);//格式化操作,stdio.h MessageBox(hwnd, szChar, "gaojun", 0);//输出操作windows.h中 break; case WM_LBUTTONDOWN://鼠标左键按下消息 MessageBox(hwnd, "this is click event!", "点击", 0); HDC hdc; hdc = GetDC(hwnd);//获取设备上下文句柄,用来输出文字 //在x=0,y=50(像素)的地方输出文字 TextOut(hdc, 0, 50, "响应WM_LBUTTONDONW消息!", strlen("响应WM_LBUTTONDONW消息!")); ReleaseDC(hwnd, hdc);//在使用完DC后一定要注意释放 break; case WM_PAINT://窗口重给时报消息响应 HDC hDc; PAINTSTRUCT ps; hDc = BeginPaint(hwnd, &ps); TextOut(hDc, 0, 0, "这是一个Paint事件!", strlen("这是一个Paint事件!")); EndPaint(hwnd, &ps); break; case WM_CLOSE://关闭消息 if (IDYES == MessageBox(hwnd, "确定要关闭当前窗口?", "提示", MB_YESNO)) { DestroyWindow(hwnd);//销毁窗口 } break; case WM_DESTROY: PostQuitMessage(0);//在响应消息后,投递一个退出的消息使用程序安全退出 break; default: return DefWindowProc(hwnd, uMsg, wParam, lParam);//调用缺省的消息处理过程函数 } return 0; } |
1. C++主要特点:
封装性(Encapsulation):把数据与操作数据的函数组织在一起,使程序结构更加紧凑,提高类内部数据的安全性。
继承性(Inheritance):增加了软件的可扩充性及代码重用性;
多态性(Polymorphism):使设计人员在设计程序时可以对问题进行更好的抽象,有利于代码的维护和可重用
2. C++中几个特性的实现技术和其它要注意的地方:
构造函数,析构函数,覆盖,隐藏,重载,继承,多态(迟绑定)等技术,算法
类的编写与应用
以下是部分资料:
1。定义结构体和类时。例如Class Point{int x;int y;};要注意一定加上分号,结构体定义默认成员是public,而Class定义默认为private
2。#include <xxx。h>与#include "xxx。h"的区别:<>不查找运行时目录,""查找运行时目录!(#include<xxx>引入是源文件,要用上using namespace xxx;)
3。类的定义中,如果未指明成员类型,则缺省为private。而结构体中则缺省为public。
4。引用:引用经常用在函数的传参上。另外数值交换函数也经常用引用。例
change(int &x,int &y){int temp;temp=x;x=y;y=x}调用时即可以用 int a=3;int b=4;change(a,b);一般不用指针来作为参数进行数值交换。因为会引起歧义。
5。通常将类的定义放。h文件,而将其实现放在cpp文件中,别忘记了在cpp文件中#include "xxx。h"
6。如何防止类的重复定义?
用#inndef Point_H_H
#define Point_H_H
class Point{};
#endif来防止
7。源文件cpp文件单独编译成obj文件。最后由链接器将与将要使用到的C++标准库类链接成exe文件,头文件不参加编译。所以在cpp文件中别忘记了加入#include "xxx。h"
8。函数的覆盖,在子类中重写父类的函数,此时采用早期绑定的方法。如果加入了virtual,则将采用迟绑定的技术,在运行时根据对象的类型确定调用哪一个函数。此迟绑定技术是MFC的类的继承的精髓。
9。强制类型转换。如果CFish从CAnimal派生而来。则可以将鱼的对象转换为CAnimal的对象,而反之则不行。从现实中理解也是正常的,鱼可以是动物,而动物却不是鱼。再如int可以强制转换成char型。而反之则出错。
1. MFC简介:MFC(Microsoft Foundation Class,微软基础类库)是微软为了简化程序员的开发工作所开发的一套C++类的集合,是一套面向对象的函数库,以为的方式提供给用户使用.利用这些类,可以有效发帮助程序员完成Windows应用程序的开发
MFC AppWizard是一个辅助生成源代码的向导工具,它可以帮助我们自动生成基于MFC框架的源代码.在向导的每一个步骤中,我们可以根据需要来选择各种特性,从而实现定制应用程序.
2. 窗口类,窗口对象与窗口关系
窗口类中定义一个类型为HWND成员变量,用来保存与之相关的窗口句柄值,可以用一个窗口类的实例即窗口对象来对应一个创建的窗口(是一种资源),窗口对象与窗口之间的关系是:
C++窗口类对象与窗口并不是一回事,它们之间唯一的关系是C++窗口类对象内部定义了一个窗口句柄变量,保存了与这个C++窗口类对象相关的那个窗口的句柄.窗口销毁时,与之对应的C++窗口类对象销毁与否,要看其生命周期是否结束,但C++窗口类对象销毁时,与之相关的窗口将销毁,因为它们之间的纽带(m_hWnd)已经断了,因此这时要回收窗口资源.
窗口销毁时调用DestroyWindow函数,窗口类对象销毁即将m_hWnd变量设置为NULL.
VC 6.0一些常用操作快捷方式:
功能分类 |
快捷键 |
说明 |
File (文件) |
Ctrl+N |
New新建工程 |
Ctrl+O |
Open 打开文件 |
|
Find (查找) |
Alt+F3/Ctrl+F |
弹出查找对话框 |
F3 |
查找下一个 |
|
Shift+F3 |
查找上一个 |
|
Ctrl+H |
替换 |
|
Ctrl+]/Ctrl+E |
寻找下一半括弧 |
|
F4 |
寻找下一个错误/警告位置 |
|
Shift+F4 |
寻找上一个错误/警告位置 |
|
格式 |
Ctrl+U |
将选定区域转换成小写 |
Ctrl+Shift+U |
将选定区域转换成大写 |
|
Alt+F8 |
自动格式重排 |
|
Build (建立) |
F7 |
Build(编绎并链接成exe文件) |
Ctrl+F7 |
Compile(编译) |
|
Ctrl+F5 |
Execute(编译+链接+运行) |
|
Ctrl+Shift+F5 |
Restarts the program(重新运行程序) |
|
Debug (调试) |
F5 |
Go(顺序执行) |
F11 |
step into (顺序执行,进入循环或函数) |
|
F10 |
step over(顺序执行,不进入循环或函数) |
|
Ctrl+F10 |
Run to cursor(自动执行到用户光标所指的语句前) |
|
Shift+F5 |
Stop Debugging(停止调试) |
|
F9 |
Insert/Remove breakpoint(在当前行插入/去掉断点) |
|
Ctrl+Shift+F9 |
去掉所有断点 |
1.在main或WinMain之前,全局变量已经被分配内存并初始化了。
2.在MFC中在WinMain之前有个theApp全局变量先被构造并被初始化,而由于子类构造函数执行前,其父类的构造函数先被执行,所以CTestApp的父类CWinAPP的构造函数先执行。产生了theApp对象后,在WinMain()中的指针*pThread和*pApp就有了内容。
3.MFC大致流程:
CTestApp theApp;//构造全局对象
WinMain()
{
AfxWinMain();//调用下面的函数
}
AfxWinMain()
{
pThread->Initinstance();//初始化工作和注册窗口类,窗口显示和更新
pThread->Run();//消息循环
}
而在BOOL CTestApp::InitInstance()中的代码
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CTestDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CTestView));
AddDocTemplate(pDocTemplate);
完成了将这三个类关联起来的工作。
4.如何在单文档文件中显示一个CButton的对象?
在CMainFrame::OnCreate()中定义一个CButton的对象btn;然后调用btn.Create("维新",WS_DISABLED |WS_CHILD | WS_VISIBLE | BS_AUTO3STATE,
CRect(0,0,300,100),/*GetParent(),*/this,123);
注意点:
(1).此处btn不能是局部变量,否则它的生命周期太短,将不能显示。
(2).在CBUTTON类的Create函数的第二个参数中加入WS_VISIBLE 参数才行。否则必须调用ShowWindow也可以在view的OnCreate消息响应函数中加入
(3).CButton类的定义头文件在afxwin.h中,而stdafx.h包含了afxwin.h,所以可以直接使用。因为MFC中的每一个类中都有#include "stdafx.h"的声明。
1. 在单文档中view挡在MainFrame的前面。此时如果编写针对MainFrame的mouseClick
事件,将不会有反应。因为MFC视类窗口是覆盖在框架窗口上的,因此框架窗口不能感
到鼠标消息.
2. MFC的消息映射机制:
在每个能接收和处理消息的类中,定义一个消息和消息函数对照表,即消息映射表.在消息映射表中,消息与对应的消息处理函数指针成对出现.某个类能处理的所有消息及其对应的消息处理函数的地址都列在这个类所对应的静态表中.当有消息需要处理时,程序只要搜索该消息静态表,查看表中是否含有该消息,就可知道该类能否处理此消息.如果能处理该消息,则同样依照静态表很容易找到并调用对应的消息处理函数.
MFC消息映射机制是针对能接受消息和处理消息的类来定义对应的消息映射表,而不是由父类来定义所有消息对应的虚函数,由子类来覆盖其函数实现,因为这样做会使程序背着一个很大的虚拟函数表的包袱运行,对内存是一种浪费.
MFC工程中一个消息映射在三处添加代码:
(1): CDrawView视类的头文件.h
//{{AFX_MSG(CDrawView)
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
两个AFX_MSG注释宏(因为加了注释符)之间,afx_msg是限定符(也是宏),表明函数是一个消息响应函数的声明,如果是用户自定义的消息函数响应声明则在注释宏下, DECLARE_MESSAGE_MAP之上加写代码
(2): CDrawView的cpp(源文件)的BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间,定义了CDrawView类的消息映射表,其中ON_WM_LBUTTONDOWN映射宏就是将鼠标左键按下消息(WM_LBUTTONDOWN)与一个消息响应函数(OnLButtonDown)关联.
BEGIN_MESSAGE_MAP(CDrawView, CView)
//{{AFX_MSG_MAP(CDrawView)
ON_WM_LBUTTONDOWN()
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
END_MESSAGE_MAP()
如果添加自定义的消息映射,使用ON_MESSAGE(用户定义消息,消息响应函数名)无”;”结尾
(3): 是CDrawView的cpp(源文件)中有函数实现。
void CDrawView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TOD Add your message handler code here and/or call default
m_ptOrigin=m_ptOld=point;
m_bDraw=TRUE;
CView::OnLButtonDown(nFlags, point);
}
通过分析MFC消息响应函数在程序中有三处属地省:函数原型,用来关联消息和消息响应函数的宏和函数实现.
3. 以下绘图程序,参考代码的注释可解决部分绘图问题
void CLesson3View::OnLButtonUp(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default //作图 /*调用SDK函数获取设备上下文句柄 HDC hdc; hdc = ::GetDC(m_hWnd);//这个m_hWnd是CWnd类中的保护成员, 保存窗口句柄, //而CLesson3View类是从CWnd类继承来的,所以也有这个成员 MoveToEx(hdc, m_ptnOrigin.x, m_ptnOrigin.y, NULL); LineTo(hdc, point.x, point.y); ::ReleaseDC(m_hWnd, hdc); // 在使用完设备上下文句柄后一定注意释放*/
/*使用CDC(MFC)关于作图对HDC一个封装*/ // CDC *pDc; // pDc = GetDC(); // pDc->MoveTo(m_ptnOrigin); // pDc->LineTo(point); // ReleaseDC(pDc);
/*使用客户区绘图类,这个是比较常用的*/ //CClientDC dc(this);//CClientDC的构造函数,使用当前窗口句柄值做为参数 //CClientDC dc(GetParent());//得到关于父类窗口一个设备上下文 // dc.MoveTo(m_ptnOrigin); // dc.LineTo(point); // CClientDC类在构造时调用GetDC,然后在释放时又调用ReleaseDC所以不用手动释放
//利用MFC的CWindowDC绘图 /好处是可以访问整个窗口区域,包括框架窗口客户区和非客户区,桌面等, // CWindowDC dc(this); // CWindowDC dc(GetParent()); // dc.MoveTo(m_ptnOrigin); // dc.LineTo(point); // CWindowDC dc(GetDesktopWindow());//这个可以画到桌面上其它地方 // dc.MoveTo(m_ptnOrigin); // dc.LineTo(point); //以上所画的线条颜色都是黑色的,因为在设备描述表中使用默认的画笔(黑色), //要改变线条颜色则需要自己生成一个新的画笔对象, //将它选到设备描述表中,再画就使用新画笔来绘图 // CPen m_pen(PS_DASH, 2, RGB(255, 0, 0));//生成新的画笔 // CClientDC dc(this); // CPen *pOldPen = dc.SelectObject(&m_pen);//选择进设备描述表中 // dc.MoveTo(m_ptnOrigin); // dc.LineTo(point); // dc.SelectObject(pOldPen);//在使用完新的画笔后,要将原来的画笔重新选择时设备描述表 //使用画刷来填充矩形 // CBrush m_brush(RGB(120, 0, 23)); // CClientDC dc(this); // dc.FillRect(CRect(m_ptnOrigin, point), &m_brush); //使用位图画刷来填充矩形 //创建一个位图对象 // CBitmap m_bitmap; // m_bitmap.LoadBitmap(IDB_MyBitmap); // CBrush m_Brush(&m_bitmap); // CClientDC dc(this); // dc.FillRect(CRect(m_ptnOrigin, point), &m_Brush); //透明画刷 //首先使用Win32的API函数GetStockObject来获取一个NULL_BRUSH画刷 CClientDC dc(this); CBrush *pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH)); //是静态成员函数,从句柄获取对象的指针 CBrush *pOldBrush = dc.SelectObject(pBrush); dc.Rectangle(CRect(m_ptnOrigin, point)); dc.SelectObject(pOldBrush); bIsMouseDown = FALSE; CView::OnLButtonUp(nFlags, point); } 类的静态成员函数可以由类名直接调用,也可以由对象调用。可以认为静态成员函数并不属于某个对象,它属于类本身。程序运行伊始,即使没有实例化类的对象,静态成员函数和静态成员变量已然有其内存空间。静态成员函数不能访问非静态成员变量!静态成员变量必须在类的外部初始化。当然如果并不打算用到静态成员变量,此时你可以不初始它。 |
4. 理解代码区,数据区,堆,栈!(http://www.cnblogs.com/gaojun/admin/(http://www.downcode.com/server/j_server/J_1010.Html)
对于一个进程的内存空间而言,可以在逻辑上分成3个部份:代码区,静态数据区和动态数据区。动态数据区一般就是“堆栈”。“栈(stack)”和“堆(heap)”是两种不同的动态数据区,栈是一种线性结构,堆是一种链式结构。进程的每个线程都有私有的“栈”,所以每个线程虽然代码一样,但本地变量的数据都是互不干扰。一个堆栈可以通过“基地址”和“栈顶”地址来描述。全局变量和静态变量分配在静态数据区,本地变量分配在动态数据区,即堆栈中。程序通过堆栈的基地址和偏移量来访问本地变量。
1,创建插入符:
void CreateSolidCaret( int nWidth, int nHeight );//创建插入符
void CreateCaret( CBitmap* pBitmap );//创建位图插入符
void ShowCaret( );//显示插入符
void HideCaret( );//隐藏插入符
static void PASCAL SetCaretPos( POINT point );//移动插入符号
说明:
1)创建插入符要在窗口创建完成之后,CreateSolidCaret函数创建的插入符被初始化为隐藏,所以需要调用ShowCaret()将其显示。
2)使用CreateCaret函数创建位图插入符的时候,不能使用局部的位图对象关联位图资源。(与资源相关联的C++对象,当它析构的时候会同时把与它相关联的资源销毁。)
2,获取当前字体信息的度量:CDC::GetTextMetrics
BOOL GetTextMetrics( LPTEXTMETRIC lpMetrics ) const;
说明:
typedef struct tagTEXTMETRIC { /* tm */
int tmHeight;//字体高度。Specifies the height (ascent + descent) of characters.
int tmAscent;//基线以上的字体高度
int tmDescent;//基线以下的字体高度
int tmInternalLeading;
int tmExternalLeading;
int tmAveCharWidth;//字符平均宽度
int tmMaxCharWidth;
int tmWeight;
BYTE tmItalic;
BYTE tmUnderlined;
BYTE tmStruckOut;
BYTE tmFirstChar;
BYTE tmLastChar;
BYTE tmDefaultChar;
BYTE tmBreakChar;
BYTE tmPitchAndFamily;
BYTE tmCharSet;
int tmOverhang;
int tmDigitizedAspectX;
int tmDigitizedAspectY;
} TEXTMETRIC;
//创建设备描述表
CClientDC dc(this);
//定义文本信息结构体变量
TEXTMETRIC tm;
//获得设备描述表中的文本信息
dc.GetTextMetrics(&tm);
//根据字体大小,创建合适的插入符
CreateSolidCaret(tm.tmAveCharWidth / 8, tm.tmHeight);
ShowCaret();
3,OnDraw函数:
virtual void OnDraw( CDC* pDC )
当窗口(从无到有或尺寸大小改变等)要求重绘的时候,会发送WM_PAIN消息,调用OnDraw函数进行重绘。在客户区的绘图如果想保持不变也可以在这个函数中进行编写,每次重给的时候会再次执行代码,生成绘图.
4,获取某字符串的高度和宽度(区别字符串的长度,长度表示字符个数):
CDC::GetTextExtent
CSize GetTextExtent( LPCTSTR lpszString, int nCount ) const;
CSize GetTextExtent( const CString& str ) const;
说明:
//The CSize class is similar to the Windows SIZE structure。
typedef struct tagSIZE {
int cx;//the x-extent
int cy;//the y-extent
} SIZE;
5,路径层:
BOOL BeginPath( );//CDC中函数
//在这作图定义路径层剪切区域
BOOL EndPath( );
BOOL SelectClipPath( int nMode );//调用这个函数来使当前路径层剪切区域与新剪切区域进行互操作。
//在这覆盖作图(包含前定义的路径层区域)定义新的剪切区域
说明:
1)SelectClipPath Selects the current path as a clipping region for the device context, combining the new region with any existing clipping region by using the specified mode. The device context identified must contain a closed path.
2)应用:当作图的时候,如果想要在整幅图形其中的某个部分和其它部分有所区别,我们可以把这部分图形放到路径层当中,然后指定调用指定互操作模式调用SelectClipPath( int nMode )函数来使路径层和覆盖在其上新绘图剪切区域进行互操作,达到特殊效果。
6,关于文本字符串一些函数:
COLORREF GetBkColor( ) const;//得到背景颜色
virtual COLORREF SetBkColor( COLORREF crColor );//设置背景颜色
BOOL SetTextBkColor( COLORREF cr );//设置文本背景颜色
virtual COLORREF SetTextColor( COLORREF crColor );//设置文本颜色
virtual BOOL TextOut( int x, int y, LPCTSTR lpszString, int nCount );//输出文本
BOOL TextOut( int x, int y, const CString& str );//在x,y所指定坐标处输出str
CString Left( int nCount ) const;//得到字符串左边nCount个字符
int GetLength( ) const;//得到字符串长度strlen()
7,字体CFont::CFont
CFont( );//构造函数
//Constructs a CFont object. The resulting object must be initialized with CreateFont, CreateFontIndirect, CreatePointFont, or CreatePointFontIndirect before it can be used.
选用字体事例代码组:
CClientDC dc(this);
CFont font;//构造字体对象
font.CreatePointFont(300,"华文行楷",NULL);//初始化字体对象,与字体资源相关联
CFont *pOldFont=dc.SelectObject(&font);//将新字体选入DC
...
dc.SelectObject(pOldFont);//恢复原字体
说明:
1)构造字体对象时候,必须初始化。(初始化是将字体对象与字体资源相关联)。
2)初始化对象时候,选用的字体也可以是系统字体,但不一定都有效,据测试选用。
8,在MFC中CEditView 和 cRichEditView类已经完成了初步的文字处理。可以让应用程序的View类以CEditView 和 cRichEditView类为基类。在创建工程中的第六步可以选择.
9,平滑变色
CDC::TextOut()是一个字母一个字母的输出,达不到平滑效果。
CDC::DrawText():将文字的输出局限于一个矩形区域,超出矩形区域的文字都被截断。利用这一特点,可每隔些时间增加矩形大小,从而可实现人眼中的平滑效果。
CWnd::SetTimer():设置定时器。按设定的时间定时发送WM_TIMER消息。
说明:
UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) );
//nIDEvent定时器标示,nElapse消息发送间隔时间,void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD)设置回调函数,如果设置则由设置的回调函数处理WM_TIMER消息,如果没有设置回调函数设为NULL,这发送的WM_TIMER消息压入消息队列,交由相关联的窗口处理(添加WM_TIMER消息处理函数OnTimer())。
afx_msg void OnTimer( UINT nIDEvent );
//响应WM_TIMER消息,nIDEvent为消息对应定时器标示(可以设置不同的定时器发送WM_TIMER消息)
问题:
1,在CCareView类中添加WM_CREATE消息响应函数OnCreate(),WM_CREATE消息是在什么时候被检查到而被响应的呢?
(猜测:添加WM_CREATE消息后,消息被压入消息队列,然后经过消息循环进行分发到具体窗口,从而进行响应)
2,现有一文本文件内容已经读入串STR中,要求在视图客户区按原先文本文件中的格式输出。
问题是,利用CDC的TextOut()来在CView类派生类窗口中输出串时,忽略了串中的TAB、回车换行等格式,无论串有多长均在一行上输出。
这其中是CDC类成员函数TextOut()忽略串中格式的,还是CView类派生类窗口设置从中做怪呢?怎么解决
1. MFC中的顶层菜单默认为弹出菜单(Pop-up),它是不能用来作命令响应的,当取消Pop-up选项后可接受命令响应。
2. 消息的分类:标准消息,命令消息,通告消息。
[标准消息]:除WM_COMMAND之外,所有以WM_开头的消息。从CWnd类派生的类都可以接收到这一消息
[命令消息]:来自菜单、加速键或工具栏按钮的消息。这类消息都以WM_COMMAND呈现。在MFC中,通过菜单项的标识(ID)来区分不同的命令消息;在SDK中,通过消息的wParam参数识别。从CCmdTarget(CWnd的父类)派生的类都可以接收到这一类消息
[通告消息]:由控件产生的消息,例如,按钮的单击,列表框的选择等均产生此类消息,为的是向其父窗口(通常是对话框)通知事件的发生。这类消息也是以WM_COMMAND形式呈现。从CCmdTarget(CWnd的父类)派生的类都可以接收到这一类消息
总结:凡是从CWnd派生的类,既可以接收标准消息,也要以接收命令消息和通告消息。而对于那些从CCmdTarget派生的类,则只能接收命令消息和通告消息,不能接收标准消息。
3. MFC中菜单项消息如果利用ClassWizard来对菜单项消息分别在上述四个类中进行响应,则菜单消息传递顺序:View类--Doc类--CMainFrame类--App类。菜单消息一旦在其中一个类中响应则不再在其它类中查找响应函数。
菜单消息与前面介绍的标准消息的实现机制是相类似的,都是在消息响应函数原型(头文件),消息映射宏(源文件)和消息函数实现(源文件)中添加代码。注意:文档类与应用程序类都是由CCmndTarget类派生,所以可以接收菜单命令消息,但不能接收标准消息(只能由CWnd类派生才可以)。
具体消息路由过程:
当点击一个菜单项的时候,最先接受到菜单项消息的是CMainFrame框架类,CMainFrame框架类将会把菜单项消息交给它的子窗口View类,由View类首先进行处理;如果View类检测到没对该菜单项消息做响应,则View类把菜单项消息交由文档类Doc类进行处理;如果Doc类检测到Doc类中也没对该菜单项消息做响应,则Doc类又把该菜单项消息交还给View类,由View类再交还给CMainFrame类处理。如果CMainFrame类查看到CMainFrame类中也没对该消息做响应,则最终交给App类进行处理。
4. 一个菜单栏可以有若干个子菜单,一个子菜单又可以有若干个菜单项等。对菜单栏的子菜单由左至右建立从0开始的索引。对特定子菜单的菜单项由上至下建立了从0开始的索引。访问子菜单和菜单项均可以通过其索引或标识(如果有标识的话)进行。
相关重要函数:
CMenu* GetMenu( ) ;//CWnd::GetMenu得到窗口菜单栏对象指针。
CMenu* GetSubMenu( ) ;//CMenu::GetSubMenu获得指向弹出菜单对象指针
UINT CheckMenuItem( );//CMenu::CheckMenuItem 添加选中标识
BOOL SetDefaultItem();//CMenu::SetDefaultItem 为指定菜单设置缺省菜单项
BOOL SetMenuItemBitmaps( );//CMenu::SetMenuItemBitmaps 设置位图标题菜单。
UINT EnableMenuItem();//CMenu::EnableMenuItem使菜单项有效,无效,或变灰。
BOOL SetMenu( CMenu* pMenu );//CWnd::SetMenu在当前窗口上设置新菜单或移除菜单。
HMENU Detach( );//CMenu::Detach;断开一个菜单资源与相关的类对象句柄关联,可以定义局部对象,在使用完后调用Detach函数,则不会因为局部对象影响使用
说明:
1)在计算子菜单菜单项的索引的时候,分隔栏符也算索引的。
2)int GetSystemMetrics()获取系统信息度量。可以用它来获取菜单标题的尺寸(后面还会使用到获取屏目尺寸)从而设置位图标题菜单中位图的大小。
3)在MFC中MFC为我们提供了一套命令更新机制,所有菜单项的更新都是由这套机制来完成的。所以要想利用CMenu::EnableMenuItem来自己控制菜单使用或不使用变灰等,必须要在CMainFrame的构造函数中将变量m_bAutoMenuEnable设置为FALSE。
EXAMPLE:
CMenu menu;//定义为局部对象
menu.LoadMenu(IDR_MAINFRAME);
SetMenu(&menu);
menu.Detach();// 这里menu对象作为一个局部对象。使用Detach()从menu对象中分离窗口菜单句柄,从而当menu对象析构的时候窗口菜单资源不随之销毁。
5. 命令更新机制:
菜单项状态的维护是依赖于CN_UPDATE_COMMAND_UI消息,谁捕获CN_UPDATE_COMMAND_UI消息,MFC就在其中创建一个CCmdUI对象。
在后台操作系统发出WM_INITMENUPOPUP消息,然后由MFC的基类如CFrameWnd接管并创建一个CCmdUI对象和第一个菜单项相关联,调用对象成员函数DoUpdate()(注:这个函数在MSDN中没有找到说明)发出CN_UPDATE_COMMAND_UI消息,这条消息带有指向CCmdUI对象的指针。此后同一个CCmdUI对象又设置为与第二个菜单项相关联,这样顺序进行,直到完成所有菜单项。
更新命令UI处理程序仅应用于弹出式菜单项上的项目,不能应用于永久显示的顶级菜单项目。
说明:
1)可以手工或用ClassWizard来给菜单项添加UPDATE_COMMAND_UI消息响应,利用响应函数中传进来的CCmdUI对象指针可完成设置菜单项可使用,不可使用,变灰,设置标记菜单等操作。
6,如果要想让工具栏上的某个图标与菜单项的某个菜单相关联,只需要将图标的ID设置为该菜单项的ID。
工具栏图标的索引记数顺序是:从做至右从0开始,分隔符也算索引号。
7,利用向项目中添加VC的POPMENU控件:Project->Add to Project->Components and Controls..
系统增加的内容:A,一个菜单资源;B,在派生View类中增加了OnContextMenu()函数
说明:
1)CWnd::OnContextMenu Called by the framework when the user has clicked the right mouse button (right clicked) in the window. You can process this message by displaying a context menu using the TrackPopupMenu.
2)BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL );
//CMenu::TrackPopupMenu Displays a floating pop-up menu at the specified location and tracks the selection of items on the pop-up menu. A floating pop-up menu can appear anywhere on the screen.
8,利用调用TrackPopupMenu函数,手工添加弹出菜单:
1)用资源管理器添加一个菜单资源
2)在鼠标右键消息响应函数中,加载菜单资源,并获得要显示的子菜单指针,并用该指针调用TrackPopupMenu函数便完成任务(但要注意:鼠标响应函数传进来的坐标是客户区坐标,而TrackPopupMenu函数中使用的是屏幕坐标,在调用TrackPopupMenu前要调用ClientToScreen客户区坐标到屏幕坐标的转换)
事例代码:
CMenu menu;
menu.LoadMenu(IDR_MENU1);
CMenu *pPopup=menu.GetSubMenu(0);
ClientToScreen(&point);
pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y,this);
说明:
CWnd::ClientToScreen(..);//将一个坐标点或一个矩形区域坐标转换成屏幕坐标。
CMenu::TrackPopupMenu(..);//在指定位置以指定的方式显示弹出菜单。
CWnd::ScreenToClient(..);
//Converts the screen coordinates of a given point or rectangle on the display to client coordinates.
9,当弹出菜单属于框架窗口的时候(可在TrackPopupMenu函数参数中设置),弹出菜单上的消息,在路由的时候,仍然遵循View-DOC-MainFrame-APP的响应顺序。
10,动态菜单编程:
所有的资源对象都有一个数据成员保存了资源的句柄。
CMenu::AppendMenu //Appends a new item to the end of a menu.
CMenu::CreatePopupMenu //Creates an empty pop-up menu and attaches it to a CMenu object.
CMenu::InsertMenu
//Inserts a new menu item at the position specified by nPosition and moves other items down the menu.
CMenu::GetSubMenu //Retrieves a pointer to a pop-up menu.
CWnd::GetMenu //Retrieves a pointer to the menu for this window.
CMenu::DeleteMenu //Deletes an item from the menu.
11,手动给动态菜单项添加响应函数:
在Resource.h中可以添加资源的ID
在头文件中写消息函数原型
在代码文件中添加消息映射和添加消息响应函数
说明:
可以先创建一个临时的菜单项,设置它的ID和动态菜单项的一致,然后对它用向导进行消息响应,然后删除临时菜单。
再在代码文件中把消息映射放到宏外(注意一定要放到宏外面,因为CLASSWIZARD发现菜单删除了,同时要把其宏对里的消息映射也删除掉的)
12,CWnd::DrawMenuBar
//Redraws the menu bar. If a menu bar is changed after Windows has created the window, call this function to draw the changed menu bar
CWnd::GetParent //get a pointer to a child window’s parent window (if any).
CWnd::Invalidate //注意其参数的作用
13,集合类:
CStringArray,CStringArray,CDWordArray,CPtrArray,CStringArray,CUIntArray,CWordArray
其中成员函数:
CArray::GetAt
CArray::Add
14,命令消息是到OnCommand函数的时候完成路由的。
由于CWnd::OnCommand 是个虚函数,可以在框架类中重写OnCommand函数,从而可以截获菜单消息使它不再往下(VIEW类)路由。
例:
BOOL CMainFrame::OnCommand(WPARAM wParam, LPARAM lParam)
{
// TODO: Add your specialized code here and/or call the base class
int MenuCmdId=LOWORD(wParam);//取命令ID
CMenu2View *pView=(CMenu2View*)GetActiveView();//获取当前VIEW类指针
if(MenuCmdId>=IDM_PHONE1 && MenuCmdId<IDM_PHONE1+pView->m_strArray.GetSize())//消息范围判断
{
CClientDC dc(pView);
dc.TextOut(0,0,pView->m_strArray.GetAt(MenuCmdId-IDM_PHONE1));
return TRUE;
//函数返回,避免调用CFrameWnd::OnCommand函数,在CFrameWnd::OnCommand中截获的消息会交由VIEW类处理
}
return CFrameWnd::OnCommand(wParam, lParam);
//调用基类OnCommand函数,在CFrameWnd::OnCommand中截获的消息会交由VIEW类处理
}
15,LOWORD与HIWORD宏
WORD LOWORD(
DWORD dwValue // value from which low-order word is retrieved
);
WORD HIWORD(
DWORD dwValue // value from which high-order word is retrieved
);
//The LOWORD macro retrieves the low-order word from the given 32-bit value.
//The HIWORD macro retrieves the high-order word from the given 32-bit value.
16,CFrameWnd::GetActiveView
CView* GetActiveView( ) const;//获取当前视窗口指针(单文档框架中)
源文件是单独参与编译的。
1. Windows应用程序工作的基本流程是从用户那里得到数据,经过相应的处理之后,现把处理结果输出到屏幕,打印机或者绵输出设备。这就需要用到Windows应用程序的用户接口对话框。对话框就是一个窗口,它不公可以接收消息,而且还可以被移动和关闭,甚至可以在它的客户区中进行绘图。相当于一个窗口,在它上面能够旋转各种标准控件和扩展控件。都是由CWnd类派生来
2. 对话框的类型:模态(Model)对话框和非模态(Modeless)对话框
模态对话框:指当其显示时,程序会暂停执行,直到关闭这个模态对话框后,才能继续执行程序中其它任务。当一个模态对话框打开时,用户只能与该对话框进行交互,而其它用户界面对象接收不到输入信息。
非模态对话框:当其显示时,允许执行程序中其它任务,而不用关闭这个对话框。
在MFC中,对资源的操作通常都是通过一个与资源相关的类来完成。对话框资源对应CDialog基类。
3. 在对话框资源界面,选择[View]->[ClassWizard]菜单命令,(也可以新建的对话框资源上双击鼠标左键),选择一个基类,创建关于它的类。其中一般有两个函数一个是构造函数(可用于初始化成员变量),另外一个是DoDataExchange主要用来完成对话框数据的交换和校验,要想在其它窗口的事件中显示该对话框就涉及到创建对话框的模式(模态与非模态)
模态对话框的创建:
创建模态对话框要调用CDialog类的成员函数:DoModel(),创建一个模态对话框,其返回值作为CDialog类的另一个成员函数:EndDialog的参数,后者的功能就是关闭模态对话框。
Void CMyboleView::OnDialog(){
CTestDlg dlg;//视类中源文件要包含这个类的头文件
dlg.DoModal();//创建一个模态对话框,
}
非模态对话框的创建:
需要利用CDialog类的Create成员函数。
BOOL Create(LPCTSTR lpszTemplateName, CWnd* pParaentWnd = NULL);
BOOL Create(UINT nIDTemplate, Cwnd* pParentWnd = NULL);
lpszTemplateName对话框模板的名称,nIDTemplate对话框模板的ID,pParentWnd对话框的父窗口,如果是NULL则父窗口就是主应用程序窗口
注意:当创建非模态对话框时,还需要调用ShowWindow函数显示对话框,而模态的不需要
Void CMyboleView::OnDialog(){
CTestDlg *pDlg = new CTestDlg;//定义成指针,在堆上分配内存(或定义为全局变
//量,在堆上分配的内存如不主动销毁则与程序生命周期一样,
pDlg->Create(IDD_DIALOG1, this);//创建一个非模态对话框,使用对话框ID,
//父窗口是调用OnDalog函数的窗口
pDlg->ShowWindow(SW_SHOW);//显示窗口
}
4. 动态添加Button
void CDlg::OnBtnAdd()
{
if (!IsCreated/*m_btn.h_Wnd*/)//也可使用m_btn对象中的成员变量判断句柄是否有值
{
m_btn.Create("New", BS_DEFPUSHBUTTON | WS_VISIBLE | WS_CHILD,
CRect(0, 0, 100, 100), this, 123);//在对话框上动态添加一个按钮
IsCreated = TRUE;//判断是否已经创建了按钮,是的话销毁,现点击可再重新创建
}
else
{
m_btn.DestroyWindow();//销毁窗口,因为按钮也是从CWnd继承来
IsCreated = FALSE;
}
}
5. 控件的访问
获取对话框上的项目指针:GetDlgItem()
获取窗口信息:GetWindowText()
更改窗口信息:SetWindowText()
直接取得对指定话框上项目的信息:GetDlgItemText() 想当于GetDlgItem()和GetWindowText()合用。
当然,有SetDlgItemText() 相当于GetDlgItem()和SetItemText() 合用。
GetDlgItemInt(),SetDlgItemInt()等等,S/GetDlgItemInt()可以处理有符号的整数。
字符到数组的转换:atoi() 转换一个类型到指定类型时,用 类型的第一个字母 to 指定类型的一个字母。
在DoExchange函数里,放置以DDX_为前缀的函数,来关联一个控件和变量,DDV_为前缀的函数,用来校验一个控件内容。
DDX_(对话框数据交换) DDV_(对话框数据校验)
注意,在用数据变量关联控件的方式时,千万注意要使用UpdateData()!
也可以用一个控件变量关联一个控件,用它的成员函数,来对控件进行操作,例如:CEDIT.GetWindowText()
SendMessage()的用法,比较好用,注意,发送消息,是控件向系统发送,由系统处理。
SendDlgItemMessage()。
总结以上:
1,GetDlgItem()->G/SetWindowText()。
2, G/SetDlgItemText()。
3, G/SetDlgItemInt。
4, 将一个控件和一个整型,字符串或其它类型变量相关联。
5, 将一个控件和一个控件变量相关联,用成员函数GetWindowText()和SetWindowText()去访问控件。
6, SendMessage(),Windows程序都基于消息的,为了获取或设置窗口的文本,只要知道获取或设置窗口文本的消息,就可以通过SendMessage来发送这条消息,从而获取或设置控件的文本。获取窗口文本的消息是WM_GETTEXT发送该消息后,系统将把指定窗口的文本复制到调用者提供的一个缓存中,在这个消息两个附加参数中,wParam指定将复制的字符数据,lParm就是调用者提供的用来保存窗口文本的缓存地址。WM_SETTEST消息wParam参数没用,值为0,lParam参数指定用来设置文本字符串地址;SendDlgItemMessage()
以下是几种控件操作的代码
void CTestDlg::OnBtnAdd() { // TODO: Add your control notification handler code here //点击后创建一个新的按钮 // if (!m_btn) // { // m_btn.Create("新按钮", BS_DEFPUSHBUTTON | WS_VISIBLE | WS_CHILD, CRect(0, 0, 100, 20), this, 123); // } // if (!m_btn.m_hWnd) // { // m_btn.Create("新按钮", BS_DEFPUSHBUTTON | WS_VISIBLE | WS_CHILD, CRect(0, 0, 100, 20), this, 123); // } // else{ // m_btn.DestroyWindow(); // } /*对话框控件访问方式 (1):GetDlgItem()->G(S)etWidowText()方式 */ /*int num1, num2, num3; char ch1[10], ch2[10], ch3[10];
GetDlgItem(IDC_NUM1)->GetWindowText(ch1, 10); GetDlgItem(IDC_NUM2)->GetWindowText(ch2, 10);
num1 = ::atoi(ch1); num2 = ::atoi(ch2); num3 = num1 + num2;
::itoa(num3, ch3, 10);
GetDlgItem(IDC_NUM3)->SetWindowText(ch3); */ /*对话框控件访问方式 (2):G(S)etDlgItemInt()方式 */ /*int num1, num2, num3; num1 = GetDlgItemInt(IDC_NUM1, NULL, TRUE); num2 = GetDlgItemInt(IDC_NUM2, NULL, TRUE);
num3 = num1 + num2; SetDlgItemInt(IDC_NUM3, num3, TRUE); */
/*对话框控件访问方式 (3):G(S)etDlgItemText()方式 */ /*int num1, num2, num3; char ch1[10], ch2[10], ch3[10];
GetDlgItemText(IDC_NUM1, ch1, 10); GetDlgItemText(IDC_NUM2, ch2, 10);
num1 = ::atoi(ch1); num2 = ::atoi(ch2); num3 = num1 + num2;
::itoa(num3, ch3, 10);
SetDlgItemText(IDC_NUM3, ch3); */
/*对话框控件访问方式 (4):将控件和整型变量相关联,然后调用UpdateData(TRUE)初始化或校验数据,在操作完成后调用UpdateData(FALSE);实现对话框数据初始化 */ /*注意,该方法有数据校验的功能,在添加变量时可以指定数据值的范围 UpdateData(TRUE);//获取编辑框中的数据 m_num3 = m_num2 + m_num1;//数据操作(+) UpdateData(FALSE);//初始化编辑框3中的数据,即将变量中的数据显示到控件中 */ /*对话框控件访问方式 (5):将控件和控件变量相关联,然后调用控件变量的G(S)etWindowext方法 */ /*int num1, num2, num3; char ch1[10], ch2[10], ch3[10];
m_cNum1.GetWindowText(ch1, 10); m_cNum2.GetWindowText(ch2, 10);
num1 = ::atoi(ch1); num2 = ::atoi(ch2); num3 = num1 + num2;
::itoa(num3, ch3, 10);
SetDlgItemText(IDC_NUM3, ch3); */ /*对话框控件访问方式 (6):SendMessage(),平台SDK的函数,发送WM_GETTEXT/WM_SETTEXT消息 */ /*int num1, num2, num3; char ch1[10], ch2[10], ch3[10]; ::SendMessage(GetDlgItem(IDC_NUM1)->m_hWnd, WM_GETTEXT, 10, (LPARAM)ch1); ::SendMessage(GetDlgItem(IDC_NUM2)->m_hWnd, WM_GETTEXT, 10, (LPARAM)ch2);
num1 = ::atoi(ch1); num2 = ::atoi(ch2); num3 = num1 + num2;
::itoa(num3, ch3, 10); ::SendMessage(GetDlgItem(IDC_NUM3)->m_hWnd, WM_SETTEXT, 10, (LPARAM)ch3); GetDlgItem(IDC_NUM1)->SendMessage(WM_GETTEXT, 10, (LPARAM)ch1); */
/*对话框控件访问方式 (8):SendDlgItemMessage() */ int num1, num2, num3; char ch1[10], ch2[10], ch3[10]; SendDlgItemMessage(IDC_NUM1, WM_GETTEXT, 10, (LPARAM)ch1); SendDlgItemMessage(IDC_NUM2, WM_GETTEXT, 10, (LPARAM)ch2); num1 = ::atoi(ch1); num2 = ::atoi(ch2); num3 = num1 + num2;
::itoa(num3, ch3, 10);
SendDlgItemMessage(IDC_NUM3, WM_SETTEXT, 0, (LPARAM)ch3); SendDlgItemMessage(IDC_NUM3, EM_SETSEL, 0, -1); GetDlgItem(IDC_NUM3)->SetFocus(); // GetDlgItem(IDC_NUM1)->GetWindowText(ch1, 10); // GetDlgItem(IDC_NUM2)->GetWindowText(ch2, 10); // num1 = ::atoi(ch1); // num2 = ::atoi(ch2); // num3 = num1 + num2; // itoa(num3, ch3, 10); // GetDlgItem(IDC_NUM3)->SetWindowText(ch3);
// CString str1, str2, str3; // int num1, num2, num3; // GetDlgItem(IDC_NUM1)->GetWindowText(str1); // GetDlgItem(IDC_NUM2)->GetWindowText(str2); // num1 = ::atoi(str1); // num2 = ::atoi(str2); // num3 = num1 + num2; // str3.Format("%d", num3); // GetDlgItem(IDC_NUM3)->SetWindowText(str3);
// UpdateData(); // m_num3 = m_num1 + m_num2; // UpdateData(FALSE); } itoa函数:将整型转换成字符 atoi函数:将字符转换成整型 SendMessage函数:要使用SDK函数,前面加::表示全局函数,第一个参数句柄可以先获取控件句柄然后再取出其m_hWnd窗口句柄变量,如果控件绑定变量,使用变量的SendMessage函数则不需要句柄值 |
9,伸缩对话框:
改变窗口的大小Wnd::SetWindowPos()对话框从父类继承来的函数。
判断一个矩形是否为空:IsRectEmpty(),IsRectNull()。前者是判断矩形面积是否为空,后者是判断矩形的四个坐标值是否为0,不关心是否能做为一个矩形。
·····SetWindowPos()改变窗口的大小和Z次序的排列。 ·····
10,焦点的问题(:
SetFocus()
SetWindowLong
::GetNextWindow()
::GetWindow()
::GetNextDlgTabItem()
CWnd::GetWindow()
CWnd::GetNextWindow
CWnd::GetNextDlgTabItem()
缺省OK按钮的ID为IDOK,
VC++环境下打开对话框资源编辑器,选择[Layout]->[Tab order]可以查看和调整对话框上控件的tab顺序;
11,在用变量来更新控件值,更新控件数据与获取控件数据都要注意一点:
无论是获取数据还是更新窗口上的数据都是要进行数据交换,要使用UpdateData函数,
Void CTestDlg::OnBtnAdd(){
UpdateData();//使控件变量获取控件上的数据
m_num3 = m_num1 + m_num2;//三个变量都是绑定控件的,对应控件中的值
UpdateData(FALSE);//函数默认参数为TRUE,要更新控件数据设置为FALSE
}
6. 夺
逃跑按钮的巧妙实现
1.如何改变按纽的字体?在对话框的属性中改变字体的属性即可
2.逃跑按纽的实现
1.从CButton派生一个类,CWeixinBtn
2.将IDC_EDIT1关联成员变量m_btn1,类型为CWeixinBtn,注意要包含头文件。
3.在CWeixinBtn中加一个指针成员变量CWeixinBtn *pWeixinBtn,然后将其地址初始化。
4.在新类中增加鼠标移动的消息处理。
3.属性表单
1.插入属性页资源。Insert->new Resource->Dialog
2.当选择Classwizard菜单时,系统提示是否为创建新的类,我们将其从CPropertyPage派生!这样可以为方便为其增加消息响应函数。
3.插入新的从CPropertySheet派生的类,在类中增加3个CPropertyPage的实例。
4.在view中增加菜单项,当点击时显示属性表单,出现中文乱码,修改CPropertyPage属性为中文,另外将其字体设为宋体。
5.在CPropertyPage中设置SetWizardButtons可将其属性改为上一步、完成!
6.为IDC_RADIO1关联成员变量,需要先设置Group属性才行。另外别忘记调用UpdateData().
7.为CPropertyPage增加虚函数,OnWizardNext,如果用户点击下一步时,不想让他进入下一步,刚返回-1!
8.将用户的选择输出到屏幕上,此时可以在View中增加几个成员变量,用来接收用户选择的数据。
注意: memset()的用法! memset(m_bLike,0,sizeof(m_bLike));将所指定长度设置为0
ZeroMemory(m_bLike, sizeof(m_bLike));同上效果
1,修改外观和图标可以在MainFrm中进行,而修改背景和光标只能在View中进行。为什么?因为view的显示挡在了MainFrame的前面。
a.在MainFrame中
PreCreateWindow()中,在窗口创建之前,用重新注册窗口类的方法,比较麻烦。在PreCreateWindow()中修改也可以用简单的方法,用全局函数
//cs.lpszClass=AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,0,0,
// LoadIcon(NULL,IDI_WARNING));在窗口创建之后,在OnCreate()中修改
//SetWindowLong(m_hWnd,GWL_STYLE,WS_OVERLAPPEDWINDOW);
//SetWindowLong(m_hWnd,GWL_STYLE,GetWindowLong(m_hWnd,GWL_STYLE) & ~WS_MAXIMIZEBOX);
// SetClassLong(m_hWnd,GCL_HICON,(LONG)LoadIcon(NULL,IDI_ERROR));
b.在View中
PreCreateWindow()中
//cs.lpszClass=AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,
// LoadCursor(NULL,IDC_CROSS),(HBRUSH)GetStockObject(BLACK_BRUSH),NULL);
cs.lpszClass=AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW);
OnCreate()中
SetClassLong(m_hWnd,GCL_HBRBACKGROUND,(LONG)GetStockObject(BLACK_BRUSH));
SetClassLong(m_hWnd,GCL_HCURSOR,(LONG)LoadCursor(NULL,IDC_HELP));
2.创建一个不断变化的图标。用定时器和SetClassLong完成
a.准备三个图标文件,放在RES文件夹,Insert->Resource-三个图标,
b.在CMainFrame中增加图标句柄数组,m_hIcons[3]
m_hIcons[0]=LoadIcon(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDI_ICON1));//MAKEINTRESOURCE是一个宏,它将整数转化为Win32的资源类型,简单的说它是一个类型转换
#define MAKEINTRESOURCEA(i) (LPSTR)((DWORD)((WORD)(i)))
m_hIcons[1]=LoadIcon(theApp.m_hInstance,MAKEINTRESOURCE(IDI_ICON2));//此处需要用到theAPP对象,故要在文件中声明extern CStyleApp theApp;
m_hIcons[2]=LoadIcon(AfxGetApp()->m_hInstance,MAKEINTRESOURCE(IDI_ICON3));
然后将其初始化
c.然后在定时器中实现
3.工具栏的编程
a.加入分隔符的方法,向右拖动即可;
b.删除按纽的方法,拖出即可。
4.创建一个新的工具栏的方法
a.插入一个工具栏,画出其图形。
b.在头文件中,定义CToolBar m_newToolBar
c.在MainFrm.cpp的OnCreate()中调用
if (!m_newToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_RIGHT
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_newToolBar.LoadToolBar(IDR_TOOLBAR1))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
d.点击“新的工具栏”菜单时,隐藏工具栏。两种方法
第一种/*if(m_newToolBar.IsWindowVisible())
{
m_newToolBar.ShowWindow(SW_HIDE);
}
else
{
m_newToolBar.ShowWindow(SW_SHOW);
}
RecalcLayout();
DockControlBar(&m_newToolBar);*/
第二种ShowControlBar(&m_newToolBar,!m_newToolBar.IsWindowVisible(),FALSE);
e.将菜单增加复选标记。在OnUpdateUI中加入代码
pCmdUI->SetCheck(m_newToolBar.IsWindowVisible());
5.状态栏编程
a.Indicator[]数组中有状态栏的信息
如果要增加,可以在String Table中加入一个IDS_Timer,然后将其加入到[]中。
b.在时间栏显示时间,代码略,比较简单
6.进度栏
a.增加成员变量,CProgressCtrl m_progress
b.OnCreate中 m_progress.Create(WS_CHILD | WS_VISIBLE,// | PBS_VERTICAL,
rect,&m_wndStatusBar,123);
m_progress.SetPos(50);*/
c.将其创建到状态栏的方法!如果在OnCreate()中创建,则不成立,因为获取矩形大小时失败。
解决办法,用自定义消息:
在MainFrm.h中#define UM_PROGRESS WM_USER+1//WM_USER是一个界限
消息函数原型声明:afx_msg void OnProgress()
在MainFrm.cpp中
ON_MESSAGE(UM_PROGRESS,OnProgress)
然后实现这个函数
void CMainFrame::OnProgress()
{
CRect rect;
m_wndStatusBar.GetItemRect(2,&rect);
m_progress.Create(WS_CHILD | WS_VISIBLE | PBS_SMOOTH,
rect,&m_wndStatusBar,123);
m_progress.SetPos(50);
}
最后在OnCreate中调用 PostMessage(UM_PROGRESS);//不能用SendMessage()
d.解决重绘时进度栏改变的问题。在OnPain()中重写代码
CRect rect;
m_wndStatusBar.GetItemRect(2,&rect);
m_progress.Create(WS_CHILD | WS_VISIBLE | PBS_SMOOTH,
rect,&m_wndStatusBar,123);
m_progress.SetPos(50);
然后在定时器消息处理函数中加入
m_progress.StepIt();
e.显示鼠标位置。在View中增加OnMouseMove()处理函数
CString str;
str.Format("x=%d,y=%d",point.x,point.y);
//((CMainFrame*)GetParent())->m_wndStatusBar.SetWindowText(str);
//((CMainFrame*)GetParent())->SetMessageText(str);
//((CMainFrame*)GetParent())->GetMessageBar()->SetWindowText(str);
GetParent()->GetDescendantWindow(AFX_IDW_STATUS_BAR)->SetWindowText(str);
7.加入启动画面
Project-Component and ->Visual C++ Components->SplashScreen->插入
1. 画图:
a.创建四个菜单,为其添加消息响应;
b.在View中添加m_DrawType,保存绘画类型;
c.增加成员变量,m_PtOrigin,当按下鼠标左键时,保存此点;
d.在OnLButtonUp中画点,线,矩形,椭圆,别忘记设置成透明画刷
2. 为其添加一个设置对话框(线型和线宽)
a.创建对话框,为其创建一个新类关联它;
b.为其中的线宽关联成员变量;
c.在View中增加一个菜单,响应新的对话框;
d.添加线型选项设置,将其Group属性选中,并为单选按纽关联成员变量。在view中增加一个线型变量m_nLineStyle
3 .添加一个颜色对话框
a.实例化一个CColorDialog
b.调用DoModal方法
4. 添加字体对话框,将选择的字体在View中显示出来。
a.实例化一个对象;
b.为View添加一个字体成员变量,得到用户选择的字体。
c.调用Invadate()发出重绘消息;
d.再次注意一个对象只能创建一次,故要再次创建,必须将原告的删除!
5. 为设置对话框增加示例功能。
a.当控件内容改变时,发出En_change消息。而Radio按纽则为Clicked。需先UpdateData()。另外还需要ScreenToClient(&rect)
6. 改变对话框的背景色和控件颜色。
每个控件被绘制时都发出WM_CTlColor消息,
7. 如何改变OK按纽的字体和背景?
OK按纽
a.创建一个新类,CTestBtn,基类为CButton
b.在类中增加虚函数,DrawItem,添加代码。
c.将OK按纽关联成员变量。类型为CTestBtn,注意将OK按纽的OwnerDraw特性选中。
Cancel按纽
用新类来改变。
a.加入新文件。
b.为Cancel关联一个成员变量,类型为CSXBtn;
c.调用CSXBtn的方法。
Cancel2按纽
a.方法同上。
8. 在窗口中贴图,4个步骤
1、创建位图
CBitmap bitmap;
bitmap.LoadBitmap(IDB_BITMAP1);
2、 创建兼容DC
CDC dcCompatible;
dcCompatible.CreateCompatibleDC(pDC);
3、 将位图选到兼容DC中
dcCompatible.SelectObject(&bitmap);
4、 将兼容DC中的位图贴到当前DC中。在WM_EraseBkgnd()中调用,但不能再调用基类的擦除背景函数。也可以在OnDraw函数中完成,但效率低,图像会闪烁,因为它先擦除背景,慢。
pDC->BitBlt(rect.left,rect.top,rect.Width(),
rect.Height(),&dcCompatible,0,0,SRCCOPY);
1. 创建4个菜单,为其添加消息响应,用成员变量保存绘画类型。添加LButtonDown和Up消息。
2. 当窗口重绘时,如果想再显示原先画的数据,则需要保存数据。为此创建一个新类来记录绘画类型和两个点。
class CGraph
{
public:
CPoint m_ptOrigin;//起点
CPoint m_ptEnd;//终点
UINT m_nDrawType;//绘画类型
CGraph();
CGraph(UINT m_nDrawType,CPoint m_ptOrigin,CPoint m_ptEnd);//此为构造函数。
virtual ~CGraph();
};
然后在void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point)中加入如下代码
//CGraph graph(m_nDrawType,m_ptOrigin,point);//不能用局部变量
//m_ptrArray.Add(&graph);//加入这种指针数组中
/* OnPrepareDC(&dc);//这个函数中可以重新设置窗口原点,对于滚动条中,保存数据前要调用此函数
dc.DPtoLP(&m_ptOrigin);//将设备坐标转换为逻辑坐标
dc.DPtoLP(&point);//
CGraph *pGraph=new CGraph(m_nDrawType,m_ptOrigin,point);//在堆中创建新的对象
m_ptrArray.Add(pGraph);*///加入到指针数组中
在GraphicView.h中有如下代码
CPtrArray m_ptrArray;
//在OnDraw中重画时调出数据
for(int i=0;i<m_ptrArray.GetSize();i++)
3. 在CView::OnPaint()调用了OnDraw(),但在void CGraphicView::OnPaint()中MFC的Wizard没有调用OnDraw(),要注意这个区别。如果你此时想调用,必须手动添加代码。 OnDraw(&dc);
4. 让窗口具有滚动条的功能。
第1.将CGraphicView的头文件中的CView全部替换成CSrollView
第2.添加如下的代码
void CGraphicView::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
// TOD Add your specialized code here and/or call the base class
SetScrollSizes(MM_TEXT,CSize(800,600));//设置映射模式,设定窗口大小。OK!
}
5. 坐标系的转换,此处不再详细介绍,需要时请查阅相关资料。
6. 解决重绘时线跑到上面的问题。为什么会错位?因为逻辑坐标和设备坐标没有对应起来。解决方法:
在OnLButtonDown画完图后,保存之前。调用
/* OnPrepareDC(&dc);//重新设置逻辑坐标的原点!!!
dc.DPtoLP(&m_ptOrigin);//设备坐标转化为逻辑坐标
dc.DPtoLP(&point);
CGraph *pGraph=new CGraph(m_nDrawType,m_ptOrigin,point);
m_ptrArray.Add(pGraph);*/
7. 另外两种方法来保存数据。一种是用CMetaFileDC,另一种是利用兼容DC,重绘时利用 pDC->BitBlt(0,0,rect.Width(),rect.Height(),&m_dcCompatible,0,0,SRCCOPY);将兼容DC的图拷贝到屏幕DC上去。
1. 常量指针与指针常量的区分
char ch[5]="lisi";
const char *pStr=ch;//const在*之前,表明指针指向的内容为常量,即为常量指针,但指针可指向其它变量。
char * const pStr=ch;//const在*之后,表明指针的地址不能改变,即为指针常量,但指针所指向的内容是可以改变的;
const char * const pStr = ch;指向常量的常量指针,指针的地址与指向的内容都不可以改变。
2. 对文件读写的三种方法
<1>.C语言
FILE *pFile=fopen("1.txt","w");//参数1文件路径,只写文件则在本工程中,参数2:打开模式
fwrite("http://www.sunxin.org",1,strlen("http://www.sunxin.org"),pFile);//写文件
原型:size_t fwrite(const void *buffer, size_t size, size_t count, FILE *stream);
在写完文件后要使用fflush(pFile);或fclose(pFile)使数据写入到文件中,因为C语言对文
件的操作使用了缓冲文件系统,一般如果不手工刷新缓冲区的话,直到缓冲区满后才将数据写入到文件中
int fseek(FILE *stream, long offset, int origin);
stream 指向FILE结构体的指针
offset 设定偏移量
origin 指定文件指针的起始位置.( SEEK_SET开始处, SEEK_CUR文件当前位置处, SEEK_END文件的结尾处)
//fseek(pFile,0,SEEK_SET);
对于C语言文件操作来说 ,它有一个文件指针,该指针会随时根据我们对文件的操作来移动地,始终指向文件下一个写入的位置.当执行定稿操作之后,文件指针就指向了所写数据占据位置的下一个位置.如果希望在写入数据后,返回到文件的开始位置处再写入数据,就需要将这个文件指针移动到文件开始校园,这可以利用 fseek函数实现
//fwrite("ftp:",1,strlen("ftp:"),pFile);
//fwrite("http://www.sunxin.org",1,strlen("http://www.sunxin.org"),pFile);
char ch[100];接收文件中数据字符数据
memset(ch, 0, strlen(ch));
fread(ch, 1, 100, pFile);
fclose(pFile);*/关闭文件
//fflush(pFile);刷新缓冲区
<2>.C++中
要包括头文件 "fstream.h"
/* ofstream ofs("4.txt");
ofs.write("http://www.sunxin.org",strlen("http://www.sunxin.org"));
ofs.close();*/
要注意的是:在读取文件时候ROF表示文件结尾,,readnext会将文件指针指向文件中下一个字符
<3<.MFC中 用CFile类
CFileDialog fileDlg(FALSE);
fileDlg.m_ofn.lpstrTitle="我的文件保存对话框";
fileDlg.m_ofn.lpstrFilter="Text Files(*.txt)\0*.txt\0All Files(*.*)\0*.*\0\0";
fileDlg.m_ofn.lpstrDefExt="txt";
if(IDOK==fileDlg.DoModal())
{
CFile file(fileDlg.GetFileName(),CFile::modeCreate | CFile::modeWrite);
file.Write("http://www.sunxin.org",strlen("http://www.sunxin.org"));
file.Close();
}
二进制文件和文本文件,实际上它们都是以二进制数据的方式存储的,文件只是计算机内在中以二进制表示的数据在外部存储介质上的另一种存放形式,对于文本文件来,它只是一种特殊形式的文件,它所存放的第一个字节都可以转换为一个可读的字符
注意:写入和读取文件操作的方式要一致,文本方式写入就用文本方式读取,二进制方式写入就用二进制方式读取.
面试题:给你一个整数,如:98341,将这个整数保存到文件中,要求在以记事本程序打开该文件时,显示是:98341,<要注意字符与整型是通用的,在文件中字符也是以ASCII保存的)
FILE *pFile = fopen(“3.txt”, “w”);
char ch[5];
ch[0] = 9 + 48;
ch[1] = 8 + 48;
ch[2] = 3 + 48;
ch[3] = 4 + 48;
ch[4] = 1 + 48;//0对应的ASCII是48,
fwrite(ch, 1, 5, pFile);//以这种方式写入,在记事本打开时就是98341
fclose(pFile);
4.利用win32 API函数 CreateFile(),及WriteFile()
CreateFile函数将创建或打开下列对象,并返回一个用于读取该对象的句柄:
文件, 管道, 邮槽(在线程通信时会用到), 通信资源, 磁盘设备, 控件台,目录(仅适用于打开操作)
Example: void CLesson12View::OnFileWrite() { // TODO: Add your command handler code here /*FILE *pFile = fopen("1.txt", "w"); fwrite("C语言文件操作", 1, strlen("C语言文件操作"), pFile); fclose(pFile); */ /*CFile file; file.Open("2.txt", CFile::modeCreate | CFile::modeWrite); file.Write("MFC文件操作", strlen("MFC文件操作")); file.Close();*/
/*CFileDialog m_fileDlg(FALSE); m_fileDlg.m_ofn.lpstrDefExt = "txt"; m_fileDlg.m_ofn.lpstrTitle ="保存我的文件"; m_fileDlg.m_ofn.lpstrFilter = "Text File(*.txt)\0*.txt\0All Files(*.*)\0*.*\0\0"; if(IDOK == m_fileDlg.DoModal()) { CFile mFile; mFile.Open(m_fileDlg.GetPathName(), CFile::modeCreate | CFile::modeWrite); mFile.Write("test file write!", strlen("test file write!")); mFile.Close(); }*/ //定义一个句柄变量 HANDLE hFile; //创建文件 hFile = CreateFile("5.txt", GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); //接收实际写入的字节数据 DWORD dwWrites; //写入数据 WriteFile(hFile, "example", strlen("example"), &dwWrites, NULL); //关闭文件句柄 CloseHandle(hFile); } |
4.注册表读写
1.对win.ini的读写
//::WriteProfileString("http://www.sunxin.org","admin","zhangsan");
/* CString str;
::GetProfileString("http://www.sunxin.org","admin","lisi",
str.GetBuffer(100),100);
AfxMessageBox(str);*/
2.注册表的读写
HKEY hKey;
DWORD dwAge=30;
RegCreateKey(HKEY_LOCAL_MACHINE,"Software\\http://www.sunxin.org\\admin",&hKey);
RegSetValue(hKey,NULL,REG_SZ,"zhangsan",strlen("zhangsan"));
RegSetValueEx(hKey,"age",0,REG_DWORD,(CONST BYTE*)&dwAge,4);
RegCloseKey(hKey);以上是写入
Example: void CLesson12View::OnRegWrite() { // TODO: Add your command handler code here //注册表操作 //RegCreateKey() //RegCloseKey()如果不需要以上找到注册表项 HKEY mKey; DWORD dAge = 30; ::RegCreateKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\www.sunxin.org\\admin", &mKey); ::RegSetValue(mKey, NULL, REG_SZ, "zhangsan", strlen("zhangsan")); ::RegSetValueEx(mKey, "age", 0, REG_DWORD, (CONST BYTE*)&dAge, 4); ::RegCloseKey(mKey);//在不使用的时候要调用这个函数 }
void CLesson12View::OnRegRead() { // TODO: Add your command handler code here /*LONG lValue; RegQueryValue(HKEY_LOCAL_MACHINE, "SOFTWARE\\www.sunxin.org\\admin", NULL, &lValue); char *ch = new char[lValue]; RegQueryValue(HKEY_LOCAL_MACHINE, "SOFTWARE\\www.sunxin.org\\admin", ch, &lValue); MessageBox(ch);*/
HKEY hKey; ::RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\www.sunxin.org\\admin", &hKey); DWORD dwType; DWORD dwValue; DWORD dwAge; ::RegQueryValueEx(hKey, "age", 0, &dwType, (LPBYTE)&dwAge, &dwValue); CString str; str.Format("Age = %d", dwAge); MessageBox(str); } |
1. CArchive在菜单打开保存时的代码
CFile file("1.txt",CFile::modeCreate | CFile::modeWrite);
CArchive ar(&file,CArchive::store);
int i=4;
char ch='a';
float f=1.3f;
CString str("http://www.sunxin.org");
ar<<i<<ch<<f<<str;以上是保存,打开略
2. 文档-视类结构简介
OnNewDocument在程序启动时被调用,此时可设置文档标题,也可以在String Table的IDR_MAINFRAME的第二个"\"后改变文档的标题。须了解的7个字符串的用途,见PPT。
在WinAPP的InitInstance()中完成DOC,View,MainFrame的归一。
当点击系统的打开和新建菜单时,有一系列的步骤,孙鑫老师给我们跟踪了代码的调用过程,此段跟踪我们略过。但我们要牢记住:CWinAPP负责管理文档管理器,文档管理器有一个指针链表,且来保存文档模板的指针,文档模板指针管理三个类DOC,VIEW,MAINFRAME,使其为某文件对象服务。
3. 利用CArchive来保存一个类的对象,此类必须支持串行化,需要5个步骤。
a.让类从CObject派生;
b.覆盖Serialize()函数,在其中完成保存和读取功能;
c.在.h中加入 DECLARE_SERIAL(CGraph);
d.在。cpp中加入IMPLEMENT_SERIAL(CGraph, CObject, 1 );
e.定义一个不带参数的构造函数。
保存绘画数据到文件的简单过程
a.在CGraph中增加一个画图的成员函数,其实不增加也行。可以在View中完成相应功能。
b.增加四个画图菜单,菜单可以从11课的代码中拷贝。
c.在View中增加LButtonDown和UP的响应,在UP中画图,在DOWN中保存点
d.利用CObArray集合类来保存绘画数据
e.在CGraphicDOC::Serialize()中保存和读取数据
f.然后在OnDraw中重绘。
4. 新建和打开文档时,要注意销毁原来的数据。在DOC的DeleteContents虚函数中是好时机。代码如下
Example: int nCount; nCount=m_obArray.GetSize(); /*for(int i=0;i<nCount;i++) { delete m_obArray.GetAt(i);//释放指针指向的内存空间 //m_obArray.RemoveAt(i);//移除链表中的元素。但在此处不能这样用,会导致非法操作。要用下面的方法沙 } m_obArray.RemoveAll();*/ while(nCount--) { delete m_obArray.GetAt(nCount); m_obArray.RemoveAt(nCount); } |
sockets(套接字)编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW);基于TCP的socket编程是采用的流式套接字(SOCK_STREAM)。基于UDP采用的数据报套接字(SOCK_DGRAM).
1.TCP流式套接字的编程步骤
在使用之前须链接库函数:工程->设置->Link->输入ws2_32.lib,OK!
服务器端程序:
1、加载套接字库
2、创建套接字(socket)。
3、将套接字绑定到一个本地地址和端口上(bind)。
4、将套接字设为监听模式,准备接收客户请求(listen)。
5、等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)。
6、用返回的套接字和客户端进行通信(send/recv)。
7、返回,等待另一客户请求。
8、关闭套接字。
客户端程序:
1、加载套接字库
2、创建套接字(socket)。
3、向服务器发出连接请求(connect)。
4、和服务器端进行通信(send/recv)。
5、关闭套接字。
(TCP)服务器端代码如下:
#include <winsock2.h> #include <stdio.h> void main() { WORD wVersionRequested;//版本号 WSADATA wsaData; int err; wVersionRequested = MAKEWORD(1, 1);//1.1版本的套接字
err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { return; }//加载套接字库,如果失败返回0 if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) { return; }//判断高低字节是不是1,如果不是1.1的版本则退出
//创建流式套接字,基于TCP(SOCK_STREAM) SOCKET socSrv = socket(AF_INET, SOCK_STREAM, 0); //Socket地址结构体的创建 SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//转换Unsigned long型为网络字节序格式 addrSrv.sin_family = AF_INET;//指定地址簇 addrSrv.sin_port = htons(6000); //指定端口号,除sin_family参数外,其它参数都是网络字节序,因此需要转换
//将套接字绑定到一个端口号和本地地址上 bind(socSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
listen(socSrv, 5); SOCKADDR_IN addrClient;//字义用来接收客户端Socket的结构体 int len = sizeof(SOCKADDR);//初始化参数,这个参数必须进行初始化
//循环等待接受客户端发送请求 while (1) { //等待客户请求到来;当请求到来后,接受连接请求, //返回一个新的对应于此次连接的套接字(accept)。 //此时程序在此发生阻塞 SOCKET sockConn = accept(socSrv, (SOCKADDR*)&addrClient, &len);
char sendBuf[100]; sprintf(sendBuf, "Welcome %s to http://sunxin.org", inet_ntoa(addrClient.sin_addr));//格式化输出 //用返回的套接字和客户端进行通信 send(sockConn, sendBuf, strlen(sendBuf)+1, 0);//多发送一个字节
//接收数据 char recvBuf[100]; recv(sockConn, recvBuf, 100, 0); printf("%s\n", recvBuf); closesocket(sockConn); } } |
(TCP)客户端代码如下:
#include <winsock2.h> #include <stdio.h> void main() { WORD wVersionRequested;//版本号 WSADATA wsaData; int err; wVersionRequested = MAKEWORD(1, 1);//1.1版本的套接字
err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { return; }//加载套接字库,如果失败返回0 if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) { return; }//判断高低字节是不是1,如果不是1.1的版本则退出 //创建流式套接字,基于TCP(SOCK_STREAM) SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0); //Socket地址结构体的创建 SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//转换字符型为网络字节序格式 addrSrv.sin_family = AF_INET;//指定地址簇 addrSrv.sin_port = htons(6000); //指定端口号,除sin_family参数外,其它参数都是网络字节序,因此需要转换
connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); char recvBuf[100];//和服务器端进行通信(send/recv)。 recv(sockClient,recvBuf,100,0); printf("%s\n",recvBuf); send(sockClient,"This is lisi",strlen("This is lisi")+1,0);
closesocket(sockClient);//关闭套接字。 WSACleanup();//必须调用这个函数清除参数 } |
2.UDP型套接字。
服务器端(接收端)程序:
1、创建套接字(socket)。
2、将套接字绑定到一个本地地址和端口上(bind)。
3、等待接收数据(recvfrom)。
4、关闭套接字。
客户端(发送端)程序:
1、创建套接字(socket)。
2、向服务器发送数据(sendto)。
3、关闭套接字。
(UDP)服务器端代码:
#include <winsock2.h> #include <stdio.h>
//基于UDP开支套接字服务器程序 void main(){ WORD wVersionRequested; WSADATA wsaData; int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return; }
if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; }
SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);//创建套字(socket) SOCKADDR_IN addSrv; addSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); addSrv.sin_family = AF_INET; addSrv.sin_port = htons(6000); bind(sockSrv, (SOCKADDR*)&addSrv, sizeof(SOCKADDR));
SOCKADDR_IN addrClient; int len = sizeof(SOCKADDR); char recvBuf[100];
recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrClient, &len); printf("%s\n", recvBuf); closesocket(sockSrv); WSACleanup();
} |
(UDP)客户端代码:
#include <winsock2.h> #include <stdio.h>
void main(){ WORD wVersionRequested; WSADATA wsaData; int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return; }
if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; }
SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(6000);
sendto(sockClient, "Hello Server!", sizeof("Hello Server!") + 1, 0, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); closesocket(sockClient); WSACleanup(); } |
===========下面是字符界面下的一个简单UDP聊天程序=====
UDP聊天程序服务器端:==============================
#include <winsock2.h> #include <stdio.h>
//=========基于UDP聊天程序===== //服务器端 void main(){ WORD wVersionRequested; WSADATA wsaData; int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return; } if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; }
SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(6000);
bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
char sendBuf[100]; char recvBuf[100]; char tempBuf[100];
int len = sizeof(SOCKADDR);
SOCKADDR_IN addrClient; while (1) { recvfrom(sockSrv, tempBuf, sizeof(tempBuf), 0, (SOCKADDR*)&addrClient, &len); if ('q' != tempBuf[0]) { sprintf(recvBuf, "%s say: %s", inet_ntoa(addrClient.sin_addr), tempBuf); printf("%s\n", recvBuf); printf("please input your data:"); gets(sendBuf); sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrClient, len); } else { printf("%s request to quit the chat platform", inet_ntoa(addrClient.sin_addr)); sendto(sockSrv, "q", strlen("q") + 1, 0, (SOCKADDR*)&addrClient, len); break; }
} closesocket(sockSrv); WSACleanup(); } |
UDP聊天程序客户端:===============================
#include <WINSOCK2.H> #include <stdio.h>
void main(){ WORD wVersionRequested; WSADATA wsaData; int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return; } if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; }
SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(6000);
char sendBuf[100]; char tempBuf[100]; char recvBuf[100];
int len = sizeof(SOCKADDR); while(1){ printf("please input your data:\n"); gets(sendBuf); sendto(sockClient, sendBuf, sizeof(sendBuf) + 1, 0, (SOCKADDR*)&addrSrv, len); recvfrom(sockClient, tempBuf, 100, 0, (SOCKADDR*)&addrSrv, &len);
if ('q' != tempBuf[0]) { sprintf(recvBuf, "%s say: %s", inet_ntoa(addrSrv.sin_addr), tempBuf); printf("%s\n", recvBuf); } else{ printf("the server has been closed!\n"); sendto(sockClient, "q", strlen("q") + 1, 0, (SOCKADDR*)&addrSrv, len); break; }
} closesocket(sockClient); WSACleanup(); } |
1. 程序,进程,线程
A: 程序是计算机指令的集合,它以文件的形式存储在磁盘上,而进程通常被定义为一个正在运行的程序的实例,是一个程序在其自身的地址空间中的一次执行活动.一个程序可以对应多个进程.
进程是资源申请,高度和独立运行的单位,因此,它使用系统中的运行资源,而程序不能申请系统资源,不能被系统高度也不能作为独立运行的单位,因此它不占系统运行资源.
进程组成:
<1> 操作系统用来管理进行的内核对象
内核对象也是系统用来存放关于进程的统计信息的地方.内核对象是操作系统内部分配的一个内在块,该内存块是一种数据结构,其成员负责维护该对象的各种信息.
<2> 地址空间
它包含所有可执行模块或DLL模块的代码和数据.另外,它也包含动态内存分配的空间,例如线程的栈和堆分配空间
B: 进程从来不执行任何东西,它只是纯种的容器,若要使进行完成某项操作,它必须拥有一个在它的环境中运行的纯种,此线程负责执行包含在进程的地址空间的中的代码.也就是,真正完成代码执行的是线程,而进行只是纯种的容器,或者说是线程的执行环境.
单个进程可能包含若干个纯种,这些线程都”同时”执行进行地址空间的中代码,每个线程至少拥有一个纯种,来执行进行的地址空间中的代码,当创建一个进程时,操作系统会自动创建这个进程的第一个线程,称为主纯种.也就是执行main函数或WinMain函数的线程,可以把main函数或WinMain函数看作是主线程的入口点函数.此后,主线程可以创建其它的线程.
C: 线程也由两部分组成:
<1> 线程内核对象,操作系统用它来对线程实施管理,内核对象也是系统用来存放线程统计信息的地地方
<2>线程栈stack:它用于维护线程在执行代码时需要的所有函数参数和局部变量.线程总是在某个进程环境中创建,系统从进程的地址空间中分配内存,供线程的栈使用.
线程运行:
操作系统为每一个运行线程安排一定的CPU时间—时间片,系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,因时间片相当短,因此给用户的感觉就是好像多个线程是同时运行一样.
2. 创建线程的SDK函数
HANDLE CreateThread (
SEC_ATTRS SecurityAttributes,//安全结构全指针,NULL为默认安全性
ULONG StackSize,//线程初始栈的大小 ,以字节为单位,如果为0或小于0,默认将使用
//与调用该线程相同的栈空间大小
SEC_THREAD_START StartFunction,//线程处理函数地址(可用函数名)
PVOID ThreadParameter,//可以为数据或其它信息的指针,表示给新线程传送参数
ULONG CreationFlags,//线程创建的标记,可以为0或CREATE_SUSPENDED,如果
//CREATE_SUSPENDED线程创建后暂停状态,直到程序调用//ResumeThread,如果为0立即运行
PULONG ThreadId //[out]返回值,系统分配新的线程ID
);
3. 一个简单的多线程程序,模拟售票系统
#include <windows.h>//必要的头文件,使用Windows API函数 #include <iostream.h>
int index = 0; int tickets = 100;//票数 HANDLE hMutex; //使用全局的互斥对象来保证对同一资源的互斥访问与操作这里是tickets //线程处理函数原型,形式可从MSDN中拷贝 //线程1 的入口函数 DWORD WINAPI Fun1Proc( LPVOID lpParameter // thread data ); DWORD WINAPI Fun2Proc( LPVOID lpParameter // thread data );
void main(){ HANDLE hThread1; DWORD thread1ID; //创建线程1 hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, &thread1ID); HANDLE hThread2; DWORD thread2ID; //创建线程2 hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, &thread2ID); CloseHandle(hThread1); //关闭线程的句柄,为什么要关闭?它将线程的使用计数减1 CloseHandle(hThread2);//这样当线程结束时,线程内核对象被释放, //否则只有当进程结束,才释放线程的内核对象hThread1与hThread2 //创建一个互斥对象,如果成功返回互斥对象的句柄,否则返回NULL hMutex = CreateMutex(NULL, FALSE, "tickets"); if (hMutex) { if(ERROR_ALREADY_EXISTS == GetLastError()) { cout << "only one instance can run!" << endl; return; } } // while(index++ < 100) // { // cout << "main Thread is running!" << endl; // } Sleep(4000);//主线程睡眠4秒钟,给其它线程运行的时间,因为一旦主线程退出则进行退出,其它线程也将退出 }
DWORD WINAPI Fun1Proc( LPVOID lpParameter // thread data ){ // while(index++ < 100) // cout << "Thread1 is running!" + index << endl;
while(TRUE){ WaitForSingleObject(hMutex, INFINITE);//如果全局互斥对象是有信号状态,则获得该对象, //直到调用ReleaseMutex之前,互斥对象是无信号状态,其它线程不能对互斥对象进行访问 if(tickets > 0) { Sleep(1); cout << "Thread1 sell tickets : " << tickets-- << endl; } else break; ReleaseMutex(hMutex);//将互斥对象设置为有信号状态 } return 0; }
DWORD WINAPI Fun2Proc( LPVOID lpParameter // thread data ) { while(TRUE){ WaitForSingleObject(hMutex, INFINITE); if (tickets > 0) { Sleep(1); cout << "Thread2 sell tickets : " << tickets-- << endl; } else break; ReleaseMutex(hMutex); } return 0; } |
3. 多线程聊天程序
(1) 加载套接字库在InitInstance()中,调用AfxSocketInit(),此时可以不加载库文件,但要加入Afxsock.h"头文件
if (!AfxSocketInit())
{
AfxMessageBox("加载套接字库失败!");
return FALSE;
}
(2) 在CChatDlg.h中类的声明外,创建一个全局的结构体,包含套接字和窗口的句柄值,主要是在投送消息时可以将两个需要传送的消息同时发送
struct RECVPARAM{
SOCKET socket;
HWND hWnd;
};
(3) 在CChatDlg中创建成员变量m_socket,然后增加一个成员函数,IniSocket(),在其中完成m_socket的初始化和绑定。
BOOL CChatDlg::InitSocket() { m_socket = socket(AF_INET, SOCK_DGRAM, 0); if (INVALID_SOCKET == m_socket) { MessageBox("创建套接字失败!"); return FALSE; } SOCKADDR_IN addrSock; addrSock.sin_addr.S_un.S_addr = htonl(INADDR_ANY); addrSock.sin_family = AF_INET; addrSock.sin_port = htons(6010);
int bindRst; bindRst = bind(m_socket, (SOCKADDR*)&addrSock, sizeof(SOCKADDR)); if (SOCKET_ERROR == bindRst) { closesocket(m_socket); MessageBox("绑定失败!"); return FALSE; } return TRUE; } |
(4) .创建一个线程,CreateThread(),须将线程函数RecvProc定义为静态的或者全局函数。因为对于运行的代码来说,它不知道要产生哪一个对象,即运行时根本不知道如何去产生一个CChatDialog类的对象,对于运行时代码来说,如果要调用纯种函数来启动某个纯种的话,应该不需要产生某个对象就可以调用这个纯种函数.因此要定义为类的静态成员或全局函数,即这个代码是所以对象共有的,不需要定义对象才能使用.
static DWORD WINAPI RecvProc(LPVOID lpParameter);
DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter) { //根据线程函数参数获取发送端套接字和要设置包含文本控件的窗口句柄 SOCKET socket = ((RECVPARAM*)lpParameter)->socket; HWND hWnd = ((RECVPARAM*)lpParameter)->hWnd;//窗口句柄
SOCKADDR_IN addrRecv; int len = sizeof(SOCKADDR); char recvBuf[100]; char tempBuf[100]; int recvRst; CString recvStr; while(TRUE) { //接收数据 recvRst = recvfrom(socket, recvBuf, 100, 0, (SOCKADDR*)&addrRecv, &len); if (SOCKET_ERROR == recvRst) { break; } sprintf(tempBuf, "%s说:%s\r\n", inet_ntoa(addrRecv.sin_addr), recvBuf); // recvStr += tempBuf;//这种方式更加简单不用使用消息 // ::SetDlgItemText(hWnd, IDC_EDIT_RECV, recvStr); //使用自定义消息的方式来对控件填充内容 ::PostMessage(hWnd, WM_RECVDATA, 0, (LPARAM)tempBuf); } return 0; } |
在OnInitDialog中调用InitSocket完成初始化工作。
InitSocket();//初始化套接字库 RECVPARAM *pRecvParm = new RECVPARAM(); pRecvParm->socket = m_socket; pRecvParm->hWnd = m_hWnd;
HANDLE hThreadRecv = CreateThread(NULL, 0, RecvProc, (LPVOID)pRecvParm, 0, NULL); CloseHandle(hThreadRecv); |
(5) ::PostMessage()完成将收到的数据发送给对话框。用自定义的消息参考下面的代码。注意要将EDitBox的MultiLine属性选上。
<1>: 在ChatDlg.h