MFC教程(Visual C++ 6.0)|合集 |更新中

文章目录

前言

C++语言是最主要的软件开发语言,几乎所有的计算机软件、手机软件以及嵌入式软件等都是使用C++语言进行开发的。在Windows桌面上的全部软件几乎都是使用C++语言开发的,例如,QQ、360、迅雷、各种Office办公软件以及各种网络游戏和播放器软件等。
Visual C++ 简称VC或VC++,是由微软提供的C++语言开发工具,它是一个编译器而且是一个集成开发环境包括编辑器、调试器和编译器等。除了VC之外还有gcc也是C++语言编译器,VC主要用于开发Windows桌面软件;gcc主要针对UNIX和Linux操作系统,开发嵌入式软件和手机软件等。
MFC(Microsoft Foundation Classes,微软基础类库)是微软专门封装的用于Windows平台开发的类库。MFC内部全部使用C++语言,分类封装Windows API和Windows SDK(Software Development Kit,软件开发工具包)中的结构和功能。MFC还提供了一个应用程序框架,例如,应用程序向导和类向导自动生成的代码,大大减少了软件开发者的工作量,提高了开发效率。

第1章 Visual C++ 6.0的安装和使用

第1节安装 Visual C++ 6.0

下载链接:
在这里插入图片描述

  1. 选择“英文版”或者“中文版”;
    在这里插入图片描述
  2. 单击“next”按钮;在这里插入图片描述
  3. 选择“I accept the agreement”,单击“next”按钮;在这里插入图片描述
  4. 随便输入“Your name”,单击“next”按钮;在这里插入图片描述
  5. 单击“next”按钮;在这里插入图片描述
  6. 单击“next”按钮;
    在这里插入图片描述
  7. 单击“继续”按钮;
    在这里插入图片描述
    在这里插入图片描述
  8. 单击“确定”按钮;
    在这里插入图片描述
  9. 选择“typical”;
    在这里插入图片描述
  10. 把勾划掉,单击“OK”按钮;
    在这里插入图片描述
  11. 安装完毕后,继续安装MSDN。

第2节 安装MSDN for Visual C++ 6.0

第3节 开始使用Visual C++ 6.0

新建一个控制台程序
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

// 001C.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <stdio.h>

int main(int argc, char* argv[])
{
	printf("这是我的第一个C语言程序!\n");
	return 0;
}

在这里插入图片描述
默认位置C:\Program Files\Microsoft Visual Studio\MyProjects\001C

单击 build,或者F7 编译;
执行;


进入Windows控制台窗口在这里插入图片描述
C:\Program Files\Microsoft Visual Studio\MyProjects\001C\Debug

然后输入001C;

第4节 Visual C++ 6.0开发环境简介

在这里插入图片描述

第5节 Visual C++ 6.0调试环境介绍

进入调试状态

  1. 直接按<F10>或者<F11>,进入main函数开始单步执行程序;
  2. 按在光标所在行设置一个断点,然后按让程序直接运行到断点处再单步执行;
  3. 把光标停放在某一行,然后按<Ctrl+F10>让程序直接运行到光标处再单步执行。

调试窗口

  1. 手动变量观察窗口(Watch)在这里插入图片描述
  2. 自动变量观察窗口(Variables)在这里插入图片描述
  3. Debug工具栏
    在这里插入图片描述
    Debug工具栏中显示的都是重要的调试功能,调试快捷键及功能
    在这里插入图片描述

第2章 Windows编程基础

使用Win32和MFC两种方式讲解简单Windows软件开发的方法。Win32程序开发包括Win32程序编写、图标资源加载和对话框资源加载等。介绍Windows开发中常用的数据类型,包括Windows数据类型和MFC封装的数据类型。

第1节 第一个Win32软件

  1. 执行File->New命令,新建工程;
  2. 在左侧列表中选择“Win32 Application”,选择目录,最后在“Project Name”输入工程名;
    在这里插入图片描述
  3. 单击“OK”进入程序向导,选中“A simple Win32 application”,单击“finish”完成;
  4. 双击“Globals”下面的“WinMain”函数,输入一个简单的输出语句;
    在这里插入图片描述
// First32A.cpp : Defines the entry point for the application.
//

#include "stdafx.h"

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
 	// TODO: Place code here.
	MessageBox(NULL,"这是我开发第一个Windows软件","温馨提示",MB_OK);
	return 0;
}
  1. build或按<F7>;
  2. Debug目录下生成了可执行文件First32.exe
    在这里插入图片描述
  3. 修改
// First32A.cpp : Defines the entry point for the application.
//

#include "stdafx.h"

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
 	// TODO: Place code here.
	MessageBox(NULL,"这是我开发第一个Windows软件","温馨提示",MB_OK|MB_ICONINFORMATION);
	return 0;
}

在这里插入图片描述

第2节 Win32程序资源管理

打开第1节建立的First32工程,添加Windows资源

  1. File->New,添加资源;
  2. 选中Resource Scrip,填写名称,与工程名相同;
    在这里插入图片描述
  3. 单击OK
  4. ResourceView展开根节点是时,出现提示框,关闭First32.rc再展开;在这里插入图片描述
  5. 在ResourceView根节点上单击鼠标右键,选择“Insert”;
  6. 选中Icon,再单击Next;
  7. 随意勾图案;
  8. <F7>编译生成可执行文件,这时候应用程序就有一个自己的图标了。

第3节 基于对话框的Win32程序

本节开发基于对话框的Win32程序,输入输出功能。

  1. 在ResourceView根节点上单击鼠标右键,选择“Insert”;
  2. 选中Dialog,单击New;
  3. 在对话框资源上单击右键,选择Properties; 在这里插入图片描述
  4. 分别设置字体、ID、Caption;
    在这里插入图片描述
  5. 添加控件Edit和Static Text;
    在这里插入图片描述
    按住 ctrl+D。可以使用Tap键;
  6. 属性设置;
    Edit的ID分别为IDC_LEFT,IDC_RIGHT、IDC_RESULT;
    Static Text的Caption分别为+、-;
  7. 计算、关闭;
  8. 在ClassView中双击WinMain函数修改代码;
// First32.cpp : Defines the entry point for the application.
//
#include "stdafx.h"
#include "resource.h"
#include <stdio.h>

BOOL CALLBACK MainProc(
  HWND hwndDlg,  // handle to dialog box
  UINT uMsg,     // message
  WPARAM wParam, // first message parameter
  LPARAM lParam  // second message parameter
)
{
    if(WM_COMMAND==uMsg)
    {
    if(LOWORD(wParam)==IDCANCEL)
    {
        EndDialog(hwndDlg,IDCANCEL);
        return TRUE;
    }
    if(LOWORD(wParam)==IDOK)
    {
        //MessageBox(hwndDlg,"测试了计算","测试",0);
        int nLeft = GetDlgItemInt(hwndDlg,IDC_LEFT,NULL,TRUE);
        int nRight = GetDlgItemInt(hwndDlg,IDC_RIGHT,NULL,TRUE);
        int nResult = nLeft + nRight;
        SetDlgItemInt(hwndDlg,IDC_RESULT,nResult,TRUE);
    }     
    }

    return FALSE;
}


int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
    DialogBox(hInstance,(LPCSTR)IDD_FIRSTDLG,NULL,MainProc);
    return 0;
}

或者

// First32.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "resource.h"
#include <stdio.h>


BOOL CALLBACK MainProc(
  HWND hwndDlg,  // handle to dialog box
  UINT uMsg,     // message
  WPARAM wParam, // first message parameter
  LPARAM lParam  // second message parameter
)

{
	switch(uMsg)
	{
	case WM_COMMAND:   //判断消息类型
		switch(wParam)
		{
		case IDCANCEL:  //判断单击的按钮
			EndDialog(hwndDlg,IDCANCEL);
			break;
		case IDOK:  //判断单击的按钮
			{
				int nLeft = GetDlgItemInt(hwndDlg,IDC_LEFT,NULL,TRUE);
				int nRight = GetDlgItemInt(hwndDlg,IDC_RIGHT,NULL,TRUE);
				int nResult = nLeft + nRight;
				SetDlgItemInt(hwndDlg,IDC_RESULT,nResult,TRUE);
			}
			break;
		}
		break;
	}
	return FALSE;
}


int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
    DialogBox(hInstance,(LPCSTR)IDD_FIRSTDLG,NULL,MainProc);
    return 0;
}

  1. 查看MSDN中关于DialogBox弹出对话框的函数说明;
DialogBox
The DialogBox macro creates a modal dialog box from a dialog box template resource. DialogBox does not return control until the specified callback function terminates the modal dialog box by calling the EndDialog function. The DialogBox macro uses the DialogBoxParam function.

int DialogBox(
  HINSTANCE hInstance,  // handle to application instance
  LPCTSTR lpTemplate,   // identifies dialog box template
  HWND hWndParent,      // handle to owner window
  DLGPROC lpDialogFunc  // pointer to dialog box procedure
);

1)第一个参数hInstance:相当于应用程序实例,由WinMain函数代入用于加载进程内的资源;
2)第二个参数lpTemplate:指定与对话框模版关联的资源ID,代入前要对数字ID强制类型转化;
3)第三个参数hWndParent:指定父窗口句柄,一般代入NULL,因为对话框作为主窗口没有父窗口;
4)最后一个参数lpDialogFunc:是一个指定格式的回调函数的地址,该函数用于处理各类窗口事件。

  1. 回调函数的参数列表和返回值必须按指定的格式编写;
DialogProc
The DialogProc function is an application-defined callback function used with the DialogBox function. It processes messages sent to a modal or modeless dialog box. The DLGPROC type defines a pointer to this callback function. DialogProc is a placeholder for the application-defined function name. 

BOOL CALLBACK DialogProc(
  HWND hwndDlg,  // handle to dialog box
  UINT uMsg,     // message
  WPARAM wParam, // first message parameter
  LPARAM lParam  // second message parameter
);
 1)第一个参数hwndDlg:与该回调函数关联的对话框句柄,用户操作对话框;
 2)第二个参数uMsg:消息号码,用于解析对话框窗口发生的事件,例如,单击按钮;
 3)最后2个参数wParam和lParam,是窗口消息的相关数据,例如,单击按钮的ID。

第4节 Windows数据类型

第5节 初步使用MFC

基于MFC架构平台的对话框程序

  1. 执行File->New命令,选择MFC AppWizard(exe),在Project name 填写工程名FirstMFC;
    在这里插入图片描述
  2. 进入MFC应用程序向导,选中Dialog based 基本对话框;在这里插入图片描述
  3. 第二步不做修改;
    在这里插入图片描述
    选中As a statically linked library作为静态的DLL,finish;
    在这里插入图片描述
  4. 完成工程创建后按F7编译;通过MFC应用程序向导创建的工程中,工作区(Workspace)自动带有资源视图(ResourceView)分页,其中已经自动生成了Dialog、Icon、Version以及StringTable等资源。
  5. 在主对话框资源中,设置对话框的标题(Caption)并添加一些控件;
    在这里插入图片描述
  6. 添加设置对话框所有控件的属性;
控件类型IDCaptionStyles
Static TextIDC_STATIC工号
Edit BoxIDC_NUMB
Static TextIDC_STATIC姓名
Edit BoxIDC_NAME
Static TextIDC_STATIC基本工资
Edit BoxIDC_SALA
List ControlIDC_LISTView:Report
ButtonIDC_ADD添加
ButtonIDC_DEL删除
ButtonIDC_MOD修改
  1. 分别双击 添加、删除、修改按钮,创建3个与按钮关联的成员函数;
  2. 在对话框关联的源文件FirstMFCDlg.cpp中,修改初始化函数代码;
BOOL CFirstMFCDlg::OnInitDialog()
{
	CDialog::OnInitDialog();
	CListCtrl* pList = (CListCtrl*)GetDlgItem(IDC_LIST);
	pList ->InsertColumn(0,"工号",LVCFMT_LEFT,100);
	pList ->InsertColumn(1,"姓名",0,100);
	pList ->InsertColumn(2,"工资",0,100);
//后面其余部分原来的代码不变
  1. 再修改3个按钮关联函数的代码;
void CFirstMFCDlg::OnAdd() 
{
	CString str;
	this ->GetDlgItemText(IDC_NUMB,str);
	CListCtrl* pList = (CListCtrl*)GetDlgItem(IDC_LIST);
	int nCount = pList ->GetItemCount();
	pList ->InsertItem(nCount,str);
	GetDlgItemText(IDC_NAME,str);
	pList ->SetItemText(nCount,1,str);
	GetDlgItemText(IDC_SALA,str);
	pList ->SetItemText(nCount,2,str);

}
/*

void CFirstMFCDlg::OnAdd() 
{
	CString szNumb,szName,szSala;
	GetDlgItemText(IDC_NUMB,szNumb);
	GetDlgItemText(IDC_NAME,szName);
	GetDlgItemText(IDC_SALA,szSala);
	CListCtrl* pList = (CListCtrl*)GetDlgItem(IDC_LIST);
	int nCount = pList ->GetItemCount();
	pList ->InsertItem(nCount,szNumb);
	pList ->SetItemText(nCount,1,szName);
	pList ->SetItemText(nCount,2,szSala);
}
*/

void CFirstMFCDlg::OnDel() 
{
	CListCtrl* pList = (CListCtrl*)GetDlgItem(IDC_LIST);

	//int nSel = pList ->GetSelectionMark();
	POSITION pos = pList ->GetFirstSelectedItemPosition();
	int nSel = pList ->GetNextSelectedItem(pos);
	if(nSel < 0)
	{
		AfxMessageBox("请先选中一行再删除!");
		return;
	}
	if(IDYES==AfxMessageBox("确认删除选中的数据吗?",MB_YESNO))
		pList ->DeleteItem(nSel);
}

void CFirstMFCDlg::OnMod() 
{
	CListCtrl* pList = (CListCtrl*)GetDlgItem(IDC_LIST);

	//int nSel = pList ->GetSelectionMark();
	POSITION pos = pList ->GetFirstSelectedItemPosition();
	int nSel = pList ->GetNextSelectedItem(pos);
	if(nSel < 0)
	{
		AfxMessageBox("请先选中一行再修改!");
		return;
	}

	CString str;
	GetDlgItemText(IDC_NUMB,str);
	if(IDNO==AfxMessageBox("确定要修改 " + str + " 的数据吗?",MB_YESNO))
		return;

	
	GetDlgItemText(IDC_NAME,str);
	pList ->SetItemText(nSel,1,str);
	GetDlgItemText(IDC_SALA,str);
	pList ->SetItemText(nSel,2,str);

/*
	CString szNumb,szName,szSala;
	GetDlgItemText(IDC_NUMB,szNumb);
	GetDlgItemText(IDC_NAME,szName);
	GetDlgItemText(IDC_SALA,szSala);
	pList ->InsertItem(nSel,szNumb);
	pList ->SetItemText(nSel,1,szName);
	pList ->SetItemText(nSel,2,szSala);
*/
}
  1. 编译运行;
  2. MFC的动态链接和静态链接。在使用MFC应用程序向导新建工程时,选择“As a statically linked library”选项编译器会把MFC类库代码编译到可执行文件内部;选择“As a shared DLL”使用系统内共享的MFC动态库。前者编译出来的可执行文件虽然较大,但是不需要系统提供动态库支持,更加安全。
  3. API和MFC的关系。API(Application Programming Interface,应用程序接口)是由操作系统提供给开发者的C语言格式的全局函数。GetDlgItemInt和SetDlgItemInt函数就是API函数。Win32是面向API的编程平台。MFC(Microsoft Fundation Class,微软的基础类库)这个类库分装了大部分Windows API,便于开发人员了解类库函数功能。

第6节对话框资源编辑

对话框和内部的每个控件都有自己的资源ID,ID是一些不重复的数字标志。

  1. 对话框资源自身的属性包括5个分页;
  2. General分页;ID、标题Caption、字体Font;
    在这里插入图片描述
    在这里插入图片描述
  3. Styles分页;
    在这里插入图片描述
    在这里插入图片描述
    1)Style:默认选择Popup弹出式对话框,在下拉列表中还包括Child风格用于设置子对话框;
    2)Border:默认选择DialogFrame,边框比较厚重而且固定窗口尺寸;若在下拉列表中选择Resizing可以通过鼠标拖动对话框边缘来改变窗口尺寸;选Thin则边框没有边缘,看起来比较薄(去掉Title bar属性后生效);选None则会强制去除Title bar和System menu属性,而且没有边框线;
    3)Title bar:用于设置对话框是否显示标题栏;
    4)System menu:设置标题栏上是否含有系统菜单;
    5)Minimize box:设置是否显示最小化按钮;
    6)Maximize box:设置是否显示最大化按钮;
  4. More Styles分页,这个分页有些属性只适合在非模式对话框中使用;
    在这里插入图片描述
    在这里插入图片描述
    Disable:使对话框内所所有控件都失效;
    Context help:在标题栏显示问好按钮;
    Center:对话框启动后在屏幕中央显示;
    Control:对话框启动后去掉标题栏;
  5. Extended Styles分页
    在这里插入图片描述
    在这里插入图片描述
    Tool windows:使标题栏和关闭按钮栏变小,并且在任务栏中不显示窗口图标;
    Client edge:深度下陷;Static edge浅度下陷;
    Accept files:允许从外部拖放一些文件放入对话框内,并在拖放后产生WM_DROPFILES消息;
    Context help:标题栏显示问好按钮,单击该按钮后单击窗口产生WM_HELP消息;
  6. 静态文本控件(Static Text)
    1)一般的静态文本控件的默认ID都是IDC_STATIC,开发时一般不需要修改默认ID即可;
    2)只有当需要通过代码操作静态文本时,才设置静态控件的ID;
    3)填写静态文本控件的Caption时,加入&符号可以使其后面的英文字母带有下划线;例如:工号(&N),按快捷键<alt+N>时焦点落在“工号”后面的编辑框内;
  7. 编辑控件(Edit Box)主要设置Style分页;
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    1)文本对其Align text;
    2)多行文字Multiline;
    3)自动横向滚动Auto HScroll;
    4)竖直滚动Vertical scroll;
    5)换行Want return;
    6)密码Password:*隐藏输入的文字;
    7)只读Read-only;
    8)数字Number;
    9)大些Uppercase;
    10)小写Lowercase:
  8. 列表控件List Ccontrol,主要设置Style分页;
    在这里插入图片描述
    在这里插入图片描述
    1)列表视图View:大图标Icon、小图标Small icon、简单列表List、详细列表Report;
    2)排序方式Sort:不排序None、由小到大排序Ascending、由大到小排序Descending;
    3)单行选取Single selection;
    4)编辑表哥Edit Labels;
    5)没有列表头No column header;
    6)不排序表头No sort header;
    7)一直显示选择项Show selection always;

第7节 MFC封装的数据类型

MFC封装的数据类型不是函数库而是类库,最常用的基本类型有字符串类(Cstring)、文件类(CFile)、时间类(CTime)等。用于几何空间的类有坐标点(CPoint)、空间尺寸(CSize)、矩形区域类(CRect)等。还有用来记录各群体数据的集合类,包括链表(CList)、动态数组(CArray)、映射类(CMap)等。

第3章 MFC原理介绍

包括C++封装原理和MFC六大关键技术,以及类向导和MFC应用程序向导的使用方法。讲解MFC消息映射机制的原理、消息映射函数的建立,以及消息发送和接收的方法。

第1节 使用时间类(CTime)

  1. 打开MSDN,索引输入CTime,选择CTime Class Members,查看CTime类所有成员函数;
函数原型函数说明
CTime( );缺省构造函数,构造一个无效时间对象
CTime( const CTime& timeSrc );拷贝构造函数,从另一个对象复制时间
CTime( time_t time );构造函数,从C语言时间句柄构造时间
CTime( int nYear, int nMonth, int nDay, int nHour, int nMin, int nSec, int nDST = -1 );构造函数,通过年月日时分秒及毫秒构造时间
CTime( const SYSTEMTIME& sysTime, int nDST = -1 );从SYSTEMTIME结构体对象构造时间
CTime( const FILETIME& fileTime, int nDST = -1 );从FILETIME结构体对象构造时间
static CTime GetCurrentTime( );获取当前时间
time_t GetTime( ) const;从CTime对象获取C语言时间句柄
int GetYear( ) const;获取年份
int GetMonth( ) const;获取月份
int GetDay( ) const;获取日期
int GetHour( ) const;获取小时
int GetMinute( ) const;获取分钟
int GetSecond( ) const;获取秒
int GetDayOfWeek( ) const;获取星期
运算符“==”、“!=”、“>”、“<”、“>=”、“<=”比较两个时间对象
  1. 新建工程对话框;添加ID为IDC_TEST的按钮;
    在这里插入图片描述
    双击“测试CTime”按钮,创建与按钮关联的成员函数OnTest;修改OnTest函数代码,用CTime类来显示当前时间;
void CTmDlg::OnTest() 
{
	// static成员无需对象,直接使用类名来调用即可
	CTime t = CTime::GetCurrentTime();
	int nYear = t.GetYear();
	int nMonth = t.GetMonth();
	int nDay = t.GetDay();
	int mHour = t.GetHour();
	int nMinute = t.GetMinute();
	int nSecond = t.GetSecond();
	CString szTime;//格式化组串:将各种类型的数据合成一个字符串
	szTime.Format("%d-%d-%d %d:%d:%d",nYear,nMonth,nDay,mHour,nMinute,nSecond);
	AfxMessageBox(szTime);
	
}

在这里插入图片描述

第2节 C++ 封装原理

面向对象的程序有四大特征:抽象、封装、继承和多态;在C++程序设计中经常封装一个类,让调用更容易使用;本节通过对比C语言time族函数和CTime类,学习MFC封装类的产生过程。

函数原型函数说明
time_t time( time_t *timer );获取当前时间,返回值是一个时间句柄
struct tm *localtime( const time_t *timer );将一个时间句柄转换为年月日时分秒的结构体对象
time_t mktime( struct tm *timeptr );将一个年月日时分秒保存到一个时间句柄

添加按钮IDC_TEST2,测试time,创建与按钮关联的成员函数OnTest2;

void CTmDlg::OnTest2() 
{
	time_t t = time(NULL); //获取当前时间句柄
	tm* pt = localtime(&t); //将时间句柄转化为tm结构体,再逐一转化成时间数值
	int nYear = pt->tm_year+1900;
	int nMonth = pt->tm_mon+1;
	int nDay = pt->tm_mday;
	int mHour = pt->tm_hour;
	int nMinute = pt->tm_min;
	int nSecond = pt->tm_sec;
	char szTime[64]; //将时间数值合成一个字符串
	sprintf(szTime,"%d-%d-%d %02d:%02d:%02d",nYear,nMonth,nDay,mHour,nMinute,nSecond);
	::MessageBox(NULL,szTime,"测试time",MB_OK);
}

tm结构体的定义如下;

struct tm {
        int tm_sec;     /* seconds after the minute - [0,59] */
        int tm_min;     /* minutes after the hour - [0,59] */
        int tm_hour;    /* hours since midnight - [0,23] */
        int tm_mday;    /* day of the month - [1,31] */
        int tm_mon;     /* months since January - [0,11] */
        int tm_year;    /* years since 1900 */
        int tm_wday;    /* days since Sunday - [0,6] */
        int tm_yday;    /* days since January 1 - [0,365] */
        int tm_isdst;   /* daylight savings time flag */
        };

了解封装的过程。在GetCurrentTime行设置一个断点,按F5调试运行到断点处按F11,进入函数查看MFC源代码;
CTime的类成员函数,全部是调用了C语言的time族函数;

CTime PASCAL CTime::GetCurrentTime()
// return the current system time
{
	return CTime(::time(NULL));
}

struct tm* CTime::GetGmtTm(struct tm* ptm) const
{
	if (ptm != NULL)
	{
		*ptm = *gmtime(&m_time);
		return ptm;
	}
	else
		return gmtime(&m_time);
}

struct tm* CTime::GetLocalTm(struct tm* ptm) const
{
	if (ptm != NULL)
	{
		struct tm* ptmTemp = localtime(&m_time);
		if (ptmTemp == NULL)
			return NULL;    // indicates the m_time was not initialized!

		*ptm = *ptmTemp;
		return ptm;
	}
	else
		return localtime(&m_time);
}

BOOL CTime::GetAsSystemTime(SYSTEMTIME& timeDest) const
{
	struct tm* ptm = GetLocalTm(NULL);
	if (ptm == NULL)
		return FALSE;

	timeDest.wYear = (WORD) (1900 + ptm->tm_year);
	timeDest.wMonth = (WORD) (1 + ptm->tm_mon);
	timeDest.wDayOfWeek = (WORD) ptm->tm_wday;
	timeDest.wDay = (WORD) ptm->tm_mday;
	timeDest.wHour = (WORD) ptm->tm_hour;
	timeDest.wMinute = (WORD) ptm->tm_min;
	timeDest.wSecond = (WORD) ptm->tm_sec;
	timeDest.wMilliseconds = 0;

	return TRUE;
}

第3节 MFC六大关键技术

MFC所有的封装类一共有200多个,但是MFC的内部技术不只是简单的封装。MFC的内部总共有六大黑箱技术架构起整个MFC的开发平台。六大关键技术的目的是为了提高开发效率,开发者只要在局部作简单修改,即可处理大部分窗口事物。MFC的六大关键技术包括MFC程序的初始化过程、运行时类型识别(RTTI)、动态创建、永久保存、消息映射和消息传递。
新建一个Win32程序“MFC32”,手动升级到MFC程序,演示“MFC初始化过程”的原理。
1)新建“Win32 Application”列表项,名称MFC32;
2)选中“A simple Win32 application”;
3)finish,单击WinMain函数并修改代码;

#include "stdafx.h"
class CMyApp:public CWinApp
{
	 BOOL InitInstance()
	{
		AfxMessageBox("由Win32工程转换而成MFC软件工程,测试MFC启动过程");
		return TRUE;
	}
};
CMyApp theApp;
/*
int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
	MessageBox(NULL,"Win32软件启动方式,WinMain","Win32软件",0);
	return 0;
}
*/

4)编译,错误;
5)没有包含CWinApp类所在的头文件;
6)在FileView中双击“StdAfx.h”,去掉Win32头文件并添加MFC头文件;
在这里插入图片描述
6)编译,错误;
7)没有链接到MFC代码库;
执行Project,Setting命令,选择Use MFC in a Static Library;Project
8)编译;
9)CWinApp::InitInstance虚函数与WinMain函数的关系;
把光标停放在CMyApp类中的InitInstance函数处,按F9设置断点,再按F5运行到断点处。从Variables的调试窗口的下拉列表中,可以看到MFC执行程序的启动过程。操作系统内核(kernel32.dll)先调用WinMain函数,在WinMain函数中再调用CWinApp派生类中的InitInstance函数;
10)在AfxWinMain函数中,发现其中pThread和pApp都是指向theApp全局变量地址的指针变量。通过基类指针执行pThread->InitInstance()时,会自动回调到派生类中,因为该函数是虚函数;
11)任何应用程序启动的过程,都是从操作系统内核(kernel32.dll)调用可执行文件的主函数开始的。
12)不借助“MFC AppWizard”,手工建立MFC程序步骤;
(1)从CWinMain类派生一个应用程序框架;
(2)使用派生类定义的全局变量(theApp);
(3)在预定义头文件“stdafx.h”中禁用“Windows.h”,取而代之的是MFC头文件;
(4)编译设置选择“Use MFC in a Static Library”;
(5)在CWinApp派生类中,重写InitInstance函数作为程序启动代码。

第4节 Win32消息处理机制

在回调函数中通过分支语句对消息进行解析和处理,就是Win32程序的消息处理机制;
1)建立Win32应用程序;
2)工程名Test32;
3)File->New,添加资源脚本;Resource Scrip,资源名Test32;
4)关闭正在编辑的窗口;
5)Resource Scrip的根结点,Insert;
6)选中Dialog,New;
在这里插入图片描述
7)修改WinMain函数;

// Test32.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "resource.h"
#include <stdio.h>

BOOL CALLBACK theProc(
  HWND hwndDlg,  // handle to dialog box
  UINT uMsg,     // message
  WPARAM wParam, // first message parameter
  LPARAM lParam  // second message parameter
)
{
	if(uMsg==WM_COMMAND)
	{ //判断消息类型
		if(wParam==IDCANCEL||wParam==IDOK)
		{ //判断按钮ID
			EndDialog(hwndDlg,wParam);
			return TRUE;
		}
	}
	return FALSE;
}

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
	::DialogBox(hInstance,MAKEINTRESOURCE(IDD_DIALOG1),NULL,theProc);
	return 0;
}

8)编译;单击cancel或OK都可以关闭对话框;
9)在关闭Visual C++ 6.0时可能会弹出提示;
说明工作区的编译列表发生了一些变化,是否保存;因为插入资源脚本时,编译列表中增加了一个“.rc”文件;
10)继续修改消息回调函数,增加一些消息的种类;

BOOL CALLBACK theProc(HWND hwndDlg,UINT uMsg,WPARAM wParam, LPARAM lParam)
{
	if(uMsg==WM_COMMAND) //单击按钮消息
	{
		if(wParam==IDCANCEL||wParam==IDOK)
		{
			return EndDialog(hwndDlg,wParam);
		}
	}
	if(uMsg==WM_LBUTTONDOWN) //单击鼠标左键消息
	{
		char s[100];
		sprintf(s,"x=%d,y=%d",LOWORD(lParam),HIWORD(lParam));
		MessageBox(NULL,s,"Test32",MB_OK);
		return TRUE;
	}
	if(uMsg==WM_PAINT) //窗口绘图消息
	{
		PAINTSTRUCT ps;
		HDC hdc = ::BeginPaint(hwndDlg,&ps);
		RECT rect;
		GetClientRect(hwndDlg,&rect);
		Rectangle(hdc,rect.left,rect.top,rect.right,rect.bottom);
		::EndPaint(hwndDlg,&ps);
		return TRUE;
	}	

	return FALSE;
}

11)编译并运行;

第5节 MFC消息映射机制

第6节 使用“MFC应用程序向导”建立MFC工程

第7节 消息传递

第8节 解码消息映射机制

第4章 对话框程序

讲解模式对话框和非模式对话框的区别和调用方法,介绍对话框的关闭过程和常用回调函数。总体介绍MFC类库内容,讲解其中最重要的两大类库内部成员函数的功能。

第1节 模式对话框和非模式对话框

对话框主要分为两个类型,即模式对话框和非模式对话框。当模式对话框弹出后,无法操作它的父窗口或者上一级窗口,直到关闭该对话框为止。
使用MFC应用程序向导,建立Test,基于对话框的工程。
1)在资源试图中,MFC程序向导生成了一个主对话框模版,在其中添加2个按钮;
2)按钮属性;

IDCaption用于
IDC_ABOUT关于测试 模式对话框
IDC_CHAT聊天测试 非模式对话框3)在资源视图中,Dialog单击右键,Insert Dialog,插入一个对话框;

4)修改属性;ID:ABOUTDLG,Caption:关于本软件;
5)选中对话框资源,执行View->Wizard,打开类向导。
类向导自动弹出创建新类的对话框,提示内容:IDD_ABOUTDLG是一个新的对话框资源,需要创建一个与它关联的类。选中Create a new class单击,OK,创建新对话框的关联类;
在这里插入图片描述
6)填写类名CAboutDlg后OK;完成对话框类的创建;
在这里插入图片描述
7)双击主对话框中的“关于”按钮建立消息映射函数;
在这里插入图片描述
8)编写OnAbout函数代码;

#include "AboutDlg.h"
void CTestDlg::OnAbout() 
{
	CAboutDlg dlg;
	dlg.DoModal();
}

第2节 调用非模式对话框

非模式对话框的调用过程;
1)输入一个对话框
2)修改对话框属性ID:IDD_CHATDLG
3)执行View->ClassWizard命令,创建一个CDialog派生类CChatDlg;
4)双击聊天按钮,建立消息映射函数;
5)编写OnChat函数代码;

#include "ChatDlg.h"
void CTestDlg::OnChat() 
{
	CChatDlg* pDlg = new CChatDlg;
	pDlg->Create(IDD_CHATDLG);
	pDlg->ShowWindow(SW_SHOW);
}

6)编译运行,反复单机聊天按钮,弹出多个聊天对话框;
7)非模式对话框特点:
(1)在关闭模式对话框前,不阻挡对背景窗口的操作;
(2)使用CDialog::Creat函数创建非模式对话框,该函数是非阻塞函数,因此,必须在堆空间内New申请对话框类对象,不能在栈内申请对象。因为临时对象在离开函数体时会自动销毁;
例如,以下代码是错误的,因为申请的dlg就是临时栈内变量;

CChatDlg dlg;
dlg.Creat(IDD_CHATDLG);

8)模式对话框特点;
(1)在关闭模式对话框前,无法操作背景窗口,除非本身就是主窗口;
(2)使用CDialog::DoModal函数弹出模式对话框,该函数执行过程为阻塞函数;因此在栈内或者堆内申请对话框的对象都可以,申请栈内变量开发和运行效率高一些;

第3节 对话框的常用回调函数

无论是模式对话框还是非模式对话框,都可以直接申请基类CDialog对象来创建了创建CDialog派生类的原因是,在派生类中可以接收对话框的消息映射和虚函数回调,响应来自用户的操作和系统事件;
1)类视图主对话框右键,选择Add WindowsMessage Handle;
2)选中WM_CREATE单击Add Handle;
3)WM_CREATE移到右边,已添加消息映射函数;
在这里插入图片描述
4)单击Edit Existing,修改OnCreate函数代码;
5)编译运行,对话框弹出之前标题被修改;
6)修改OnInitDialog函数代码;
7)编译运行,可以判断,OnCreate函数的回调时间早于OnInitDialog;
8)修改代码;测试;
OnInitDialog函数中子窗口都已创建完成,可以对任意子窗口进行初始化操作;
OnCreate函数中,对话框中的控件子窗口还未创建,不可以操作控件窗口;

第4节 对话框程序的关闭过程

可以实现单击系统关闭按钮时最小化或者隐藏窗口的功能。详见书籍P90。

第5节 MFC类库介绍

第6节 CWnd类

第7节 CWinApp类

调用MFC全局函数AfxMessageBox弹出消息框的标题文字,就是采用CWinApp类成员变量m_pszAppName中记录的文字。

。。。

第8节 CWinApp类的应用

使对话框的背景为白色,静态文本控件中的文字为红色;
。。。
加载系统图标
。。。

第9节 读/写配置文件(.ini)

配置文件一般扩展名是“.ini”,是一种用于简易存储数据的文本文件。
1)“Tp”工程,为主对话框类添加一个WM_DESTROY的消息映射函数;
2)修改该消息映射函数代码;
3)再修改主对话框的初始化函数OnInitDialog;
4)编译运行;把主窗口拖放到一个位置上之后再退出程序,关闭对话框时的位置已经被记录在配置文件中了;重新启动对话框程序后位置和关闭时是相同的。默认是保存在Windows目录下和可执行文件名相同的INI文件;
5)打开文件,可根据需要修改其中的配置;可以把中括号内的文字比作是C++的一个类名,每个等号左边的文字就是一个类内的成员变量;
6)CWinApp类封装的API函数包括GetPrivateProfileInt、GetPrivateProfileString、WritePrivateProfileInt、WritePrivateProfileString;
7)在InitInstance函数中添加一行代码,即可使配置数据保存到注册表中,而不再保存在INI文件中;
8)编译运行;执行 开始-运行-regedit-确定,打开注册表编辑器;
9)配置数据已经保存在注册表中了。

第5章 对话框组合

讲解登录对话框与主对话框的组合、权限管理与登录对话框的组合以及数据录入对话框的组合等。介绍对话框的分类,以及CFile类和CDialog类的功能。

第1节 登录对话框与主对话框组合

使用MFC应用程序向导,创建工程名为QQ的对话框程序。
1)在资源试图中的Dialog上右键,选择Insert Dialog,添加一个新的对话框资源作为登录对话框;
2)修改对话框ID为IDD_LOGIN_DLG,标题为登录;编辑登录对话框
3)添加控件修改属性;

控件类型IDCaptioonStyle
Static TextIDC_STATIC账号:
Edit BoxIDC_NAME
Static TextIDC_STATIC密码:
Edit BoxIDC_PASSPassword
ButtonIDOK登录
ButtonIDCANCEL退出

4)在登录对话框上单击右键,选择ClassWizard;
在这里插入图片描述
5)通过类向导为IDD_LOGIN_DLG建立一个对话框类CLoginDlg;
6)在登录对话框资源中双击登录按钮,建立消息映射函数OnOK并编写代码;

void CLoginDlg::OnOK() 
{
	CString szName,szPass;
	GetDlgItemText(IDC_NAME,szName);
	GetDlgItemText(IDC_PASS,szPass);
	szName.MakeLower();  //用户名不区分大小写
	if(szName=="admin" && szPass=="123456")
		CDialog::OnOK();   //关闭对话框
	else
	{
		AfxMessageBox("账号或密码错误,请重新输入");
		SetDlgItemText(IDC_NAME,"");
		SetDlgItemText(IDC_PASS,"");
		GetDlgItem(IDC_NAME)->SetFocus();  //设置焦点,或者,thi)->SetFocus();
	}	
}

7)修改App类中的进程启动函数InitInstance的代码;
在这里插入图片描述

#include "LoginDlg.h"
BOOL CQQApp::InitInstance()
{
	CLoginDlg ldlg;
	if(ldlg.DoModal()==IDCANCEL)
		return FALSE;  //单击退出按钮结束进程;
	CQQDlg dlg;
	dlg.DoModal();
	return FALSE;
}

8)在主对话框资源内添加一个按钮,ID为IDC_LOGIN,标题重新登录;
9双击重新登录的登录,建立消息映射函数,并编写代码;

#include "LoginDlg.h"
void CQQDlg::OnLogin()
{
	ShowWindow(SW_HIDE);
	CLoginDlg dlg;
	if(dlg.DoModal()==IDCANCEL)
		OnCancel();  //关闭主对话框并推出程序
	else
		ShowWindow(SW_SHOW);
}

10)编译运行,测试代码;
在CLoginDlg::OnOK函数中,只有输入正确的用户名和密码才能执行基类函数CDialog::OnOK,结束阻塞函数ldlg.Modal,如果单机确定按钮,按钮执行的基类函数CDialog::OnCancel,则结束阻塞函数Idlg.DoModal并返回IDCANCEL。在InitInstance函数中根据返回值判断,如果是IDCANCEL则程序不进入主对话框而直接退出。

第2节 权限管理与登录对话框组合

高级权限可以添加盒删除账号,普通权限则不能。
1)在类视图中双击CQQApp类,修改头文件代码;
添加一个结构体SUser用于存储权限管理数据,并在CQQApp类中添加一个类成员变量;

struct SUser
{	//登录用户信息结构体
	char sName[20];
	char sPass[20];
	int sPrior[8];
};

class CQQApp : public CWinApp
{
public:
	SUser m_user;	//用于保存当前登录用户信息
	CQQApp();
	DECLARE_MESSAGE_MAP()
};

2)添加一个新的对户口资源,ID为IDD_PRIOR_DLG,标题为权限管理;
3)添加控件,修改控件属性;

控件类型IDCaptionStyles
Static TextIDC_STATIC账号:
EditIDC_NAME
Static TextIDC_STATIC密码:
EditIDC_PASS
Static TextIDC_STATIC权限:
Combo BoxIDC_PRIORType:Drop List
ButtonIDC_ADD添加
ButtonIDC_DEL删除
ButtonIDC_MOD修改
List ControlIDC_LISTView_Report

4)通过类向导为IDD_PRIOR_DLG建立一个对话框类CPriorDlg;
5)在类视图中的CPriorDlg对话框类上单击鼠标右键,在弹出的快捷菜单中,选择Add Member Function命令,添加一个普通成员函数;
6)填写函数名为ReadUsers,返回值是void,然后单击OK按钮完成添加函数;
在这里插入图片描述
7)修改新添加的类成员函数的代码;

void CPriorDlg::ReadUsers(CListCtrl *pList)
{	//从文件中读取用户列表
	CFile file;
	if(!file.Open("./Users.dat",CFile::modeRead))
		return;	
	SUser u;
	int i = 0;
	while(file.Read(&u,sizeof(u))>0 ) 
	{
		pList ->InsertItem(i,u.sName);
		pList ->SetItemText(i,1,u.sPass);
		pList ->SetItemText(i,2,u.sPrior);
		++i;
	}
	file.Close();
}

8)在权限管理对话框中,添加WM_INITDIALOG的消息映射函数OnInitDialog;
在这里插入图片描述
9)按Edit Code,修改函数代码;

BOOL CPriorDlg::OnInitDialog() 
{
	CDialog::OnInitDialog();
	CListCtrl* pList = (CListCtrl*)GetDlgItem(IDC_LIST);
	pList ->InsertColumn(0,"账户",0,100);
	pList ->InsertColumn(1,"密码",0,100);
	pList ->InsertColumn(2,"权限",0,100);
	
	CComboBox* pComb = (CComboBox*)GetDlgItem(IDC_PRIOR);
	pComb ->AddString("普通");
	pComb ->AddString("高级");
	pComb ->SetCurSel(0);
	ReadUsers(pList);
	return TRUE;  // return TRUE unless you set the focus to a control
	              // EXCEPTION: OCX Property Pages should return FALSE
}

10)为CPriorDlg类添加WM_DESTROY的消息映射函数;
在这里插入图片描述
在这里插入图片描述
11)修改消息映射函数CPriorDlg::OnDestroy() 的代码;

void CPriorDlg::OnDestroy() 
{	//在权限对话框关闭时自动保存用户列表
	CDialog::OnDestroy();
	CFile file;
	if(!file.Open("./Users.dat",CFile::modeCreate|CFile::modeWrite))
		return;
	CListCtrl* pList = (CListCtrl*)GetDlgItem(IDC_LIST);
	int i = 0,nCount = pList ->GetItemCount();
	SUser u;
	while(i<nCount)
	{
		pList ->GetItemText(i,0,u.sName,sizeof(u.sName));
		pList ->GetItemText(i,1,u.sPass,sizeof(u.sPass));
		pList ->GetItemText(i,2,u.sPrior,sizeof(u.sPrior));
		file.Write(&u,sizeof(u));
		++i;
	}
	file.Close();
	
}
void CPriorDlg::OnDestroy() 
{	//在权限对话框关闭时自动保存用户列表
	CDialog::OnDestroy();
	CFile file;
	if(!file.Open("./Users.dat",CFile::modeCreate|CFile::modeWrite))
	{
		AfxMessageBox("保存文件时失败!");
		return;
	}
	CListCtrl* pList = (CListCtrl*)GetDlgItem(IDC_LIST);
	int i = 0,nCount = pList ->GetItemCount();
	SUser u;
	while(i<nCount)
	{
		pList ->GetItemText(i,0,u.sName,sizeof(u.sName));
		pList ->GetItemText(i,1,u.sPass,sizeof(u.sPass));
		u.sPrior = pList ->GetItemText(i,2)=="高级";
		file.Write(&u,sizeof(u));
		++i;
	}

	file.Close();
	
}

12)在权限管理对话框中,分别双击增、删、改3个按钮,建立消息映射函数;

void CPriorDlg::OnAdd() 
{
	SUser u;
	GetDlgItemText(IDC_NAME,u.sName,sizeof(u.sName));
	//账号登录时不区分大小写
	strlwr(u.sName);
	CListCtrl*pList=(CListCtrl*)GetDlgItem(IDC_LIST);
	int i=0,nCount=pList->GetItemCount();
	while(i<nCount)
	{
		if(pList->GetItemText(i,0)==u.sName)
		{
			CString str;
			str.Format("用户\"%s\"已经存在!",u.sName);
			AfxMessageBox(str);
			return;
		}
		i++;GetDlgItemText(IDC_PASS,u.sPass,sizeof(u.sPass));
	GetDlgItemText(IDC_PRIOR,u.sPrior,sizeof(u.sPrior));
	pList->InsertItem(nCount,u.sName);
	pList->SetItemText(nCount,1,u.sPass);
	pList->SetItemText(nCount,2,u.sPrior);void CPriorDlg::OnDel()
{
	CListCtrl*pList=(CListCtrl*)GetDlgItem(IDC_LIST);
	POSITION pos=pList->GetFirstSelectedItemPosition();
	int nSel=pList->GetNextSelectedItem(pos);
	if(nSel<0)AfxMessageBox("请选择一个用户再删除!");
	return;//保留一个超级管理用户名
	CString szName=pList->GetItemText(nSel,0);
	if(szName=="admin")
	return;
	CString str;
	str.Format("确定删除账号\"%s\"吗?",szName);
	if(AfxMessageBox(str,MB_YESNO)==IDYES)
		pList->DeleteItem(nSel);void CPrirDlg::OnMod()
{
	CListCtrl*pList=(CListCtrl*)GetDlgItem(IDC_LIST);
	POSITION pos=pList->GetFirstSelectedItemPosition();
	int nSel=pList ->GetNextSelectedItem(pos);
	if(nSel<0)AfxMessageBox("请选择一个用户再修改!"); 
		return;
	}
	SUser u:
	GetDlgItemText(IDC_PASS,u.sPass,sizeof(u.sPass));
	pList->SetItemText(nSel,1,u.sPass);
	CString szName=pList->GetItemText(nSel,0);
	//如果是admin账户则只允许修改密码
	if(szName=="admin")
		return;
	GetDlgItemText(IDC_PRIOR,u.sPrior,sizeof(u.sPrior));
	pList->SetItemText(nSel,2,u.sPrior);
}

13)在主对话框中添加一个按钮,ID为IDC_PRIOR,标题为权限管理;
在这里插入图片描述
14)双击权限管理按钮建立消息映射函数,并编写代码;

#include "PriorDlg.h"
void CQQDlg::OnPrior() 
{
	CPriorDlg dlg;
	dlg.DoModal();
}	

15)在类视图中的CLoginDlg对话框类上单击鼠标右键,选择Add Member Function命令,添加一个成员函数;
在这里插入图片描述
16)单击OK,添加CheckUser函数并修改代码;

extern CQQApp theApp
BOOL CLoginDlg::CheckUser(CString szName, CString szPass)
{
	CFile file;
	if(!file.Open("./Users.dat",CFile::modeRead))
	{
		CreateUser();	//如果用户不存在则创建文件,再重新打开
		if(!file.Open("./Users.dat",CFile::modeRead))
			return FALSE;
	}
//把登录用户记录在theApp.m_user;
Suser &u=theApp.m_user;
while(file.read(&u,sizeof(u))>0)
{
	if(szName==u.sName)
		return szPass==u.sPass;
}
file.Close();
return FALSE;
}

17)在视图中的CLoginDlg对话框上单击鼠标右键,选择Add Member Function命令,添加一个成员函数;
在这里插入图片描述
18)单击OK按钮添加普通成员函数并修改代码;

void CLoginDlg::CreateUser()
{
	CFile file;
	if(!file.Open("./Users.dat",CFile::modeCreate|CFile::modeWrite))
		return;
	SUser u={"admin","高级"};
	file.Write(&u,sizeof(u));
	file.Close();
}

19)修改CLoginDlg中的OnOK代码;

void CLoginDlg::OnOK() 
{
	CString szName,szPass;
	GetDlgItemText(IDC_NAME,szName);
	GetDlgItemText(IDC_PASS,szPass);
	szName.MakeLower();  //用户名不区分大小写
	if(CheckUser(szName,szPass))
	{	//如果密码检查通过则关闭登录对话框
		CDialog::OnOK();
		return;
	}
	AfxMessageBox("账号或密码错误,请重新输入");
	SetDlgItemText(IDC_NAME,"");
	SetDlgItemText(IDC_PASS,"");
	GetDlgItem(IDC_NAME)->SetFocus();  //设置焦点,或者,thi)->SetFocus();	
}

20)修改主对话框初始化函数OnInitDialog的代码;

extern CQQApp theApp
BOOL CQQDlg::OnInitDialog()
{
	CDialog::OnInitDialog();
	CString str=theApp.m_user.sPrior
	GetDlgItem(IDC_PRIOR)->EnableWindow(str!="普通");
	str="-"+str;
	str=theApp.m_user.sName+str;
	SetWindowText(str);
……

21)修改主对话框重新登库按钮的消息映射函数代码;

#include "LoginDlg.h"
void CQQDlg::OnLogin()
{
	ShowWindow(SW_HIDE);
	CLoginDlg dlg;
	if(dlg.DoModal()==IDCANCEL)
		OnCancel();  //关闭主对话框并推出程序
	else

	{
		CString str=theApp.m_user.sPrior;
		GetDlgItem(IDC_PRIOR)->EnableWindow(str!="普通");
		str="-"+str;
		str=theApp.m_user.sName+str;
		SetWindowText(str);	
		ShowWindow(SW_SHOW);
	}
		
}

22)编译并运行,测试代码;;

第3节 数据录入与对话框组合

第4节 系统对话框

第5节 CFile类

第6节 CDialog类

第6章 基础控件

介绍Windows基础控件和常用的控件CListCtrl常用成员,包括CComboBox类、CEdit类和CButton类等。讲解通过类向导建立控件型关联变量、通过函数建立控件型关联变量以及常用控件的数值型关联变量的方法。

第1节 Windows基础控件

在Visusal C++对话框资源编辑框界面中,Controls工具栏列出的就是所有Windows基础控件。
Picturn图片控件:CStatic类,显示图片
Static Text静态文本控件:CStatic类显示不可以编辑的文字
Edit Box编辑控件:CEdit类提供文字编辑、复制、粘贴等功能
Group Box分组控件:CButton类将功能相近的控件圈括在一个区域内
Button命令按钮:CButton类单击按钮发出命令
Check Box复选控件:CButton类提供多选的选项功能
Radio Button:CButton类提供单选功能
Combo Box组合控件:CComboBox类提供可以隐藏下拉列表控件
List Box列表框:CListBox类以列表方式显示多行数据,支持增删改以及单选或多选功能
Horizontal scrollbar横向滚动栏:CScrollBar类
Vertical scrollbar纵向滚动栏:CScrollBar类
Spin旋转按钮:CSpin Button Ctrl类,和编辑框结合,辅助增减控件内的数字
Progress进度栏:CprogressCtrl类提供耗时操作的进度显示
Slider滑块控件:CSliderCtrl类提供通过拖动滑块跳跃选取数字或者进度
Hot Key热键控件:CHotKeyCtrl类用于显示和修改热键设置
List Control列表视图控件:CList Ctrl类提供按列显示数据集合的功能,支持增删改查等功能
Tree Control树形控件:CTreeCtrl类显示和处理多层次数据结构
Tab Control标签控件:CTabCtrl类用于管理多个分页窗口,每一个分页窗口都包含不同的控件和信息
Animate动画控件:CAnimateCtrl类提供简单AVI动画播放功能
Rich Edit高级编辑控件:CRich Edit Ctrl类不仅提供文本编辑功能,还提供不同段落的字体和颜色
Date Time Picker日期和时间控件:CDateTimeCtrl类编辑和显示日期和时间信息功能
Month Calendar月历控件:Cmonth CalCtrl类提供一个简易月历界面,用户可以非常方便的选择日期
IP Address IP地址控件:CIPAddressCtrl类提供IP 地址的显示和编辑
ComboBoxEx扩展组合框:CcomboBoxEx类支持存储图形列表中的图像
每一个控件都可以与一个MFC控件类的对象关联,通过控件类的成员函数操作控件。

第2节 通过类向导建立控件类型关联变量

通过类向导Class Wizard可以建立一个控件类型的成员变量,并且这个成员变量与一个指定ID的控件关联。通过这个成员变量调用控件类的成员函数可以操作与之关联的控件,这个成员变量就是控件型关联变量。
通过MFC应用程序向导,创建一个工程名为vc的对话框程序。
1)修改对话框的外观和字体并添加一些控件;
在这里插入图片描述
2)添加控件并修改控件属性;

控件类型IDCaptionStyles
Static TextIDC_STATIC工号:
Edit BoxIDC_NUMB
Static TextIDC_STATIC姓名:
Edit BoxIDC_NAME
Static TextIDC_STATIC部门:
Combo BoxIDC_DEPTType:Drop List去掉Sort属性
ButtonIDC_ADD添加
ButtonIDC_DEL删除
ButtonIDC_MOD修改
List ControlIDC_LISTView:Report

3)在对话框上单击鼠标右键,选择ClassWizard命令;
在这里插入图片描述
4)在Member Variables 分页中,选中IDC_LIST列表项再单击Add Variables按钮;其中,在Category下拉列表框中选择Control,表示要添加的是控件型关联变量,在Variable type下拉列表框中选择CList Ctrl,表示要建立的关联变量的类型。在Member variable name文本框中填写变量名称m_list后单击OK按钮,完成添加控件型关联变量。
在这里插入图片描述
在这里插入图片描述
5)添加关联变量的结果
添加关联变量的结果。在对话框类的头文件vcDlg.h中增加一行代码CListCtrl m_list,就是刚才添加的CListCtrl类型成员变量,在源文件vcDlg.cpp中也增加了一行代码DDX_Control(pDX,IDC_LIST,m_list),表示将ID为IDC_LIST的列表控件与成员变量m_list关联。
在这里插入图片描述
在这里插入图片描述
6)按照以上方法,通过类向导为组合控件IDC_DEPT也建立一个控件型关联变量;
在这里插入图片描述
7)创建多个关联变量,若不对可单击DeleteVariable按钮删除。
在这里插入图片描述
8)在头文件和源文件中处理变量;
在这里插入图片描述
在这里插入图片描述
9)修改对话框初始化函数代码;
默认为在这里插入图片描述
修改为

BOOL CVcDlg::OnInitDialog()
{
	CDialog::OnInitDialog();
	m_list.InsertColumn(0,"工号",0,100);
	m_list.InsertColumn(1,"姓名",0,100);
	m_list.InsertColumn(2,"部门",0,100);
	m_combo.AddString("请选择部门");
	m_combo.AddString("行政部");
	m_combo.AddString("财务部");
	m_combo.AddString("市场部");
	m_combo.AddString("测试部");
	m_combo.AddString("开发部");
	m_combo.SetCurSel(0);

	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon
	
	return TRUE;  // return TRUE  unless you set the focus to a control
}

问题现象:使用CComboBox 控件下拉列表中本应该会显示3个或者更多的对象,但是程序编译后一个都不显示或者显示不全;

解决方法:在画界面设计处,选中 CComboBox
,鼠标移到的下拉箭头上,当光标为双向箭头时。再点击,出现八个小方框围成的矩形区域,将其拉到期望的大小。重新编译程序即可。

10为增删改建立消息映射函数;
添加

void CVcDlg::OnAdd() 
{
	int nSel = m_combo.GetCurSel();
	if(!nSel)
	{
		AfxMessageBox("请先选择部门再添加!");
		return;
	}
	CString str;
	GetDlgItemText(IDC_NUMB,str);
	int nCount = m_list.GetItemCount();
	m_list.InsertItem(nCount,str);
	GetDlgItemText(IDC_NAME,str);
	m_list.SetItemText(nCount,1,str);
	m_combo.GetLBText(nSel,str);
	m_list.SetItemText(nCount,2,str);
}

删除

void CVcDlg::OnDel() 
{
	POSITION pos = m_list.GetFirstSelectedItemPosition();
	int nSel = m_list.GetNextSelectedItem(pos);
	if(nSel<0)
	{
		AfxMessageBox("请选择列表中的一条信息再删除!");
		return;		
	}
	CString str = m_list.GetItemText(nSel,0);
	str = "确定要删除" + str;
	str +="号信息吗?";
	if(AfxMessageBox(str,MB_YESNO)==IDYES)
		m_list.DeleteItem(nSel);
}

修改

void CVcDlg::OnMod() 
{
	POSITION pos = m_list.GetFirstSelectedItemPosition();
	int nSel = m_list.GetNextSelectedItem(pos);
	if(nSel<0)
	{
		AfxMessageBox("请选择列表中的一条信息再修改!");
		return;		
	}
	CString str = m_list.GetItemText(nSel,0);
	str = "确定要修改" + str;
	str +="号信息吗?";
	if(AfxMessageBox(str,MB_YESNO)==IDNO)
		return;
	GetDlgItemText(IDC_NAME,str);
	m_list.SetItemText(nSel,1,str);
	m_combo.GetWindowText(str);
	m_list.SetItemText(nSel,2,str);
}

11)编译运行,测试代码;
本示例演示通过类向导,对多种控件建立控件关联变量。控件关联变量比GetDlgItem函数在使用上方便很多,关联变量建立完之后就可以长期使用,每次调用前不需要再重新关联。

第3节 通过函数建立控件型关联变量

在CWnd类中包含一系列将窗口与变量关联的函数,如下:
1)CWnd::Attach,将一个窗口句柄嫁接到一个CWnd类型变量中;
2)CWnd::Detach,移除嫁接到CWnd变量中的句柄;
3)CWnd::SubclassWindow,子类化一个窗口句柄到CWnd派生类变量中(但不要把句柄关联进入CWnd对象中,而且还要将窗口的消息映射到CWnd的子类中);
4)CWnd::UnsubclassWindow,解除子类化;
5)CWnd::SubclassDlgItem,根据窗口ID子类化对应的窗口。

使用MFC应用程序向导,建立一个工程名为vs的对话框程序。
1)修改外观字体,添加控件;
在这里插入图片描述
2)控件属性;

控件类型IDCaptionStyles
Static TextIDC_STATIC工号:
Edit BoxIDC_NUMB
Static TextIDC_STATIC姓名:
Edit BoxIDC_NAME
Static TextIDC_STATIC部门:
Combo BoxIDC_DEPTType:Drop List去掉Sort属性
ButtonIDC_ADD添加
ButtonIDC_DEL删除
ButtonIDC_MOD修改
List ControlIDC_LISTView:Report

3)在对话框类的头文件中,加入4个控件类型的变量;

class CVsDlg : public CDialog
{
	CEdit m_numb;
	CEdit m_name;
	CComboBox m_combo;
	CListCtrl m_list;

4)修改对话框初始化函数,通过调用函数建立控件窗口和变量的关联;

BOOL CVsDlg::OnInitDialog()
{
	CDialog::OnInitDialog();
	m_numb.SubclassDlgItem(IDC_NUMB,this);
	m_name.SubclassDlgItem(IDC_NAME,this);
	HWND hCombo,hList;
	GetDlgItem(IDC_DEPT,&hCombo);
	m_combo.SubclassWindow(hCombo);
	GetDlgItem(IDC_LIST,&hList);
	m_list.Attach(hList);
	m_list.SetExtendedStyle(LVS_EX_GRIDLINES|LVS_EX_FULLROWSELECT);
	m_list.InsertColumn(0,"工号",0,100);
	m_list.InsertColumn(1,"姓名",0,100);
	m_list.InsertColumn(2,"部门",0,100);
	m_combo.AddString("请选择部门");
	m_combo.AddString("行政部");
	m_combo.AddString("财务部");
	m_combo.AddString("市场部");
	m_combo.AddString("测试部");
	m_combo.AddString("开发部");
	m_combo.SetCurSel(0);
	
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon
	// TODO: Add extra initialization here
	return TRUE;  // return TRUE  unless you set the focus to a control
}

5)添加WM_DESTROY消息映射函数并修改代码;
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200704180410545.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dfbGluX2ppZQ==,size_16,color_FFFFFF
在这里插入图片描述

void CVsDlg::OnDestroy() 
{
	CDialog::OnDestroy();
	m_list.Detach();
	m_combo.UnsubclassWindow();	
}

6)为增删改3个按钮建立消息映射函数并修改代码;
添加

void CVsDlg::OnAdd() 
{
	int nSel = m_combo.GetCurSel();
	if(!nSel)
	{
		AfxMessageBox("请先选择部门再添加!");
		return;
	}
	CString str;
	m_numb.GetWindowText(str);
	int nCount=m_list.GetItemCount();
	m_list.InsertItem(nCount,str);
	m_name.GetWindowText(str);
	m_list.SetItemText(nCount,1,str);
	m_combo.GetLBText(nSel,str);
	m_list.SetItemText(nCount,2,str);
}

删除

void CVsDlg::OnDel() 
{
	POSITION pos = m_list.GetFirstSelectedItemPosition();
	int nSel = m_list.GetNextSelectedItem(pos);
	if(nSel<0)
	{
		AfxMessageBox("请选择列表中的一条信息再删除!");
		return;		
	}
	CString str = m_list.GetItemText(nSel,0);
	str = "确定要删除" + str;
	str +="号信息吗?";
	if(AfxMessageBox(str,MB_YESNO)==IDYES)
		m_list.DeleteItem(nSel);
}

修改

void CVsDlg::OnMod() 
{
	POSITION pos = m_list.GetFirstSelectedItemPosition();
	int nSel = m_list.GetNextSelectedItem(pos);
	if(nSel<0)
	{
		AfxMessageBox("请选择列表中的一条信息再修改!");
		return;		
	}
	CString str = m_list.GetItemText(nSel,0);
	str = "确定要修改" + str;
	str +="号信息吗?";
	if(AfxMessageBox(str,MB_YESNO)==IDNO)
		return;
	m_name.GetWindowText(str);
	m_list.SetItemText(nSel,1,str);
	m_combo.GetWindowText(str);
	m_list.SetItemText(nSel,2,str);
}

7)编译运行,测试代码;
实际上类向导建立的关联变量,内部也是调用子类化函数实现的,但是使用向导建立关联变量更加方便一些。因此,之后主要使用类向导建立关联变量。

第4节 通过类向导建立数值型关联变量

所有Windows基础控件都可以建立控件型关联变量,有一些控件不但可以建立控件型变量,而且可以建立数值型关联变量;
在第3节中,当通过类向导建立组合控件的关联变量(m_combo)时,在Category下拉列表框中包含Value和Control两项。选择Control表示建立控件型关联变量,选择Value表示建立数值型关联变量。大部分基础控件不支持数值型关联变量,只支持控件型关联变量,比如按钮、列表控件、树形控件;只有少部分控件才支持建立数值型关联变量,这些控件包括编辑框、下拉列表框、单选按钮、多选按钮、列表框等。

使用MFC应用程序向导,创建一个工程名为cal的对话框程序。
1)修改对话框标题为计算器,并添加一些控件;
在这里插入图片描述
2)修改主对话框的控件属性;

控件类型IDCaptionStyles
Edit BoxIDC_LEFT
Combo BoxIDC_OPERType:Drop List去掉Sort属性
Edit BoxIDC_RIGHT
Static TextIDC_STATIC
Edit BoxIDC_RESULT
ButtonIDOK计算

3)在下拉列表控件IDC_OPER的Data属性中输入默认数据;
在这里插入图片描述
4)执行View ClassWizard命令。在Member Variable分页中,选中IDC_LEFT控件ID再单击Add Variable按钮;
在这里插入图片描述
5)在类向导中为一个编辑控件建立数值型关联变量;在Category下拉列表框中,选择Value代表建立数值型关联变量;在Variable type下拉列表框选择int类型,在变量名称中输入m_nLeft,最后单击OK按钮关闭对话框,完成编辑框IDC_LEFT关联变量的创建;
在这里插入图片描述
6)用同样的方法为三个编辑框和一个下拉列表控件,全部建立int类型的关联变量;
在这里插入图片描述
7)双击计算按钮,建立消息映射函数并修改代码;

void CCalDlg::OnOK() 
{
	UpdateData();
	switch(m_nOper)
	{
	case 0:
		m_nResult=m_nLeft + m_nRight;
		break;
	case 1:
		m_nResult=m_nLeft - m_nRight;
		break;
	case 2:
		m_nResult=m_nLeft * m_nRight;
		break;
	case 3:
		m_nResult=m_nLeft / m_nRight;
		break;
	case 4:
		m_nResult=m_nLeft % m_nRight;
		break;
	}
	UpdateData(FALSE);
}

8)编译并运行;

第5节 常用控件的数值型关联变量

创建一个工程名为vd的对话框程序,演示常用控件的数值型关联变量;
1)标题:员工信息;添加控件;
在这里插入图片描述
2)控件属性;

控件类型IDCaptionStyles
Static TextIDC_STATIC工号:
Edit BoxIDC_NUMB
Static TextIDC_STATIC姓名:
Edit BoxIDC_NAME
Static TextIDC_STATIC性别:
Radio ButtonIDC_SEXGroup Tab Stop
Radio ButtonID随机
Group BoxIDC_STATIC学历
Radio ButtonIDC_EDUCA高中Group Tab Stop
Radio Button随机ID大学
Radio Button随机ID硕士
Radio Button随机ID博士
Group BoxIDC_STATIC外语
Check BoxIDC_ENGL英语
Check BoxIDC_JAPA日语
Check BoxIDC_KOREA韩语
Check BoxIDC_RUSS俄语
Static TextIDC_STATIC入职日期:
Data Time PickerIDC_JOIN
ButtonIDC_ADD添加
ButtonIDC_DEL删除
ButtonIDC_MOD修改
List ControlIDC_LIST

3)在类向导中为除按钮以外的全部控件都建立一个关联变量(列表控件建立的都是控件型变量,其他控件建立的是数值型变量);
在这里插入图片描述
4)类向导在主对话框的头文件中自动添加的代码如下;

	//{{AFX_DATA(CVdDlg)
	enum { IDD = IDD_VD_DIALOG };
	CListCtrl	m_list;
	BOOL	m_bEngl;
	BOOL	m_bJapa;
	COleDateTime	m_dtJoin;
	BOOL	m_bKorea;
	CString	m_szName;
	CString	m_szNumb;
	BOOL	m_bRuss;
	int		m_nSex;
	int		m_nEduca;
	//}}AFX_DATA

5)类向导在数据交换函数中自动增加的代码如下;

void CVdDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CVdDlg)
	DDX_Control(pDX, IDC_LIST, m_list);
	DDX_Check(pDX, IDC_ENGL, m_bEngl);
	DDX_Check(pDX, IDC_JAPA, m_bJapa);
	DDX_DateTimeCtrl(pDX, IDC_JOIN, m_dtJoin);
	DDX_Check(pDX, IDC_KOREA, m_bKorea);
	DDX_Text(pDX, IDC_NAME, m_szName);
	DDX_Text(pDX, IDC_NUMB, m_szNumb);
	DDX_Check(pDX, IDC_RUSS, m_bRuss);
	DDX_Radio(pDX, IDC_SEX, m_nSex);
	DDX_Radio(pDX, IDC_EDUCA, m_nEduca);
	//}}AFX_DATA_MAP
}

6)类向导还在构造函数中自动添加如下代码(稍作修改);

CVdDlg::CVdDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CVdDlg::IDD, pParent)
{
	//{{AFX_DATA_INIT(CVdDlg)
	m_bEngl = TRUE;	//将默认的FALSE修改为TRUE
	m_bJapa = FALSE;
	m_dtJoin = COleDateTime::GetCurrentTime();
	m_bKorea = FALSE;
	m_szName = _T("");
	m_szNumb = _T("");
	m_bRuss = FALSE;
	m_nSex = 0;	//将默认的-1修改为0
	m_nEduca = 1;	//将默认的-1修改为1
	//}}AFX_DATA_INIT
	// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

7)修改对话框初始化函数的代码;

BOOL CVdDlg::OnInitDialog()
{
	CDialog::OnInitDialog();
    m_list.InsertColumn(0,"工号",0,80);
    m_list.InsertColumn(1,"姓名",0,80);
    m_list.InsertColumn(2,"性别",0,60);
    m_list.InsertColumn(3,"学历",0,80);
    m_list.InsertColumn(4,"入职",0,80);
    m_list.InsertColumn(5,"外语",0,100);
    m_list.SetExtendedStyle(LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT);

	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon
	
	// TODO: Add extra initialization here
	
	return TRUE;  // return TRUE  unless you set the focus to a control
}

8)为增删改3个按钮建立消息映射函数并修改代码;
添加

void CVdDlg::OnAdd() 
{
    UpdateData();
    int nCount = m_list.GetItemCount();
    m_list.InsertItem(nCount,m_szNumb);
    m_list.SetItemText(nCount,1,m_szName);
    m_list.SetItemText(nCount,2,m_nSex?"女":"男");
    char *p[]={"高中","大学","硕士","博士"};
    m_list.SetItemText(nCount,3,p[m_nEduca]);
    CString str;
    if(m_bEngl)
        str+="英";
    if(m_bJapa)
        str+="日";
	if(m_bKorea)
        str+="韩";
    if(m_bRuss)
        str+="俄";
    if(str.IsEmpty())
        str+="无";
    m_list.SetItemText(nCount,5,str);
    str.Format("%d-%d-%d",m_dtJoin.GetYear(),m_dtJoin.GetMonth(),m_dtJoin.GetDay());
    m_list.SetItemText(nCount,4,str);	
}

删除

void CVdDlg::OnDel() 
{
	POSITION pos = m_list.GetFirstSelectedItemPosition();
	if(!pos)
	{
		AfxMessageBox("请选择列表中的一条信息再删除!");
		return;		
	}
	int nSel = m_list.GetNextSelectedItem(pos);
	CString str = m_list.GetItemText(nSel,0);
	str = "确定要删除" + str +  "号信息吗?";
	if(AfxMessageBox(str,MB_YESNO)==IDYES)
		m_list.DeleteItem(nSel);
}

修改

void CVdDlg::OnMod() 
{
    POSITION pos = m_list.GetFirstSelectedItemPosition();
    if(!pos)
    {
        AfxMessageBox("请选择一行再删除!");
        return;
    }
	int nSel = m_list.GetNextSelectedItem(pos);
	UpdateData();
    m_list.SetItemText(nSel,0,m_szNumb);
    m_list.SetItemText(nSel,1,m_szName);
    m_list.SetItemText(nSel,2,m_nSex?"女":"男");
    char *p[]={"高中","大学","硕士","博士"};
    m_list.SetItemText(nSel,3,p[m_nEduca]);
    CString str;
    if(m_bEngl)
        str+="英";
    if(m_bJapa)
        str+="日";
    if(m_bKorea)
        str+="韩";
    if(m_bRuss)
        str+="俄";
    if(str.IsEmpty())
        str+="无";
    m_list.SetItemText(nSel,5,str);
    str.Format("%d-%d-%d",m_dtJoin.GetYear(),m_dtJoin.GetMonth(),m_dtJoin.GetDay());
    m_list.SetItemText(nSel,4,str);
}

9)编译并运行,测试代码;
在这里插入图片描述
10)单选按钮的使用方法比较特殊,总结其特点如下;
(1)每一组单选按钮都必须而且只能有一个“组长”;
(2)“组长”就是指选择了“Group”属性的控件,一组单选按钮的“组长”必须是该组中ID最小的;
(3)通常在一组单选按钮中,第一个拖放到对话框的为“组长”,随后一次加入的是单选按钮作为“组员”;
(4)一组单选按钮中只有“组长”的ID是有用的,其他“组员”的ID随机生成即可;
(5)通过类向导建立关联变量时,只要对“组长”ID建立一个数值型关联变量即可。

第7章 GDI绘图技术

介绍绘图专用句柄HDC、CDC类及其派生类,讲解多种窗口绘图方式,GDI对象的使用方法,包括CPen类(画笔)、CBrush类(画刷)、CFont类(字体)、CBitmap类(位图)和CRgn类(区域)等。

第一节 绘图专用句柄HDC

HWND(Handle of Window,窗口句柄)专门用于窗口操作,被MFC封装于CWnd类中;
HDC(Handle of DC,设备环境句柄)是专门用于绘图的句柄,被MFC封装于CDC类中。
DC(Device Context)一般被称为设备上下文、设备环境或设备描述表。

通过HDC句柄绘图有三种方式,即标准客户区绘图、临时客户区绘图和非客户区绘图。
创建一个WIn32程序“DC32”。
1)File->New命令,选中Win32 Application;
2)选中A simple Win32 application,finish安成;
3)File->New,在Files分页中选择Resource Scrip列表项;
4)在资源视图中(Resource View)中,添加资源;
在这里插入图片描述
5)插入资源对话框,选中Dialog,单击New;
6)修改对话框ID为IDD_PAINT_DLG后,再修改WinMain和消息回掉函数代码。

第8章 图形软件开发

介绍内存设备环境CMemDC类的封装原理,讲解多种图形软件开发实用技术,包括图像透明技术、动画技术、透明动画、不规则窗口和双缓冲防闪烁技术等,讲解图层软件架构,介绍CDC类成员函数。

第9章 高级控件应用

介绍高级控件的开发方法,包括旋转按钮(CSpinButtonCtrl类)、标签控件(CTabCtrl类)、高级编辑控件(CRichEditCtrl类)以及树形控件(CTreeCtrl类)等。讲解分页技术以及属性表(CPropertySheet类)和属性页(CPropertyPage类)的开发方法,介绍控件的消息反射。

第10章界面装饰

讲解列表控件的列表项和标头图标设置、列表项选项、窗口颜色控制、自绘按钮、自绘组合控件等界面装饰相关技术。

第11章 自定义窗口

介绍手动创建控件、注册和创建自定义窗口,以及界面提示(CToolTipCtrl类)等。讲解自定义控件开发,包括按钮标签控件,以及滑块控件的二次开发。

第12章 视图与框架

讲解视图与框架的创建方法,以及多种由视图、分隔栏和框架结合生成的界面模型,包括Frame-View、Frame-Splitter、Frame-Splitter-Splitter以及MDIFrameWnd-MDIChildWnd等结构模型。

第13章 文档模版架构

介绍CFile、CArchive和CDocument等存储模型的进化过程,以及基于文档模版架构的序列化存储模式。介绍MFC六大关键技术,讲解动态创建、运行时类型识别以及命令传递技术内幕。

第14章 菜单与控制栏

介绍窗口菜单、上下文菜单以及自绘菜单的开发,讲解悬浮工具和文字工具栏、IE工具栏以及对话框栏和状态栏等。

第15章 MFC网络通信

介绍TCP/IP的层次,讲解简单UDP通信的和TCP通信的方式。讲解TCP和UDP通信协议的开发,以及TCP的短连接模式等。

Visual C++MFC入门教程 目录 +-- 第一章 VC入门 |------ 1.1 如何学好VC |------ 1.2 理解Windows消息机制 |------ 1.3 利用Visual C++/MFC开发Windows程序的优势 |------ 1.4 利用MFC进行开发的通用方法介绍 |------ 1.5 MFC常用类,宏,函数介绍 +-- 第二章 图形输出 |------ 2.1 和GUI有关的各种对象 |------ 2.2 在窗口输出文字 |------ 2.3 使用点,刷子,笔进行绘图 |------ 2.4 在窗口绘制设备相关位图,图标,设备无关位图 |------ 2.5 使用各种映射方式 |------ 2.6 多边形和剪贴区域 +-- 第三章 文档视结构 |------ 3.1 文档 视图 框架窗口间的关系和消息传送规律 |------ 3.2 接收用户输入 |------ 3.3 使用菜单 |------ 3.4 文档,视,框架之间相互作用 |------ 3.5 利用序列化进行文件读写 |------ 3.6 MFC所提供的各种视类介绍 +-- 第四章 窗口控件 |------ 4.1 Button |------ 4.2 Static Box |------ 4.3 Edit Box |------ 4.4 Scroll Bar |------ 4.5 List Box/Check List Box |------ 4.6 Combo Box/Combo Box Ex |------ 4.7 Tree Ctrl |------ 4.8 List Ctrl |------ 4.9 Tab Ctrl |------ 4.A Tool Bar |------ 4.B Status Bar |------ 4.C Dialog Bar |------ 4.D 利用AppWizard创建并使用ToolBar StatusBar Dialog Bar |------ 4.E General Window |------ 4.F 关于WM_NOTIFY的使用方法 +-- 第五章 对话框 |------ 5.1 使用资源编辑器编辑对话框 |------ 5.2 创建有模式对话框 |------ 5.3 创建无模式对话框 |------ 5.4 在对话框进行消息映射 |------ 5.5 在对话框进行数据交换和数据检查 |------ 5.6 使用属性对话框 |------ 5.7 使用通用对话框 |------ 5.8 建立以对话框为基础的应用 |------ 5.9 使用对话框作为子窗口 +-- 第六章 网络通信开发 |------ 6.1 WinSock介绍 |------ 6.2 利用WinSock进行无连接的通信 +------ 6.3 利用WinSock建立有连接的通信   第一章 VC入门 1.1 如何学好VC 这个问题很多朋友都问过我,当然流汗是必须的,但同时如果按照某种思路进行有计划的学习就会起到更好的效果。万事开头难,为了帮助朋友们更快的掌握VC开发,下面我将自己的一点体会讲一下: 1、需要有好的C/C++基础。正所谓“磨刀不误砍柴工”,最开始接触VC时不要急于开始Windows程序开发,而是应该进行一些字符界面程序的编写。这样做的目的主要是增加对语言的熟悉程度,同时也训练自己的思维和熟悉一些在编程常犯的错误。更重要的是理解并能运用C++的各种特性,这些在以后的开发都会有很大的帮助,特别是利用MFC进行开发的朋友对C++一定要能熟练运用。 2、理解Windows的消息机制,窗口句柄和其他GUI句柄的含义和用途。了解和MFC各个类功能相近的API函数。 3、一定要理解MFC消息映射的作用。 4、训练自己在编写代码时不使用参考书而是使用Help Online。 5、记住一些常用的消息名称和参数的意义。 6、学会看别人的代码。 7、多看书,少买书,买书前一定要慎重。 8、闲下来的时候就看参考书。 9、多来我的主页。^O^ 后面几条是我个人的一点意见,你可以根据需要和自身的情况选用适用于自己的方法。 此外我将一些我在选择参考书时的原则: 对于初学者:应该选择一些内容比较全面的书籍,并且书籍的内容应该以合理的方式安排,在使用该书时可以达到循序渐进的效果,书的代码要有详细的讲解。尽量买翻译的书,因为这些书一般都比较易懂,而且语言比较轻松。买书前一定要慎重如果买到不好用的书可能会对自己的学习积极性产生击。 对于已经掌握了VC的朋友:这种程度的开发者应该加深自己对系统原理,技术要点的认识。需要选择一些对原理讲解的比较透彻的书籍,这样一来才会对新技术有更多的了解,最好书对技术的应用有一定的阐述。尽量选择示范代码必较精简的书,可以节约银子。 此外最好涉猎一些辅助性的书籍。 1.2 理解Windows消息机制 Windows系统是一个消息驱动的OS,什么是消息呢?我很难说得清楚,也很难下一个定义(谁在嘘我),我下面从不同的几个方面讲解一下,希望大家看了后有一点了解。 1、消息的组成:一个消息由一个消息名称(UINT),和两个参数(WPARAM,LPARAM)。当用户进行了输入或是窗口的状态发生改变时系统都会发送消息到某一个窗口。例如当菜单转之后会有WM_COMMAND消息发送,WPARAM的高字(HIWORD(wParam))是命令的ID号,对菜单来讲就是菜单ID。当然用户也可以定义自己的消息名称,也可以利用自定义消息来发送通知和传送数据。 2、谁将收到消息:一个消息必须由一个窗口接收。在窗口的过程(WNDPROC)可以对消息进行分析,对自己感兴趣的消息进行处理。例如你希望对菜单选择进行处理那么你可以定义对WM_COMMAND进行处理的代码,如果希望在窗口进行图形输出就必须对WM_PAINT进行处理。 3、未处理的消息到那里去了:M$为窗口编写了默认的窗口过程,这个窗口过程将负责处理那些你不处理消息。正因为有了这个默认窗口过程我们才可以利用Windows的窗口进行开发而不必过多关注窗口各种消息的处理。例如窗口在被拖动时会有很多消息发送,而我们都可以不予理睬让系统自己去处理。 4、窗口句柄:说到消息就不能不说窗口句柄,系统通过窗口句柄来在整个系统唯一标识一个窗口,发送一个消息时必须指定一个窗口句柄表明该消息由那个窗口接收。而每个窗口都会有自己的窗口过程,所以用户的输入就会被正确的处理。例如有两个窗口共用一个窗口过程代码,你在窗口一上按下鼠标时消息就会通过窗口一的句柄被发送到窗口一而不是窗口二。 5、示例:下面有一段伪代码演示如何在窗口过程处理消息 LONG yourWndProc(HWND hWnd,UINT uMessageType,WPARAM wP,LPARAM) { switch(uMessageType) { //使用SWITCH语句将各种消息分开 case(WM_PAINT): doYourWindow(...);//在窗口需要重新绘制时进行输出 break; case(WM_LBUTTONDOWN): doYourWork(...);//在鼠标左键被按下时进行处理 break; default: callDefaultWndProc(...);//对于其它情况就让系统自己处理 break; } } 接下来谈谈什么是消息机制:系统将会维护一个或多个消息队列,所有产生的消息都回被放入或是插入队列。系统会在队列取出每一条消息,根据消息的接收句柄而将该消息发送给拥有该窗口的程序的消息循环。每一个运行的程序都有自己的消息循环,在循环得到属于自己的消息并根据接收窗口的句柄调用相应的窗口过程。而在没有消息时消息循环就将控制权交给系统所以Windows可以同时进行多个任务。下面的伪代码演示了消息循环的用法: while(1) { id=getMessage(...); if(id == quit) break; translateMessage(...); } 当该程序没有消息通知时getMessage就不会返回,也就不会占用系统的CPU时间。 下图为消息投递模式 在16位的系统系统只有一个消息队列,所以系统必须等待当前任务处理消息后才可以发送下一消息到相应程序,如果一个程序陷如死循环或是耗时操作时系统就会得不到控制权。这种多任务系统也就称为协同式的多任务系统。Windows3.X就是这种系统。而32位的系统每一运行的程序都会有一个消息队列,所以系统可以在多个消息队列转换而不必等待当前程序完成消息处理就可以得到控制权。这种多任务系统就称为抢先式的多任务系统。Windows95/NT就是这种系统。 1.3 利用Visual C++/MFC开发Windows程序的优势 MFC借助C++的优势为Windows开发开辟了一片新天地,同时也借助ApplicationWizzard使开发者摆脱离了那些每次都必写基本代码,借助ClassWizard和消息映射使开发者摆脱了定义消息处理时那种混乱和冗长的代码段。更令人兴奋的是利用C++的封装功能使开发者摆脱Windows各种句柄的困扰,只需要面对C++的对象,这样一来使开发更接近开发语言而远离系统。(但我个人认为了解系统原理对开发很有帮助) 正因为MFC是建立在C++的基础上,所以我强调C/C++语言基础对开发的重要性。利用C++的封装性开发者可以更容易理解和操作各种窗口对象;利用C++的派生性开发者可以减少开发自定义窗口的时间和创造出可重用的代码;利用虚拟性可以在必要时更好的控制窗口的活动。而且C++本身所具备的超越C语言的特性都可以使开发者编写出更易用,更灵活的代码。 在MFC对消息的处理利用了消息映射的方法,该方法的基础是宏定义实现,通过宏定义将消息分派到不同的成员函数进行处理。下面简单讲述一下这种方法的实现方法: 代码如下 BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) //{{AFX_MSG_MAP(CMainFrame) ON_WM_CREATE() //}}AFX_MSG_MAP ON_COMMAND(ID_FONT_DROPDOWN, DoNothing) END_MESSAGE_MAP() 经过编译后,代码被替换为如下形式(这只是作讲解,实际情况比这复杂得多): //BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) CMainFrame::newWndProc(...) { switch(...) { //{{AFX_MSG_MAP(CMainFrame) // ON_WM_CREATE() case(WM_CREATE): OnCreate(...); break; //}}AFX_MSG_MAP // ON_COMMAND(ID_FONT_DROPDOWN, DoNothing) case(WM_COMMAND): if(HIWORD(wP)==ID_FONT_DROPDOWN) { DoNothing(...); } break; //END_MESSAGE_MAP() } } newWndProc就是窗口过程只要是该类的实例生成的窗口都使用该窗口过程。 所以了解了Windows的消息机制在加上对消息映射的理解就很容易了解MFC开发的基本思路了。 1.4 利用MFC进行开发的通用方法介绍 以下是我在最初学习VC时所常用的开发思路和方法,希望能对初学VC的朋友有所帮助和启发。 1、开发需要读写文件的应用程序并且有简单的输入和输出可以利用单文档视结构。 2、开发注重交互的简单应用程序可以使用对话框为基础的窗口,如果文件读写简单这可利用CFile进行。 3、开发注重交互并且文件读写复杂的的简单应用程序可以利用以CFormView为基础视的单文档视结构。 4、利用对话框得到用户输入的数据,在等级提高后可使用就地输入。 5、在对多文档要求不强烈时尽量避免多文档视结构,可以利用分隔条产生单文档多视结构。 6、在要求在多个文档间传递数据时使用多文档视结构。 7、学会利用子窗口,并在自定义的子窗口包含多个控件达到封装功能的目的。 8、尽量避免使用多文档多视结构。 9、不要使用多重继承并尽量减少一个类封装过多的功能。 1.5 MFC常用类,宏,函数介绍 常用类 CRect:用来表示矩形的类,拥有四个成员变量:top left bottom right。分别表是左上角和右下角的坐标。可以通过以下的方法构造: CRect( int l, int t, int r, int b ); 指明四个坐标 CRect( const RECT& srcRect ); 由RECT结构构造 CRect( LPCRECT lpSrcRect ); 由RECT结构构造 CRect( POINT point, SIZE size ); 有左上角坐标和尺寸构造 CRect( POINT topLeft, POINT bottomRight ); 有两点坐标构造 下面介绍几个成员函数: int Width( ) const; 得到宽度 int Height( ) const; 得到高度 CSize Size( ) const; 得到尺寸 CPoint& TopLeft( ); 得到左上角坐标 CPoint& BottomRight( ); 得到右下角坐标 CPoint CenterPoint( ) const; 得当心坐标 此外矩形可以和点(CPoint)相加进行位移,和另一个矩形相加得到“并”操作后的矩形。 CPoint:用来表示一个点的坐标,有两个成员变量:x y。 可以和另一个点相加。 CString:用来表示可变长度的字符串。使用CString可不指明内存大小,CString会根据需要自行分配。下面介绍几个成员函数: GetLength 得到字符串长度 GetAt 得到指定位置处的字符 operator + 相当于strcat void Format( LPCTSTR lpszFormat, ... ); 相当于sprintf Find 查找指定字符,字符串 Compare 比较 CompareNoCase 不区分大小写比较 MakeUpper 改为小写 MakeLower 改为大写 CStringArray:用来表示可变长度的字符串数组。数组每一个元素为CString对象的实例。下面介绍几个成员函数: Add 增加CString RemoveAt 删除指定位置CString对象 RemoveAll 删除数组所有CString对象 GetAt 得到指定位置的CString对象 SetAt 修改指定位置的CString对象 InsertAt 在某一位置插入CString对象 常用宏 RGB TRACE ASSERT VERIFY 常用函数 CWindApp* AfxGetApp(); HINSTANCE AfxGetInstanceHandle( ); HINSTANCE AfxGetResourceHandle( ); int AfxMessageBox( LPCTSTR lpszText, UINT nType = MB_OK, UINT nIDHelp = 0 );用于弹出一个消息框 第二章 图形输出 2.1 和GUI有关的各种对象 在Windows有各种GUI对象(不要和C++对象混淆),当你在进行绘图就需要利用这些对象。而各种对象都拥有各种属性,下面分别讲述各种GUI对象和拥有的属性。 字体对象CFont用于输出文字时选用不同风格和大小的字体。可选择的风格包括:是否为斜体,是否为粗体,字体名称,是否有下划线等。颜色和背景色不属于字体的属性。关于如何创建和使用字体在2.2 在窗口输出文字会详细讲解。 刷子CBrush对象决定填充区域时所采用的颜色或模板。对于一个固定色的刷子来讲它的属性为颜色,是否采用网格和网格的类型如水平的,垂直的,交叉的等。你也可以利用8*8的位图来创建一个自定义模板的刷子,在使用这种刷子填充时系统会利用位图逐步填充区域。关于如何创建和使用刷子在2.3 使用刷子,笔进行绘图会详细讲解。 画笔CPen对象在画点和画线时有用。它的属性包括颜色,宽度,线的风格,如虚线,实线,点划线等。关于如何创建和使用画笔在2.3 使用刷子,笔进行绘图会详细讲解。 位图CBitmap对象可以包含一幅图像,可以保存在资源。关于如何使用位图在2.4 在窗口绘制设备相关位图,图标,设备无关位图会详细讲解。 还有一种特殊的GUI对象是多边形,利用多边形可以很好的限制作图区域或是改变窗口外型。关于如何创建和使用多边形在2.6 多边形和剪贴区域会详细讲解。 在Windows使用GUI对象必须遵守一定的规则。首先需要创建一个合法的对象,不同的对象创建方法不同。然后需要将该GUI对象选入DC,同时保存DC原来的GUI对象。如果选入一个非法的对象将会引起异常。在使用完后应该恢复原来的对象,这一点特别重要,如果保存一个临时对象在DC,而在临时对象被销毁后可能引起异常。有一点必须注意,每一个对象在重新创建前必须销毁,下面的代码演示了这一种安全的使用方法: OnDraw(CDC* pDC) { CPen pen1,pen2; pen1.CreatePen(PS_SOLID,2,RGB(128,128,128));//创建对象 pen2.CreatePen(PS_SOLID,2,RGB(128,128,0));//创建对象 CPen* pPenOld=(CPen*)pDC->SelectObject(&pen1);//选择对象进DC drawWithPen1... (CPen*)pDC->SelectObject(&pen2);//选择对象进DC drawWithPen2... pen1.DeleteObject();//再次创建前先销毁 pen1.CreatePen(PS_SOLID,2,RGB(0,0,0));//再次创建对象 (CPen*)pDC->SelectObject(&pen1);//选择对象进DC drawWithPen1... pDC->SelectObject(pOldPen);//恢复 } 此外系统还拥有一些库存GUI对象,你可以利用CDC::SelectStockObject(SelectStockObject( int nIndex )选入这些对象,它们包括一些固定颜色的刷子,画笔和一些基本字体。 • BLACK_BRUSH Black brush. • DKGRAY_BRUSH Dark gray brush. • GRAY_BRUSH Gray brush. • HOLLOW_BRUSH Hollow brush. • LTGRAY_BRUSH Light gray brush. • NULL_BRUSH Null brush. • WHITE_BRUSH White brush. • BLACK_PEN Black pen. • NULL_PEN Null pen. • WHITE_PEN White pen. • ANSI_FIXED_FONT ANSI fixed system font. • ANSI_VAR_FONT ANSI variable system font. • DEVICE_DEFAULT_FONT Device-dependent font. • OEM_FIXED_FONT OEM-dependent fixed font. • SYSTEM_FONT The system font. By default, Windows uses the system font to draw menus, dialog-box controls, and other text. In Windows versions 3.0 and later, the system font is proportional width; earlier versions of Windows use a fixed-width system font. • SYSTEM_FIXED_FONT The fixed-width system font used in Windows prior to version 3.0. This object is available for compatibility with earlier versions of Windows. • DEFAULT_PALETTE Default color palette. This palette consists of the 20 static colors in the system palette. 这些对象留在DC是安全的,所以你可以利用选入库存对象来作为恢复DCGUI对象。 大家可能都注意到了绘图时都需要一个DC对象,DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows耀眼的一点设备无关性。如同你将对一幅画使用照相机或复印机将会产生不同的输出,而不需要对画进行任何调整。DC的使用会穿插在本章进行介绍。 2.2 在窗口输出文字 在这里我假定读者已经利用ApplicationWizard生成了一个SDI界面的程序代码。接下来的你只需要在CView派生类的OnDraw成员函数加入绘图代码就可以了。在这里我需要解释一下OnDraw函数的作用,OnDraw函数会在窗口需要重绘时自动被调用,传入的参数CDC* pDC对应的就是DC环境。使用OnDraw的优点就在于在你使用打印功能的时候传入OnDraw的DC环境将会是打印机绘图环境,使用打印预览时传入的是一个称为CPreviewDC的绘图环境,所以你只需要一份代码就可以完成窗口/打印预览/打印机绘图三重功能。利用Windows的设备无关性和M$为打印预览所编写的上千行代码你可以很容易的完成一个具有所见即所得的软件。 输出文字一般使用CDC::BOOL TextOut( int x, int y, const CString& str )和CDC::int DrawText( const CString& str, LPRECT lpRect, UINT nFormat )两个函数,对TextOut来讲只能输出单行的文字,而DrawText可以指定在一个矩形输出单行或多行文字,并且可以规定对齐方式和使用何种风格。nFormat可以是多种以下标记的组合(利用位或操作)以达到选择输出风格的目的。 • DT_BOTTOM底部对齐 Specifies bottom-justified text. This value must be combined with DT_SINGLELINE. • DT_CALCRECT计算指定文字时所需要矩形尺寸 Determines the width and height of the rectangle. If there are multiple lines of text, DrawText will use the width of the rectangle pointed to by lpRect and extend the base of the rectangle to bound the last line of text. If there is only one line of text, DrawText will modify the right side of the rectangle so that it bounds the last character in the line. In either case, DrawText returns the height of the formatted text, but does not draw the text. • DT_CENTER部对齐 Centers text horizontally. • DT_END_ELLIPSIS or DT_PATH_ELLIPSIS Replaces part of the given string with ellipses, if necessary, so that the result fits in the specified rectangle. The given string is not modified unless the DT_MODIFYSTRING flag is specified. You can specify DT_END_ELLIPSIS to replace characters at the end of the string, or DT_PATH_ELLIPSIS to replace characters in the middle of the string. If the string contains backslash (\) characters, DT_PATH_ELLIPSIS preserves as much as possible of the text after the last backslash. • DT_EXPANDTABS Expands tab characters. The default number of characters per tab is eight. • DT_EXTERNALLEADING Includes the font抯 external leading in the line height. Normally, external leading is not included in the height of a line of text. • DT_LEFT左对齐 Aligns text flush-left. • DT_MODIFYSTRING Modifies the given string to match the displayed text. This flag has no effect unless the DT_END_ELLIPSIS or DT_PATH_ELLIPSIS flag is specified. Note Some uFormat flag combinations can cause the passed string to be modified. Using DT_MODIFYSTRING with either DT_END_ELLIPSIS or DT_PATH_ELLIPSIS may cause the string to be modified, causing an assertion in the CString override. • DT_NOCLIP Draws without clipping. DrawText is somewhat faster when DT_NOCLIP is used. • DT_NOPREFIX禁止使用&前缀 Turns off processing of prefix characters. Normally, DrawText interprets the ampersand (&) mnemonic-prefix character as a directive to underscore the character that follows, and the two-ampersand (&&) mnemonic-prefix characters as a directive to print a single ampersand. By specifying DT_NOPREFIX, this processing is turned off. • DT_PATH_ELLIPSIS • DT_RIGHT右对齐 Aligns text flush-right. • DT_SINGLELINE单行输出 Specifies single line only. Carriage returns and linefeeds do not break the line. • DT_TABSTOP设置TAB字符所占宽度 Sets tab stops. The high-order byte of nFormat is the number of characters for each tab. The default number of characters per tab is eight. • DT_TOP定部对齐 Specifies top-justified text (single line only). • DT_VCENTER部对齐 Specifies vertically centered text (single line only). • DT_WORDBREAK每行只在单词间被折行 Specifies word-breaking. Lines are automatically broken between words if a word would extend past the edge of the rectangle specified by lpRect. A carriage return杔inefeed sequence will also break the line. 在输出文字时如果希望改变文字的颜色,你可以利用CDC::SetTextColor( COLORREF crColor )进行设置,如果你希望改变背景色就利用CDC::SetBkColor( COLORREF crColor ),很多时候你可能需要透明的背景色你可以利用CDC::SetBkMode( int nBkMode )设置,可接受的参数有 • OPAQUE Background is filled with the current background color before the text, hatched brush, or pen is drawn. This is the default background mode. • TRANSPARENT Background is not changed before drawing. 接下来讲讲如何创建字体,你可以创建的字体有两种:库存字体CDC::CreateStockObject( int nIndex )和自定义字体。 在创建非库存字体时需要填充一个LOGFONT结构并使用CFont::CreateFontIndirect(const LOGFONT* lpLogFont ),或使用CFont::CreateFont( int nHeight, int nWidth, int nEscapement, int nOrientation, int nWeight, BYTE bItalic, BYTE bUnderline, BYTE cStrikeOut, BYTE nCharSet, BYTE nOutPrecision, BYTE nClipPrecision, BYTE nQuality, BYTE nPitchAndFamily, LPCTSTR lpszFacename )其的参数和LOGFONT的分量有一定的对应关系。下面分别讲解参数的意义: nHeight 字体高度(逻辑单位)等于零为缺省高度,否则取绝对值并和可用的字体高度进行匹配。 nWidth 宽度(逻辑单位)如果为零则使用可用的横纵比进行匹配。 nEscapement 出口矢量与X轴间的角度 nOrientation 字体基线与X轴间的角度 nWeight 字体粗细,可取以下值 Constant Value FW_DONTCARE 0 FW_THIN 100 FW_EXTRALIGHT 200 FW_ULTRALIGHT 200 FW_LIGHT 300 FW_NORMAL 400 FW_REGULAR 400 FW_MEDIUM 500 FW_SEMIBOLD 600 FW_DEMIBOLD 600 FW_BOLD 700 FW_EXTRABOLD 800 FW_ULTRABOLD 800 FW_BLACK 900 FW_HEAVY 900 bItalic 是否为斜体 bUnderline 是否有下划线 cStrikeOut 是否带删除线 nCharSet 指定字符集合,可取以下值 Constant Value ANSI_CHARSET 0 DEFAULT_CHARSET 1 SYMBOL_CHARSET 2 SHIFTJIS_CHARSET 128 OEM_CHARSET 255 nOutPrecision 输出精度 OUT_CHARACTER_PRECIS OUT_STRING_PRECIS OUT_DEFAULT_PRECIS OUT_STROKE_PRECIS OUT_DEVICE_PRECIS OUT_TT_PRECIS OUT_RASTER_PRECIS nClipPrecision 剪辑精度,可取以下值 CLIP_CHARACTER_PRECIS CLIP_MASK CLIP_DEFAULT_PRECIS CLIP_STROKE_PRECIS CLIP_ENCAPSULATE CLIP_TT_ALWAYS CLIP_LH_ANGLES nQuality 输出质量,可取以下值 • DEFAULT_QUALITY Appearance of the font does not matter. • DRAFT_QUALITY Appearance of the font is less important than when PROOF_QUALITY is used. For GDI raster fonts, scaling is enabled. Bold, italic, underline, and strikeout fonts are synthesized if necessary. • PROOF_QUALITY Character quality of the font is more important than exact matching of the logical-font attributes. For GDI raster fonts, scaling is disabled and the font closest in size is chosen. Bold, italic, underline, and strikeout fonts are synthesized if necessary. nPitchAndFamily 字体间的间距 lpszFacename 指定字体名称,为了得到系统所拥有的字体可以利用EmunFontFamiliesEx。 此外可以利用CFontDialog来得到用户选择的字体的LOGFONT数据。 最后我讲一下文本坐标的计算,利用CDC::GetTextExtent( const CString& str )可以得到字符串的在输出时所占用的宽度和高度,这样就可以在手工输出多行文字时使用正确的行距。另外如果需要更精确的对字体高度和宽度进行计算就需要使用CDC::GetTextMetrics( LPTEXTMETRIC lpMetrics ) 该函数将会填充TEXTMETRIC结构,该结构的分量可以非常精确的描述字体的各种属性。 2.3 使用点,刷子,笔进行绘图 在Windows画点的方法很简单,只需要调用COLORREF CDC::SetPixel( int x, int y, COLORREF crColor )就可以在指定点画上指定颜色,同时返回原来的颜色。COLORREF CDC::GetPixel( int x, int y)可以得到指定点的颜色。在Windows应该少使用画点的函数,因为这样做的执行效率比较低。 刷子和画笔在Windows作图是使用最多的GUI对象,本节在讲解刷子和画笔使用方法的同时也讲述一写基本作图函数。 在画点或画线时系统使用当前DC的画笔,所以在创建画笔后必须将其选入DC才会在绘图时产生效果。画笔可以通过CPen对象来产生,通过调用CPen::CreatePen( int nPenStyle, int nWidth, COLORREF crColor )来创建。其nPenStyle指名画笔的风格,可取如下值: • PS_SOLID 实线 Creates a solid pen. • PS_DASH 虚线,宽度必须为一 Creates a dashed pen. Valid only when the pen width is 1 or less, in device units. • PS_DOT 点线,宽度必须为一 Creates a dotted pen. Valid only when the pen width is 1 or less, in device units. • PS_DASHDOT 点划线,宽度必须为一 Creates a pen with alternating dashes and dots. Valid only when the pen width is 1 or less, in device units. • PS_DASHDOTDOT 双点划线,宽度必须为一 Creates a pen with alternating dashes and double dots. Valid only when the pen width is 1 or less, in device units. • PS_NULL 空线,使用时什么也不会产生 Creates a null pen. • PS_ENDCAP_ROUND 结束处为圆形 End caps are round. • PS_ENDCAP_SQUARE 结束处为方形 End caps are square. nWidth和crColor为线的宽度和颜色。 刷子是在画封闭曲线时用来填充的颜色,例如当你画圆形或方形时系统会用当前的刷子对内部进行填充。刷子可利用CBrush对象产生。通过以下几种函数创建刷子: • BOOL CreateSolidBrush( COLORREF crColor ); 创建一种固定颜色的刷子 • BOOL CreateHatchBrush( int nIndex, COLORREF crColor ); 创建指定颜色和网格的刷子,nIndex可取以下值: • HS_BDIAGONAL Downward hatch (left to right) at 45 degrees • HS_CROSS Horizontal and vertical crosshatch • HS_DIAGCROSS Crosshatch at 45 degrees • HS_FDIAGONAL Upward hatch (left to right) at 45 degrees • HS_HORIZONTAL Horizontal hatch • HS_VERTICAL Vertical hatch • BOOL CreatePatternBrush( CBitmap* pBitmap ); 创建以8*8位图为模板的刷子 在选择了画笔和刷子后就可以利用Windows的作图函数进行作图了,基本的画线函数有以下几种 • CDC::MoveTo( int x, int y ); 改变当前点的位置 • CDC::LineTo( int x, int y ); 画一条由当前点到参数指定点的线 • CDC::BOOL Arc( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); 画弧线 • CDC::BOOL Polyline( LPPOINT lpPoints, int nCount ); 将多条线依次序连接 基本的作图函数有以下几种: • CDC::BOOL Rectangle( LPCRECT lpRect ); 矩形 • CDC::RoundRect( LPCRECT lpRect, POINT point ); 圆角矩形 • CDC::Draw3dRect( int x, int y, int cx, int cy, COLORREF clrTopLeft, COLORREF clrBottomRight ); 3D边框 • CDC::Chord( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); 扇形 • CDC::Ellipse( LPCRECT lpRect ); 椭圆形 • CDC::Pie( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); • CDC::Polygon( LPPOINT lpPoints, int nCount ); 多边形 对于矩形,圆形或类似的封闭曲线,系统会使用画笔绘制边缘,使用刷子填充内部。如果你不希望填充或是画出边缘,你可以选入空刷子(NULL_PEN)或是(NULL_BRUSH)空笔。 下面的代码创建一条两象素宽的实线并选入DC。并进行简单的作图: { ... CPen pen; pen.CreatePen(PS_SOLID,2,RGB(128,128,128)); CPen* pOldPen=(CPen*)dc.SelectObject(&pen); dc.SelectStockObject(NULL_BRUSH);//选入空刷子 dc.Rectangle(CRect(0,0,20,20));//画矩形 ... } 2.4 在窗口绘制设备相关位图,图标,设备无关位图 在Windows可以将预先准备好的图像复制到显示区域,这种内存拷贝执行起来是非常快的。在Windows提供了两种使用图形拷贝的方法:通过设备相关位图(DDB)和设备无关位图(DIB)。 DDB可以用MFC的CBitmap来表示,而DDB一般是存储在资源文件,在加载时只需要通过资源ID号就可以将图形装入。BOOL CBitmap::LoadBitmap( UINT nIDResource )可以装入指定DDB,但是在绘制时必须借助另一个和当前绘图DC兼容的内存DC来进行。通过CDC::BitBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop )绘制图形,同时指定光栅操作的类型。BitBlt可以将源DC位图复制到目的DC,其前四个参数为目的区域的坐标,接下来是源DC指针,然后是源DC的起始坐标,由于BitBlt为等比例复制,所以不需要再次指定长宽,(StretchBlt可以进行缩放)最后一个参数为光栅操作的类型,可取以下值: • BLACKNESS 输出区域为黑色 Turns all output black. • DSTINVERT 反色输出区域 Inverts the destination bitmap. • MERGECOPY 在源和目的间使用AND操作 Combines the pattern and the source bitmap using the Boolean AND operator. • MERGEPAINT 在反色后的目的和源间使用OR操作 Combines the inverted source bitmap with the destination bitmap using the Boolean OR operator. • NOTSRCCOPY 将反色后的源拷贝到目的区 Copies the inverted source bitmap to the destination. • PATINVERT 源和目的间进行XOR操作 Combines the destination bitmap with the pattern using the Boolean XOR operator. • SRCAND 源和目的间进行AND操作 Combines pixels of the destination and source bitmaps using the Boolean AND operator. • SRCCOPY 复制源到目的区 Copies the source bitmap to the destination bitmap. • SRCINVERT 源和目的间进行XOR操作 Combines pixels of the destination and source bitmaps using the Boolean XOR operator. • SRCPAINT 源和目的间进行OR操作 Combines pixels of the destination and source bitmaps using the Boolean OR operator. • WHITENESS 输出区域为白色 Turns all output white. 下面用代码演示这种方法: CYourView::OnDraw(CDC* pDC) { CDC memDC;//定义一个兼容DC memDC.CreateCompatibleDC(pDC);//创建DC CBitmap bmpDraw; bmpDraw.LoadBitmap(ID_BMP) ;//装入DDB CBitmap* pbmpOld=memDC.SelectObject(&bmpDraw) ; //保存原有DDB,并选入新DDB入DC pDC->BitBlt(0,0,20,20,&memDC,0,0,SRCCOPY) ; //将源DC(0,0,20,20)复制到目的DC(0,0,20,20) pDC->BitBlt(20,20,40,40,&memDC,0,0,SRCAND); //将源DC(0,0,20,20)和目的DC(20,20,40,40)区域进行AND操作 memDC.SelectObject(pbmpOld) ;//选入原DDB } (图标并不是一个GDI对象,所以不需要选入DC)在MFC没有一个专门的图标类,因为图标的操作比较简单,使用HICON CWinApp::LoadIcon( UINT nIDResource )或是HICON CWinApp::LoadStandardIcon( LPCTSTR lpszIconName ) 装入后就可以利用BOOL CDC::DrawIcon( int x, int y, HICON hIcon )绘制。由于在图标可以指定透明区域,所以在某些需要使用非规则图形而且面积不大的时候使用图标会比较简单。下面给出简单的代码: OnDraw(CDC* pDC) { HICON hIcon1=AfxGetApp()->LoadIcon(IDI_I1); HICON hIcon2=AfxGetApp()->LoadIcon(IDI_I2); pDC->DrawIcon(0,0,hIcon1); pDC->DrawIcon(0,40,hIcon2); DestroyIcon(hIcon1); DestroyIcon(hIcon2); } 同样在MFC也没有提供一个DIB的类,所以在使用DIB位图时我们需要自己读取位图文件的头信息,并读入数据,并利用API函数StretchDIBits绘制。位图文件以BITMAPFILEHEADER结构开始,然后是BITMAPINFOHEADER结构和调色版信息和数据,其实位图格式是图形格式最简单的一种,而且也是Windows可以理解的一种。我不详细讲解DIB位图的结构,提供一个CDib类供大家使用,这个类包含了基本的功能如:Load,Save,Draw。DownLoad CDib 4K 2.5 使用各种映射方式 所谓的映射方式简单点讲就是坐标的安排方式,系统默认的映射方式为MM_TEXT即X坐标向右增加,Y坐标向下增加,(0,0)在屏幕左上方,DC的每一点就是屏幕上的一个象素。也许你会认为这种方式下是最好理解的,但是一个点和象素对应的关系在屏幕上看来是正常的,但到了打印机上就会很不正常。因为我们作图是以点为单位并且打印机的分辨率远远比显示器高(800DPI 800点每英寸)所以在打印机上图形看起来就会很小。这样就需要为打印另做一套代码而加大了工作量。如果每个点对应0.1毫米那么在屏幕上的图形就会和打印出来的图形一样大小。 通过int CDC::SetMapMode( int nMapMode )可以指定映射方式,可用的有以下几种: • MM_HIENGLISH 每点对应0.001英寸 Each logical unit is converted to 0.001 inch. Positive x is to the right; positive y is up. • MM_HIMETRIC 每点对应0.001毫米 Each logical unit is converted to 0.01 millimeter. Positive x is to the right; positive y is up. • MM_LOENGLISH 每点对应0.01英寸 Each logical unit is converted to 0.01 inch. Positive x is to the right; positive y is up. • MM_LOMETRIC 每点对应0.001毫米 Each logical unit is converted to 0.1 millimeter. Positive x is to the right; positive y is up. • MM_TEXT 象素对应 Each logical unit is converted to 1 device pixel. Positive x is to the right; positive y is down. 以上几种映射默认的原点在屏幕左上方。除MM_TEXT外都为X坐标向右增加,Y坐标向上增加,和自然坐标是一致的。所以在作图是要注意什么时候应该使用负坐标。而且以上的映射都是X-Y等比例的,即相同的长度在X,Y轴上显示的长度都是相同的。 DownLoad Sample 另外的一种映射方式为MM_ANISOTROPIC,这种方式可以规定不同的长宽比例。在设置这映射方式后必须调用CSize CDC::SetWindowExt( SIZE size )和CSize CDC::SetViewportExt( SIZE size )来设定长宽比例。系统会根据两次设定的长宽的比值来确定长宽比例。下面给出一段代码比较映射前后的长宽比例: OnDraw(CDC* pDC) { CRect rcC1(200,0,400,200); pDC->FillSolidRect(rcC1,RGB(0,0,255)); pDC->SetMapMode(MM_ANISOTROPIC ); CSize sizeO; sizeO=pDC->SetWindowExt(5,5); TRACE("winExt %d %d\n",sizeO.cx,sizeO.cy); sizeO=pDC->SetViewportExt(5,10); TRACE("ViewExt %d %d\n",sizeO.cx,sizeO.cy); CRect rcC(0,0,200,200); pDC->FillSolidRect(rcC,RGB(0,128,0)); } 上面代码在映射后画出的图形将是一个长方形。 DownLoad Sample 最后讲讲视原点(viewport origin),你可以通过调用CPoint CDC::SetViewportOrg( POINT point )重新设置原点的位置,这就相对于对坐标进行了位移。例如你将原点设置在(20,20)那么原来的(0,0)就变成了(-20,-20)。 2.6 多边形和剪贴区域 多边形也是一个GDI对象,同样遵守其他GDI对象的规则,只是通常都不将其选入DC。在MFC多边形有CRgn表示。多边形用来表示一个不同与矩形的区域,和矩形具有相似的操作。如:检测某点是否在内部,并操作等。此外还得到一个包含此多边形的最小矩形。下面介绍一下多边形类的成员函数: • CreateRectRgn 由矩形创建一个多边形 • CreateEllipticRgn 由椭圆创建一个多边形 • CreatePolygonRgn 创建一个有多个点围成的多边形 • PtInRegion 某点是否在内部 • CombineRgn 两个多边形相并 • EqualRgn 两个多边形是否相等 在本节讲演多边形的意义在于重新在窗口作图时提高效率。因为引发窗口重绘的原因是某个区域失效,而失效的区域用多边形来表示。假设窗口大小为500*400当上方的另一个窗口从(0,0,10,10)移动到(20,20,30,30)这时(0,0,10,10)区域就失效了,而你只需要重绘这部分区域而不是所有区域,这样你程序的执行效率就会提高。 通过调用API函数int GetClipRgn( HDC hdc, HRGN hrgn)就可以得到失效区域,但是一般用不着那么精确而只需得到包含该区域的最小矩形就可以了,所以可以利用int CDC::GetClipBox( LPRECT lpRect )完成这一功能。 第三章 文档视结构 3.1 文档 视图 框架窗口间的关系和消息传送规律 在MFCM$引入了文档-视结构的概念,文档相当于数据容器,视相当于查看数据的窗口或是和数据发生交互的窗口。(这一结构在MFC的OLE,ODBC开发时又得到更多的拓展)因此一个完整的应用一般由四个类组成:CWinApp应用类,CFrameWnd窗口框架类,CDocument文档类,CView视类。(VC6支持创建不带文档-视的应用) 在程序运行时CWinApp将创建一个CFrameWnd框架窗口实例,而框架窗口将创建文档模板,然后有文档模板创建文档实例和视实例,并将两者关联。一般来讲我们只需对文档和视进行操作,框架的各种行为已经被MFC安排好了而不需人为干预,这也是M$设计文档-视结构的本意,让我们将注意力放在完成任务上而从界面编写解放出来。 在应用一个视对应一个文档,但一个文档可以包含多个视。一个应用只用一个框架窗口,对多文档界面来讲可能有多个MDI子窗口。每一个视都是一个子窗口,在单文档界面父窗口即是框架窗口,在多文档界面父窗口为MDI子窗口。一个多文档应用可以包含多个文档模板,一个模板定义了一个文档和一个或多个视之间的对应关系。同一个文档可以属于多个模板,但一个模板只允许定义一个文档。同样一个视也可以属于多个文档模板。(不知道我说清楚没有) 接下来看看如何在程序得到各种对象的指针: • 全局函数AfxGetApp可以得到CWinApp应用类指针 • AfxGetApp()->m_pMainWnd为框架窗口指针 • 在框架窗口:CFrameWnd::GetActiveDocument得到当前活动文档指针 • 在框架窗口:CFrameWnd::GetActiveView得到当前活动视指针 • 在视:CView::GetDocument得到对应的文档指针 • 在文档:CDocument::GetFirstViewPosition,CDocument::GetNextView用来遍历所有和文档关联的视。 • 在文档:CDocument::GetDocTemplate得到文档模板指针 • 在多文档界面:CMDIFrameWnd::MDIGetActive得到当前活动的MDI子窗口 一般来讲用户输入消息(如菜单选择,鼠标,键盘等)会先发往视,如果视未处理则会发往框架窗口。所以定义消息映射时定义在视就可以了,如果一个应用同时拥有多个视而当前活动视没有对消息进行处理则消息会发往框架窗口。 3.2 接收用户输入 在视接收鼠标输入: 鼠标消息是我们常需要处理的消息,消息分为:鼠标移动,按钮按下/松开,双击。利用ClassWizard可以轻松的添加这几种消息映射,下面分别讲解每种消息的处理。 WM_MOUSEMOVE对应的函数为OnMouseMove( UINT nFlags, CPoint point ),nFlags表明了当前一些按键的消息,你可以通过“位与”操作进行检测。 • MK_CONTROL Ctrl键是否被按下 Set if the CTRL key is down. • MK_LBUTTON 鼠标左键是否被按下 Set if the left mouse button is down. • MK_MBUTTON 鼠标间键是否被按下 Set if the middle mouse button is down. • MK_RBUTTON 鼠标右键是否被按下 Set if the right mouse button is down. • MK_SHIFT Shift键是否被按下 Set if the SHIFT key is down. point表示当前鼠标的设备坐标,坐标原点对应视左上角。 WM_LBUTTONDOWN/WM_RBUTTONDOWN(鼠标左/右键按下)对应的函数为OnLButtonDown/OnRButtonDown( UINT nFlags, CPoint point )参数意义和OnMouseMove相同。 WM_LBUTTONUP/WM_RBUTTONUP(鼠标左/右键松开)对应的函数为OnLButtonUp/OnRButtonUp( UINT nFlags, CPoint point )参数意义和OnMouseMove相同。 WM_LBUTTONDBLCLK/WM_RBUTTONDBLCLK(鼠标左/右键双击)对应的函数为OnLButtonDblClk/OnRButtonDblClk( UINT nFlags, CPoint point )参数意义和OnMouseMove相同。 下面我用一段伪代码来讲解一下这些消息的用法: 代码的作用是用鼠标拉出一个矩形 global BOOL fDowned;//是否在拉动 global CPoint ptDown;//按下位置 global CPoint ptUp;//松开位置 OnLButtonDown(UINT nFlags, CPoint point) { fDowned=TRUE; ptUp=ptDown=point; DrawRect(); ... } OnMouseMove(UINT nFlags, CPoint point) { if(fDowned) { DrawRect();//恢复上次所画的矩形 ptUp=point; DrawRect();//画新矩形 } } OnLButtonUp(UINT nFlags, CPoint point) { if(fDowned) { DrawRect();//恢复上次所画的矩形 ptUp=point; DrawRect();//画新矩形 fDowned=FALSE; } } DrawRect() {//以反色屏幕的方法画出ptDown,ptUp标记的矩形 CClientDC dc(this); MakeRect(ptDown,ptUp); SetROP(NOT); Rect(); } 坐标间转换:在以上的函数point参数对应的都是窗口的设备坐标,我们应该将设备坐标和逻辑坐标相区别,在图32_g1由于窗口使用了滚动条,所以传入的设备坐标是对应于当前窗口左上角的坐标,没有考虑是否滚动,而逻辑坐标必须考虑滚动后对应的坐标,所以我以黄线虚拟的表达一个逻辑坐标的区域。可以看得出同一点在滚动后的坐标值是不同的,这一规则同样适用于改变了映射方式的窗口,假设你将映射方式设置为每点为0.01毫米,那么设备坐标所对应的逻辑坐标也需要重新计算。进行这种转换需要写一段代码,所幸的是系统提供了进行转换的功能DC的DPtoLP,LPtoDP,下面给出代码完成由设备坐标到逻辑坐标的转换。 图32_g1 CPoint CYourView::FromDP(CPoint point) { CClientDC dc(this); CPoint ptRet=point; dc.PrepareDC();//必须先准备DC,这在使用滚动时让DC重新计算坐标 //如果你作图设置了不同的映射方式,则在下面需要设置 dc.SetMapMode(...) // dc.DPtoLP(&ptRet);//DP->LP进行转换 return ptRet; } 在图32_g1以蓝线标记的是屏幕区域,红线标记的客户区域。利用ScreenToClient,ClientToScreen可以将坐标在这两个区域间转换。 在视接收键盘输入: 键盘消息有三个:键盘被按下/松开,输入字符。其输入字符相当于直接得到用户输入的字符这在不需要处理按键细节时使用,而键盘被按下/松开在按键状态改变时发送。 WM_CHAR对应的函数为OnChar( UINT nChar, UINT nRepCnt, UINT nFlags ),其nChar为被按下的字符,nRepCnt表明在长时间为松开时相当于的按键次数,nFlags的不同位代表不同的含义,在这里一般不使用。 WM_KEYDOWN/WM_KEYUP所对应的函数为OnKeyDown/OnKeyUp( UINT nChar, UINT nRepCnt, UINT nFlags )nChar代表按键的虚拟码值,如VK_ALT为ALT键,VK_CONTROL为Ctrl键。nFlags各位的含义如下: Value Description 0? Scan code (OEM-dependent value). 8 Extended key, such as a function key or a key on the numeric keypad (1 if it is an extended key). 9?0 Not used. 11?2 Used internally by Windows. 13 Context code (1 if the ALT key is held down while the key is pressed; otherwise 0). 14 Previous key state (1 if the key is down before the call, 0 if the key is up). 15 Transition state (1 if the key is being released, 0 if the key is being pressed). 3.3 使用菜单 利用菜单接受用户命令是一很简单的交互方法,同时也是一种很有效的方法。通常菜单作为一资源存储在文件,因此我们可以在设计时就利用资源编辑器设计好一个菜单。关于使用VC设计菜单我就不再多讲了,但你在编写菜单时应该尽量在属性对话框的底部提示(Prompt)处输入文字,这虽然不是必要的,但MFC在有状态栏和工具条的情况下会使用该文字,文字的格式为“状态栏出说明\n工具条提示”。 图33_g1 我们要面临的任务是如何知道用户何时选择了菜单,他选的是什么菜单项。当用户选择了一个有效的菜单项时系统会向应用发送一个WM_COMMAND消息,在消息的参数表明来源。在MFC我们只需要进行一次映射,将某一菜单ID映射到一处理函数,图33_g2。在这里我们在CView的派生类处理菜单消息,同时我对同一ID设置两个消息映射,接下来将这两种映射的作用。 图33_g2 ON_COMMAND 映射的作用为在用户选择该菜单时调用指定的处理函数。如:ON_COMMAND(IDM_COMMAND1, OnCommand1)会使菜单被选择时调用OnCommand1成员函数。 ON_UPDATE_COMMAND_UI(IDM_COMMAND1, OnUpdateCommand1) 映射的作用是在菜单被显示时通过调用指定的函数来进行确定其状态。在这个处理函数你可以设置菜单的允许/禁止状态,其显示字符串是什么,是否在前面打钩。函数的参数为CCmdUI* pCmdUI,CCmdUI是MFC专门为更新命令提供的一个类,你可以调用 • Enable 设置允许/禁止状态 • SetCheck 设置是否在前面打钩 • SetText 设置文字 下面我讲解一个例子:我在CView派生类有一个变量m_fSelected,并且在视处理两个菜单的消息,当IDM_COMMAND1被选时,对m_fSelected进行逻辑非操作,当IDM_COMMAND2被选时出一提示;同时IDM_COMMAND1根据m_fSelected决定菜单显示的文字和是否在前面打上检查符号,IDM_COMMAND2根据m_fSelected的值决定菜单的允许/禁止状态。下面是代码和说明:下载示例代码 17K void CMenuDView::OnCommand1() { m_fSelected=!m_fSelected; TRACE("command1 selected\n"); } void CMenuDView::OnUpdateCommand1(CCmdUI* pCmdUI) { pCmdUI->SetCheck(m_fSelected);//决定检查状态 pCmdUI->SetText(m_fSelected?"当前被选":"当前未被选");//决定所显示的文字 } void CMenuDView::OnUpdateCommand2(CCmdUI* pCmdUI) {//决定是否为允许 pCmdUI->Enable(m_fSelected); } void CMenuDView::OnCommand2() {//选时给出提示 AfxMessageBox("你选了command2"); } 接下来再讲一些通过代码操纵菜单的方法,在MFC有一个类CMenu用来处理和菜单有关的功能。在生成一个CMenu对象时你需要从资源装如菜单,通过调用BOOL CMenu::LoadMenu( UINT nIDResource )进行装入,然后你就可以对菜单进行动态的修改,所涉及到的函数有: • CMenu* GetSubMenu( int nPos ) 一位置得到子菜单的指针,因为一个CMenu对象只能表示一个弹出菜单,如果菜单的某一项也为弹出菜单,就需要通过该函数获取指针。 • BOOL AppendMenu( UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL ) 在末尾添加一项,nFlag为MF_SEPARATOR表示增加一个分隔条,这样其他两个参数将会被忽略;为MF_STRING表示添加一个菜单项uIDNewItem为该菜单的ID命令值;为MF_POPUP表示添加一个弹出菜单项,这时uIDNewItem为另一菜单的句柄HMENU。lpszNewItem为菜单文字说明。 • BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL )用于在指定位置插入一菜单,位置由变量nPosition指明。如果nFlags包含MF_BYPOSITION则表明插入在nPosition位置,如果包含MF_BYCOMMAND表示插入在命令ID为nPosition的菜单处。 • BOOL ModifyMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL )用于修改某一位置的菜单,如果nFlags包含MF_BYPOSITION则表明修改nPosition位置的菜单,如果包含MF_BYCOMMAND表示修改命令ID为nPosition处的菜单。 • BOOL RemoveMenu( UINT nPosition, UINT nFlags )用于删除某一位置的菜单。如果nFlags包含MF_BYPOSITION则表明删除nPosition位置的菜单,如果包含MF_BYCOMMAND表示删除命令ID为nPosition处的菜单。 • BOOL AppendMenu( UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp ) 和 BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp )可以添加一位图菜单,但这样的菜单在选时只是反色显示,并不美观。 视图是没有菜单的,在框架窗口才有,所以只有用AfxGetApp()->m_pMainWnd->GetMenu()才能得到应用的菜单指针。 最后我讲一下如何在程序弹出一个菜单,你必须先装入一个菜单资源,你必需得到一个弹出菜单的指针然后调用BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL )弹出菜单,你需要指定(x,y)为菜单弹出的位置,pWnd为接收命令消息的窗口指针。下面有一段代码说明方法,下载示例代码 17K。当然为了处理消息你应该在pWnd指明的窗口对菜单命令消息进行映射。 CMenu menu; menu.LoadMenu(IDR_POPUP); CMenu* pM=menu.GetSubMenu(0); CPoint pt; GetCursorPos(&pt); pM->TrackPopupMenu(TPM_LEFTALIGN,pt.x,pt.y,this); 另一种做法是通过CMenu::CreatePopupMenu()建立一个弹出菜单,然后使用TrackPopupMenu弹出菜单。使用CreatePopupMenu创建的菜单也可以将其作为一个弹出项添加另一个菜单。下面的伪代码演示了如何创建一个弹出菜单并进行修改后弹出: CMenu menu1,menu2; menu1.CreatePopupMenu menu1.InsertMenu(1) menu1.InsertMenu(2) menu1.InsertMenu(3) menu2.CreatePopupMenu menu2.AppendMenu(MF_POPUP,1,menu1.Detach()) 将弹出菜单加入 or InsertMenu... menu2.InsertMenu("string desc"); menu.TrackPopupMenu(...) 3.4 文档,视,框架之间相互作用 一般来说用户的输入/输出基本都是通过视进行,但一些例外的情况下可能需要和框架直接发生作用,而在多视的情况下如何在视之间传递数据。 在使用菜单时大家会发现当一个菜单没有进行映射处理时为禁止状态,在多视的情况下菜单的状态和处理映射是和当前活动视相联系的,这样MFC可以保证视能正确的接收到各种消息,但有时候也会产生不便。有一个解决办法就是在框架对消息进行处理,这样也可以保证当前文档可以通过框架得到当前消息。 在用户进行输入后如何使视的状态得到更新?这个问题在一个文档对应一个视图时是不存在的,但是现在有一个文档对应了两个视图,当在一个视上进行了输入时如何保证另一个视也得到通知呢?MFC的做法是利用文档来处理的,因为文档管理着当前和它联系的视,由它来通知各个视是最合适的。让我们同时看两个函数: • void CView::OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint ) • void CDocument::UpdateAllViews( CView* pSender, LPARAM lHint = 0L, CObject* pHint = NULL ) 当文档的UpdateAllViews被调用时和此文档相关的所有视的OnUpdate都会被调用,而参数lHint和pHint都会被传递。这样一来发生改变视就可以通知其他的兄弟了。那么还有一个问题:如何在OnUpdate知道是那个视图发生了改变呢,这就可以利用pHint参数,只要调用者将this指针赋值给参数就可以了,当然完全可以利用该参数传递更复杂的结构。 视的初始化,当一个文档被打开或是新建一个文档时视图的CView::OnInitialUpdate()会被调用,你可以通过重载该函数对视进行初始化,并在结束前调用父类的OnInitialUpdate,因为这样可以保证OnUpdate会被调用。 文档内容的清除,当文档被关闭时(比如退出或是新建前上一个文档清除)void CDocument::DeleteContents ()会被调用,你可以通过重载该函数来进行清理工作。 在单文档结构上面两点尤其重要,因为软件运行文档对象和视对象只会被产生并删除一次。所以应该将上面两点和C++对象构造和构析分清楚。 最后将一下文档模板(DocTemplate)的作用,文档模板分为两类单文档模板和多文档模板,分别由CSingleDocTemplate和CMultiDocTemplate表示,模板的作用在于记录文档,视,框架之间的对应关系。还有一点就是模板可以记录应用程序可以打开的文件的类型,当打开文件时会根据文档模板的信息选择正确的文档和视。模板是一个比较抽想的概念,一般来说是不需要我们直接进行操作的。 当使用者通过视修改了数据时,应该调用GetDocument()->SetModifiedFlag(TRUE)通知文档数据已经被更新,这样在关闭文档时会自动询问用户是否保存数据。 好象这一节讲的有些乱,大家看后有什么想法和问题请在VCHelp论坛上留言,我会尽快回复并且会对本节内容重新整理和修改。 3.5 利用序列化进行文件读写 在很多应用我们需要对数据进行保存,或是从介质上读取数据,这就涉及到文件的操作。我们可以利用各种文件存取方法完成这些工作,但MFC也提供了一种读写文件的简单方法——“序列化”。序列化机制通过更高层次的接口功能向开发者提供了更利于使用和透明于字节流的文件操纵方法,举一个例来讲你可以将一个字串写入文件而不需要理会具体长度,读出时也是一样。你甚至可以对字符串数组进行操作。在MFC提供的可自动分配内存的类的支持下你可以更轻松的读/写数据。你也可以根据需要编写你自己的具有序列化功能的类。 序列化在最低的层次上应该被需要序列化的类支持,也就是说如果你需要对一个类进行序列化,那么这个类必须支持序列化。当通过序列化进行文件读写时你只需要该类的序列化函数就可以了。 怎样使类具有序列化功能呢?你需要以下的工作: • 该类从CObject派生。 • 在类声明包括DECLARE_SERIAL宏定义。 • 提供一个缺省的构造函数。 • 在类实现Serialze函数 • 使用IMPLEMENT_SERIAL指明类名和版本号 下面的代码建立了一个简单身份证记录的类,同时也能够支持序列化。 in H struct strPID { char szName[10]; char szID[16]; struct strPID* pNext; }; class CAllPID : public CObject { public: DECLARE_SERIAL(CAllPID) CAllPID(); ~CAllPID(); public:// 序列化相关 struct strPID* pHead; //其他的成员函数 void Serialize(CArchive& ar); }; in CPP IMPLEMENT_SERIAL(CAllPID,CObject,1) // version is 1,版本用于读数据时的检测 void CAllPID::Serialize(CArchive& ar) { int iTotal; if(ar.IsStoring()) {//保存数据 iTotal=GetTotalID();//得到链表的记录数量 arr<26;i++) ar<>iTotal; for(int i=0;i26;j++) ar>>*(((BYTE*)pID)+j);//读一个strPID所有的数据 //修改链表 } } } 当然上面的代码很不完整,但已经可以说明问题。这样CAllPID就是一个可以支持序列化的类,并且可以根据记录的数量动态分配内存。在序列化我们使用了CArchive类,该类用于在序列化时提供读写支持,它重载了<>运算符号,并且提供Read和Write函数对数据进行读写。 下面看看如何在文档使用序列化功能,你只需要修改文档类的Serialize(CArchive& ar)函数,并调用各个进行序列化的类的Serial进行数据读写就可以了。当然你也可以在文档类的内部进行数据读写,下面的代码利用序列化功能读写数据: class CYourDoc : public CDocument { void Serialize(CArchive& ar); CString m_szDesc; CAllPID m_allPID; ...... } void CYourDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) {//由于CString对CArchive定义了<>操作符号,所以可以直接利用>>和<< ar<>m_szDesc; } m_allPID.Serialize(ar);//调用数据类的序列化函数 3.6 MFC所提供的各种视类介绍 MFC提供了丰富的视类供开发者使用,下面对各个类进行介绍: CView类是最基本的视类只支持最基本的操作。 CScrollView类提供了滚动的功能,你可以利用void CScrollView::SetScrollSizes( int nMapMode, SIZE sizeTotal, const SIZE& sizePage = sizeDefault, const SIZE& sizeLine = sizeDefault )设置滚动尺寸,和坐标映射模式。但是在绘图和接收用户输入时需要对坐标进行转换。请参见3.2 接收用户输入。 CFormView类提供用户在资源文件定义界面的能力,并可以将子窗口和变量进行绑定。通过UpdateData函数让数据在变量和子窗口间交换。 CTreeView类利用TreeCtrl界面作为视界面,通过调用CTreeCtrl& CTreeView::GetTreeCtrl( ) const得到CTreeCtrl的引用。 CListView类利用ListCtrl界面作为视界面,通过调用CTreeCtrl& CTreeView::GetTreeCtrl( ) const得到CListCtrl的引用。 CEditView类利用Edit接收用户输入,它具有输入框的一切功能。通过调用CEdit& CEditView::GetEditCtrl( ) const得到Edit&的引用。void CEditView::SetPrinterFont( CFont* pFont )可以设置打印字体。 CRichEditView类作为Rich Text Edit(富文本输入)的视类,提供了可以按照格式显示文本的能力,在使用时需要CRichEditDoc的支持。 第四章 窗口控件 4.1 Button 按钮窗口(控件)在MFC使用CButton表示,CButton包含了三种样式的按钮,Push Button,Check Box,Radio Box。所以在利用CButton对象生成按钮窗口时需要指明按钮的风格。 创建按钮:BOOL CButton::Create( LPCTSTR lpszCaption, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );其lpszCaption是按钮上显示的文字,dwStyle为按钮风格,除了Windows风格可以使用外(如WS_CHILD|WS_VISUBLE|WS_BORDER)还有按钮专用的一些风格。 • BS_AUTOCHECKBOX 检查框,按钮的状态会自动改变 Same as a check box, except that a check mark appears in the check box when the user selects the box; the check mark disappears the next time the user selects the box. • BS_AUTORADIOBUTTON 圆形选择按钮按钮的状态会自动改变 Same as a radio button, except that when the user selects it, the button automatically highlights itself and removes the selection from any other radio buttons with the same style in the same group. • BS_AUTO3STATE 允许按钮有三种状态即:选,未选,未定 Same as a three-state check box, except that the box changes its state when the user selects it. • BS_CHECKBOX 检查框 Creates a small square that has text displayed to its right (unless this style is combined with the BS_LEFTTEXT style). • BS_DEFPUSHBUTTON 默认普通按钮 Creates a button that has a heavy black border. The user can select this button by pressing the ENTER key. This style enables the user to quickly select the most likely option (the default option). • BS_LEFTTEXT 左对齐文字 When combined with a radio-button or check-box style, the text appears on the left side of the radio button or check box. • BS_OWNERDRAW 自绘按钮 Creates an owner-drawn button. The framework calls the DrawItem member function when a visual aspect of the button has changed. This style must be set when using the CBitmapButton class. • BS_PUSHBUTTON 普通按钮 Creates a pushbutton that posts a WM_COMMAND message to the owner window when the user selects the button. • BS_RADIOBUTTON 圆形选择按钮 Creates a small circle that has text displayed to its right (unless this style is combined with the BS_LEFTTEXT style). Radio buttons are usually used in groups of related but mutually exclusive choices. • BS_3STATE 允许按钮有三种状态即:选,未选,未定 Same as a check box, except that the box can be dimmed as well as checked. The dimmed state typically is used to show that a check box has been disabled. rect为窗口所占据的矩形区域,pParentWnd为父窗口指针,nID为该窗口的ID值。 获取/改变按钮状态:对于检查按钮和圆形按钮可能有两种状态,选和未选,如果设置了BS_3STATE或BS_AUTO3STATE风格就可能出现第三种状态:未定,这时按钮显示灰色。通过调用int CButton::GetCheck( ) 得到当前是否被选,返回0:未选,1:选,2:未定。调用void CButton::SetCheck( int nCheck );设置当前选状态。 处理按钮消息:要处理按钮消息需要在父窗口进行消息映射,映射宏为ON_BN_CLICKED( id, memberFxn )id为按钮的ID值,就是创建时指定的nID值。处理函数原型为afx_msg void memberFxn( ); 4.2 Static Box 静态文本控件的功能比较简单,可作为显示字符串,图标,位图用。创建一个窗口可以使用成员函数: BOOL CStatic::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff ); 其dwStyle将指明该窗口的风格,除了子窗口常用的风格WS_CHILD,WS_VISIBLE外,你可以针对静态控件指明专门的风格。 • SS_CENTER,SS_LEFT,SS_RIGHT 指明字符显示的对齐方式。 • SS_GRAYRECT 显示一个灰色的矩形 • SS_NOPREFIX 如果指明该风格,对于字符&将直接显示,否则&将作为转义符,&将不显示而在其后的字符将有下划线,如果需要直接显示&必须使用&&表示。 • SS_BITMAP 显示位图 • SS_ICON 显示图标 • SS_CENTERIMAGE 图象居显示 控制显示的文本利用成员函数SetWindowText/GetWindowText用于设置/得到当前显示的文本。 控制显示的图标利用成员函数SetIcon/GetIcon用于设置/得到当前显示的图标。 控制显示的位图利用成员函数SetBitmap/GetBitmap用于设置/得到当前显示的位图。下面一段代码演示如何创建一个显示位图的静态窗口并设置位图 CStatic* pstaDis=new CStatic; pstaDis->Create("",WS_CHILD|WS_VISIBLE|SS_BITMAP|SSCENTERIMAGE, CRect(0,0,40,40),pWnd,1); CBitmap bmpLoad; bmpLoad.LoadBitmap(IDB_TEST); pstaDis->SetBitmap(bmpLoad.Detach()); 4.3 Edit Box Edit窗口是用来接收用户输入最常用的一个控件。创建一个输入窗口可以使用成员函数: BOOL CEdit::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff ); 其dwStyle将指明该窗口的风格,除了子窗口常用的风格WS_CHILD,WS_VISIBLE外,你可以针对输入控件指明专门的风格。 • ES_AUTOHSCROLL,ES_AUTOVSCROLL 指明输入文字超出显示范围时自动滚动。 • ES_CENTER,ES_LEFT,ES_RIGHT 指定对齐方式 • ES_MULTILINE 是否允许多行输入 • ES_PASSWORD 是否为密码输入框,如果指明该风格则输入的文字显示为* • ES_READONLY 是否为只读 • ES_UPPERCASE,ES_LOWERCASE 显示大写/小写字符 控制显示的文本利用成员函数SetWindowText/GetWindowText用于设置/得到当前显示的文本。 通过GetLimitText/SetLimitText可以得到/设置在输入框输入的字符数量。 由于在输入时用户可能选择某一段文本,所以通过void CEdit::GetSel( int& nStartChar, in
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值