如需改变标题(Caption)时,使之换行可以用c++转义符\n
vc++中不能改变单个控件的字体,可以使用位图改变

 

应用程序的一般组成:应用程序对象和窗口对象。其中没个MFC程序必须有一个应用程序对象,负责启动和终止应用程序。
文档类:用于管理应用程序的数据

视图类:用于显示应用程序的数据

组件:专用的,自包含的窗口对象,它们通常是构成用户界面的元素。也称为控件。
组件必须包含在其他的窗口中,把该窗口成为斧窗口。所以控件也叫做子窗口控件。
MFC的六种嵌入窗口家族:
CStatic--用于显示文本或图标控件
CButton--按钮、复选框、单选按钮、组框
CListBox--显示项目滚动列表的控件
CComboBox--显示可缩回的项目列表的控件
CScrollBar--水平或垂直滚动条
CEdit--单行或多行文本编辑控件

资源:特殊形式的只读数据,由一个叫做资源编译器的程序将之联编到可执行程序中。
资源的两种基本形式:
二进制--图形资源,包括图标、光标和位图
文本--结构化的资源,包括对话框、菜单、字符串表和加速键表。
二进制资源存储在一个独立的文件中,文本资源存储在普通的ASCII文本文件中,该文件描述了每个元素的结构。该ASSCII文件称为资源描述文件--通常与你的项目同名,其扩展名为.rc


DECLARE_MESSAGE_MAP()该宏告诉VisualC++这个类将响应Windows消息,该入口以及在实现文件中的相应映射入口,是用于建立消息映射的高级宏系统的部分。消息映射保证Windows消息被交给正确的成员函数。

#include "stdafx.h"是所有MFC程序都要用到的标准应用程序框架头文件。它引入用语标准MFC组件、大多数通用扩展以及Internet Explorer4常用控件的定义。

正式版和调试版的转换:使用Build|Set Active Configuration.

构造函数和InitInstance()函数:
构造函数是在对象创建是调用的。InitInstance()是在WinMain()调用时重载的。所以够找函数是在WinMain()被调用前调用的。此时,很多MFC系统还没有完成自身的初始化,只有在主应用程序对象被构造完毕,WinMain()才会被调用。

在一个API应用程序中,WinMain()函数有三个任务,它必须完成:
1、注册一个新的主窗口类
2、创建一个窗口的实例并显示它
3、运行消息循环
InitInstance()函数为MFC的WinMain()函数执行以上内容的第二项内容。

关于CWinApp
MFC中CWinApp类定义的关键几个虚函数或可重载函数
1、InitInstance()肯定是要被重载的
2、Run()扫描消息并处理消息。
3、OnIdlc(),当Run()在没有消息要处理事就要调用该函数。OnIdle()可利用该机会执行后台任务,否则该后台任务会降低系统反应速度。
4、ExitInstance(),当一个应用程序结束时由Run()进行调用。

基于CDialog的窗口是以局部变量的形式在栈中创建的,而基于CWnd的窗口是以动态变量的形式在自由存储器(freestore)中创建的 。
故有对于基于CDialog类:
CFourUpdlg dlg;
m_pMainWnd=&dlg;
对于基于CWnd的类:
m_pMainWnd=new CMainWindow;

CFourUpDlg::CFourUpDlg(CWnd* pParent /*=NULL*/)
 : CDialog(CFourUpDlg::IDD, pParent)
构造函数中CFourUpDlg::IDD的 IDD是个枚举量(定义在头文件中),包含对话框模板的资源ID(包含在资源描述中)。表示CDialog构造函数从对话框模板中读出信息,然后构造一个窗口,在构造过程使用的是由模板给出的规范。为了创建对话框窗口(并且所有的控件都包含在对话框模板中),CDialog构造函数使用CWnd::Create()或CWnd::CreateEx()函数,对主对话框调用该函数一次并对它碰到的每个控件都调用一次。

CWnd:Create()函数
原型:
virtual BOOL Create(LPCTSTR lpszClassName,LPCTSTR lpszWindowName,DWORD dwStyle,const RECT & rect,UINT nID,CCreateContext * pContext=NULL);


在Windows中,每个窗口都分成两大部分:非客户区(包括标题条,窗口边界)和客户区。
其中Windows负责非客户区的绘制

每当Windows想绘制一个窗口的客户区时,Windows便向该窗口发送WM_PAINT消息,通常由OnPaint()成员函数。
OnPaint()大致分为:
1.获取画布或绘制平面,在Windows中,使用的绘制平面称为设备环境(device context,DC).
2.建立环境,包括收集所有需要的画笔和画刷,并测量工作平面的大小,以便能够正确地在改平面上对其图案.
3.用Windows图形库GDI中地函数绘制窗口

只可以在OnPaint()中创建CPaintDC对象.
需要在其他地方绘制时用CClientDC
调用CWnd::Invalidate()可以重画整个窗口.

画笔:用来画线或图形边框的颜色
刷子:用于填充图形或绘制窗口背景的颜色
库存对象(stock object):Windows提供的几种嵌入式的画笔和刷子.可以通过SelectStockObject()选择.

定时器创建:SetTimer(),它有三个函数:
1.定时器ID--区别不同的定时器实例.
2.定时器间隔--最大分辨率55毫秒
3.定时器回调函数--特殊的回调函数的地址,该函数用于处理定时器消息.如果值为NULL,Windows将向你通知WM_TIMER消息.
在基于对话框的应用程序中,OnInitDialog()函数是最佳的创建定时器的地方.如果创建成功,SetTimer()函数返回的定时器ID于所使用的函数相同.
删除定时器:
KillTimer()向之传递构造定时器所使用的ID.定时器是个受限的全局资源.当用完定时器后,调KillTimer()是个必须的过程.通常可以相应WM_DESTORY消息时处理KillTimer()函数
定时器的相应:
当计数器溢出时,产生一个WM_TIMER消息,通常在OnTimer()处理WM_TIMER消息


创建画笔:
第一种:使用构造函数,如:CPen greePen(PS_SOLID,10,RGB(0,255,0));
第二种:两步法
CPen greenPen;
greePen.CreatePen(PS_SOLID,10,RGB(0,255,0));
第三中:创建LOGPEN结构的实例来构造
LOGPEN lp;
lp.lopnstyle=PS_SDLID;
lp.lopnWidth=10;
lp.lopnColor=RGB(0,255,0);
Cpen greenPen;
GreenPen.CreatePenIndirect(&lp);

posted @ 2008-12-15 21:20 wrh 阅读(253) | 评论 (0)编辑 收藏

函数:fopen()


fopen

打开文件或者 URL。

语法: int fopen(string filename, string mode);

返回值: 整数

函数种类: 文件存取

内容说明

说明: 本函数可用来打开本地或者远端的文件。若参数 filename 为 "http://......" 则本函数利用 HTTP 1.0 协议与服务器连接,文件指针则指到服务器返回文件的起始处。若参数 filename 为 "ftp://......." 则本函数会与服务器连接,文件指针指到指定的文件处。若 FTP 服务器没有支持被动模式 (passive mode ftp) 则返回失败值。打开的 FTP 文件可以是读取或写入其中之一,但不能读或写二种同时使用。其它的情形,本函数打开本地的文件,文件的指针则指向打开的文件。若开文件失败,则返回 false 值。

字符串参数 mode 可以是下列的情形:

  • 'r' 开文件方式为只读,文件指针指到开始处。
  • 'r+' 开文件方式为可读写,文件指针指到开始处。
  • 'w' 开文件方式为写入,文件指针指到开始处,并将原文件的长度设为 0。若文件不存在,则建立新文件。
  • 'w+' 开文件方式为可读写,文件指针指到开始处,并将原文件的长度设为 0。若文件不存在,则建立新文件。
  • 'a' 开文件方式为写入,文件指针指到文件最后。若文件不存在,则建立新文件。
  • 'a+' 开文件方式为可读写,文件指针指到文件最后。若文件不存在,则建立新文件。
  • 'b' 若操作系统的文字及二进位文件不同,则可以用此参数,UNIX 系统不需要使用本参数。

使用范例

第一行为 UNIX 系统使用;第二行是 Windows 系列系统的用法;第三、四行则为 URL 的使用范例。

<?       
$fp 
fopen("/home/rasmus/file.txt""r");
$fp fopen("c:\\mydata\\info.txt""r");
$fp fopen("http://www.php.net/""r");
$fp fopen("ftp://user:password@my.com/""w");
?>

参考

fclose()  popen()  fsockopen()

posted @ 2008-12-13 19:21 wrh 阅读(476) | 评论 (0)编辑 收藏
---- 方法一:调用CWinApp类的成员函数SetDialogBkColor来实现。
---- 其中函数的第一个参数指定了背景颜色,第二个参数指定了文本颜色。下面的例子是将应用程序对话框设置为蓝色背景和红色文本,步骤如下:
---- ① 新建一个基于Dialog的MFC AppWizard应用程序ExampleDlg。
---- ② 在CExampleDlgApp ::InitInstance()中添加如下代码:
BOOL CExampleDlgApp: : InitInstance ( )
{
...
    CExampleDlgDlg dlg;
    m_pMainWnd = &dlg;
//先于DoModal()调用,将对话框设置为蓝色背景、红色文本
    SetDialogBkColor(RGB(0,0,255),RGB(255,0,0));
    int nResponse = dlg.DoModal();
...
}
---- 编译并运行,此时对话框的背景色和文本色已发生了改变。值得注意的是:在调用DoModal()之前必须先调用SetDialogBkColor,且此方法是将改变应用程序中所有的对话框颜色,并不能针对某一个指定的对话框。
---- 方法二:重载OnPaint(),即WM_PAINT消息。有关代码如下(以上例工程为准):
void CExampleDlgDlg::OnPaint()
{
    if (IsIconic())
...
  else
  {
        CRect rect;
        CPaintDC dc(this);
        GetClientRect(rect);
        dc.FillSolidRect(rect,RGB(0,255,0));  //设置为绿色背景
        CDialog::OnPaint();
  }
---- 方法三:重载OnCtlColor (CDC* pDC, CWnd* pWnd, UINT nCtlColor),即WM_CTLCOLOR消息。具体步骤如下(以上例工程为准):
---- ①在CExampleDlgDlg的头文件中,添加一CBrush的成员变量:
class CExampleDlgDlg : public CDialog
{
...
protected:
CBrush m_brush;
...
};
---- ②在OnInitDialog()函数中添加如下代码:
BOOL CExampleDlgDlg::OnInitDialog()
{
...
// TODO: Add extra initialization here
m_brush.CreateSolidBrush(RGB(0, 255, 0)); // 生成一绿色刷子
...
}
---- ③利用ClassWizard重载OnCtlColor(...),即WM_CTLCOLOR消息:
HBRUSH CExampleDlgDlg::OnCtlColor
(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
/*
** 这里不必编写任何代码!
**下行代码要注释掉
** HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
*/
return m_brush;  //返加绿色刷子
}
---- 方法四:还是重载OnCtlColor (CDC* pDC, CWnd* pWnd, UINT nCtlColor),即WM_CTLCOLOR消息。具体步骤如下(以上例工程为准):
---- 步骤①、②同上方法三中的步骤①、②。
---- 步骤③利用ClassWizard重载OnCtlColor(...)(即WM_CTLCOLOR消息)时则有些不同:
HBRUSH CExampleDlgDlg::OnCtlColor
(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
//在这加一条是否为对话框的判断语句
if(nCtlColor ==CTLCOLOR_DLG)
return m_brush;  //返加绿色刷子
return hbr;
}
---- 编译并运行即可。
posted @ 2008-11-05 16:01 wrh 阅读(1666) | 评论 (0)编辑 收藏

  1. MFC和Win32

     

    1. MFC Object和Windows Object的关系

       

MFC中最重要的封装是对Win32 API的封装,因此,理解Windows Object和MFC Object (C++对象,一个C++类的实例)之间的关系是理解MFC的关键之一。所谓Windows Object(Windows对象)是Win32下用句柄表示的Windows操作系统对象;所谓MFC Object (MFC对象)是C++对象,是一个C++类的实例,这里(本书范围内)MFC Object是有特定含义的,指封装Windows Object的C++ Object,并非指任意的C++ Object。

MFC Object 和Windows Object是不一样的,但两者紧密联系。以窗口对象为例:

一个MFC窗口对象是一个C++ CWnd类(或派生类)的实例,是程序直接创建的。在程序执行中它随着窗口类构造函数的调用而生成,随着析构函数的调用而消失。而Windows窗口则是Windows系统的一个内部数据结构的实例,由一个“窗口句柄”标识,Windows系统创建它并给它分配系统资源。Windows窗口在MFC窗口对象创建之后,由CWnd类的Create成员函数创建,“窗口句柄”保存在窗口对象的m_hWnd成员变量中。Windows窗口可以被一个程序销毁,也可以被用户的动作销毁。MFC窗口对象和Windows窗口对象的关系如图2-1所示。其他的Windows Object和对应的MFC Object也有类似的关系。

下面,对MFC Object和Windows Object作一个比较。有些论断对设备描述表(MFC类是CDC,句柄是HDC)可能不适用,但具体涉及到时会指出。

  1. 从数据结构上比较

     

    MFC Object是相应C++类的实例,这些类是MFC或者程序员定义的;

    Windows Object是Windows系统的内部结构,通过一个句柄来引用;

    MFC给这些类定义了一个成员变量来保存MFC Object对应的Windows Object的句柄。对于设备描述表CDC类,将保存两个HDC句柄。

  2. 从层次上讲比较

     

    MFC Object是高层的,Windows Object是低层的;

    MFC Object封装了Windows Object的大部分或全部功能,MFC Object的使用者不需要直接应用Windows Object的HANDLE(句柄)使用Win32 API,代替它的是引用相应的MFC Object的成员函数。

  3. 从创建上比较

     

    MFC Object通过构造函数由程序直接创建;Windows Object由相应的SDK函数创建。

    MFC中,使用这些MFC Object,一般分两步:

    首先,创建一个MFC Object,或者在STACK中创建,或者在HEAP中创建,这时,MFC Object的句柄实例变量为空,或者说不是一个有效的句柄。

    然后,调用MFC Object的成员函数创建相应的Windows Object,MFC的句柄变量存储一个有效句柄。

    CDC(设备描述表类)的创建有所不同,在后面的2.3节会具体说明CDC及其派生类的创建和使用。

    当然,可以在MFC Object的构造函数中创建相应的Windows对象,MFC的GDI类就是如此实现的,但从实质上讲,MFC Object的创建和Windows Object的创建是两回事。

  4. 从转换上比较

     

    可以从一个MFC Object得到对应的Windows Object的句柄;一般使用MFC Object的成员函数GetSafeHandle得到对应的句柄。

    可以从一个已存在的Windows Object创建一个对应的MFC Object; 一般使用MFC Object的成员函数Attach或者FromHandle来创建,前者得到一个永久性对象,后者得到的可能是一个临时对象。

  5. 从使用范围上比较

     

    MFC Object对系统的其他进程来说是不可见、不可用的;而Windows Object一旦创建,其句柄是整个Windows系统全局的。一些句柄可以被其他进程使用。典型地,一个进程可以获得另一进程的窗口句柄,并给该窗口发送消息。

    对同一个进程的线程来说,只可以使用本线程创建的MFC Object,不能使用其他线程的MFC Object。

  6. 从销毁上比较

     

MFC Object随着析构函数的调用而消失;但Windows Object必须由相应的Windows系统函数销毁。

设备描述表CDC类的对象有所不同,它对应的HDC句柄对象可能不是被销毁,而是被释放。

当然,可以在MFC Object的析构函数中完成Windows Object的销毁,MFC Object的GDI类等就是如此实现的,但是,应该看到:两者的销毁是不同的。

每类Windows Object都有对应的MFC Object,下面用表格的形式列出它们之间的对应关系,如表2-1所示:

表2-1 MFC Object和Windows Object的对应关系

描述

Windows句柄

MFC Object

窗口

HWND

CWnd and CWnd-derived classes

设备上下文

HDC

CDC and CDC-derived classes

菜单

HMENU

CMenu

HPEN

CGdiObject类,CPen和CPen-derived classes

刷子

HBRUSH

CGdiObject类,CBrush和CBrush-derived classes

字体

HFONT

CGdiObject类,CFont和CFont-derived classes

位图

HBITMAP

CGdiObject类,CBitmap和CBitmap-derived classes

调色板

HPALETTE

CGdiObject类,CPalette和CPalette-derived classes

区域

HRGN

CGdiObject类,CRgn和CRgn-derived classes

图像列表

HimageLIST

CimageList和CimageList-derived classes

套接字

SOCKET

CSocket,CAsynSocket及其派生类

 

 


表2-1中的OBJECT分以下几类:

 

Windows对象,

设备上下文对象,

GDI对象(BITMAP,BRUSH,FONT,PALETTE,PEN,RGN),

菜单,

图像列表,

网络套接字接口。

从广义上来看,文档对象和文件可以看作一对MFC Object和Windows Object,分别用CDocument类和文件句柄描述。

后续几节分别对前四类作一个简明扼要的论述。

    1. Windows Object

       

      用SDK的Win32 API编写各种Windows应用程序,有其共同的规律:首先是编写WinMain函数,编写处理消息和事件的窗口过程WndProc,在WinMain里头注册窗口(Register Window),创建窗口,然后开始应用程序的消息循环。

      MFC应用程序也不例外,因为MFC是一个建立在SDK API基础上的编程框架。对程序员来说所不同的是:一般情况下,MFC框架自动完成了Windows登记、创建等工作。

      下面,简要介绍MFC Window对Windows Window的封装。

      1. Windows的注册

         

一个应用程序在创建某个类型的窗口前,必须首先注册该“窗口类”(Windows Class)。注意,这里不是C++类的类。Register Window把窗口过程、窗口类型以及其他类型信息和要登记的窗口类关联起来。

  1. “窗口类”的数据结构

     

    “窗口类”是Windows系统的数据结构,可以把它理解为Windows系统的类型定义,而Windows窗口则是相应“窗口类”的实例。Windows使用一个结构来描述“窗口类”,其定义如下:

    typedef struct _WNDCLASSEX {

    UINT cbSize; //该结构的字节数

    UINT style; //窗口类的风格

    WNDPROC lpfnWndProc; //窗口过程

    int cbClsExtra;

    int cbWndExtra;

    HANDLE hInstance; //该窗口类的窗口过程所属的应用实例

    HICON hIcon; //该窗口类所用的像标

    HCURSOR hCursor; //该窗口类所用的光标

    HBRUSH hbrBackground; //该窗口类所用的背景刷

    LPCTSTR lpszMenuName; //该窗口类所用的菜单资源

    LPCTSTR lpszClassName; //该窗口类的名称

    HICON hIconSm; //该窗口类所用的小像标

    } WNDCLASSEX;

    从“窗口类”的定义可以看出,它包含了一个窗口的重要信息,如窗口风格、窗口过程、显示和绘制窗口所需要的信息,等等。关于窗口过程,将在后面消息映射等有关章节作详细论述。

    Windows系统在初始化时,会注册(Register)一些全局的“窗口类”,例如通用控制窗口类。应用程序在创建自己的窗口时,首先必须注册自己的窗口类。在MFC环境下,有几种方法可以用来注册“窗口类”,下面分别予以讨论。

  2. 调用AfxRegisterClass注册

     

    AfxRegisterClass函数是MFC全局函数。AfxRegisterClass的函数原型:

    BOOL AFXAPI AfxRegisterClass(WNDCLASS *lpWndClass);

    参数lpWndClass是指向WNDCLASS结构的指针,表示一个“窗口类”。

    首先,AfxRegisterClass检查希望注册的“窗口类”是否已经注册,如果是则表示已注册,返回TRUE,否则,继续处理。

    接着,调用::RegisterClass(lpWndClass)注册窗口类;

    然后,如果当前模块是DLL模块,则把注册“窗口类”的名字加入到模块状态的域m_szUnregisterList中。该域是一个固定长度的缓冲区,依次存放模块注册的“窗口类”的名字(每个名字是以“\n\0”结尾的字符串)。之所以这样做,是为了DLL退出时能自动取消(Unregister)它注册的窗口类。至于模块状态将在后面第9章详细的讨论。

    最后,返回TRUE表示成功注册。

  3. 调用AfxRegisterWndClass注册

     

    AfxRegisterWndClass函数也是MFC全局函数。AfxRegisterWndClass的函数原型:

    LPCTSTR AFXAPI AfxRegisterWndClass(UINT nClassStyle,

    HCURSOR hCursor, HBRUSH hbrBackground, HICON hIcon)

    参数1指定窗口类风格;

    参数2、3、4分别指定该窗口类使用的光标、背景刷、像标的句柄,缺省值是0。

    此函数根据窗口类属性动态地产生窗口类的名字,然后,判断是否该类已经注册,是则返回窗口类名;否则用指定窗口类的属性(窗口过程指定为缺省窗口过程),调用AfxRegisterCalss注册窗口类,返回类名。

    动态产生的窗口类名字由以下几部分组成(包括冒号分隔符):

    如果参数2、3、4全部为NULL,则由三部分组成。

    “Afx”+“:”+模块实例句柄”+“:”+“窗口类风格”

    否则,由六部分组成:

    “Afx”+“:”+模块实例句柄+“:”+“窗口类风格”+“:”+光标句柄+“:”+背景刷句柄+“:”+像标句柄。比如:“Afx:400000:b:13de:6:32cf”。

    该函数在MFC注册主边框或者文档边框“窗口类”时被调用。具体怎样用在5.3.3.3节会指出。

  4. 隐含的使用MFC预定义的的窗口类

     

    MFC4.0以前的版本提供了一些预定义的窗口类,4.0以后不再预定义这些窗口类。但是,MFC仍然沿用了这些窗口类,例如:

    用于子窗口的“AfxWnd”;

    用于边框窗口(SDI主窗口或MDI子窗口)或视的“AfxFrameOrView”;

    用于MDI主窗口的“AfxMDIFrame”;

    用于标准控制条的“AfxControlBar”。

    这些类的名字就 是“AfxWnd”、“AfxFrameOrView”、“AfxMdiFrame”、 “AfxControlBar”加上前缀和后缀(用来标识版本号或是否调试版等)。它们使用标准应用程序像标、标准文档像标、标准光标等标准资源。为了使用这些“窗口类”,MFC会在适当的时候注册这些类:或者要创建该类的窗口时,或者创建应用程序的主窗口时,等等。

    MFC内部使用了函数

    BOOL AFXAPI AfxEndDeferRegisterClass(short fClass)

    来帮助注册上述原MFC版本的预定义“窗口类”。参数fClass区分了那些预定义窗口的类型。根据不同的类型,使用不同的窗口类风格、窗口类名字等填充WndClass的域,然后调用AfxRegisterClass注册窗口类。并且注册成功之后,通过模块状态的m_fRegisteredClasses记录该窗口类已经注册,这样该模块在再次需要注册这些窗口类之前可以查一下m_fRegisteredClasses,如果已经注册就不必浪费时间了。为此,MFC内部使用宏

    AfxDeferRegisterClass(short fClass)

    来注册“窗口类”,如果m_fRegisteredClasses记录了注册的窗口类,返回TRUE,否则,调用AfxEndDeferRegisterClass注册。

    注册这些窗口类的例子:

    MFC在加载边框窗口时,会自动地注册“AfxFrameOrView”窗口类。在创建视时,就会使用该“窗口类”创建视窗口。当然,如果创建视窗口时,该“窗口类”还没有注册,MFC将先注册它然后使用它创建视窗口。

    不过,MFC并不使用”AfxMDIFrame”来创建MDI主窗口,因为在加载主窗口时一般都指定了主窗口的资源,MFC使用指定的像标注册新的MDI主窗口类(通过函数AfxRegisterWndClass完成,因此“窗口类”的名字是动态产生的)。

    MDI子窗口类似于上述MDI主窗口的处理。

    在MFC创建控制窗口时,如工具栏窗口,如果“AfxControlBar”类还没有注册,则注册它。注册过程很简单,就是调用::InitCommonControl加载通用控制动态连接库。

  5. 调用::RegisterWndClass。

     

    直接调用Win32的窗口注册函数::RegisterWndClass注册“窗口类”,这样做有一个缺点:如果是DLL模块,这样注册的“窗口类”在程序退出时不会自动的被取消注册(Unregister)。所以必须记得在DLL模块退出时取消它所注册的窗口类。

  6. 子类化

     

子类化(Subclass)一个“窗口类”,可自动地得到它的“窗口类”属性。

      1. MFC窗口类CWnd

         

在Windows系统里,一个窗口的属性分两个地方存放:一部分放在“窗口类”里头,如上所述的在注册窗口时指定;另一部分放在Windows Object本身,如:窗口的尺寸,窗口的位置(X,Y轴),窗口的Z轴顺序,窗口的状态(ACTIVE,MINIMIZED,MAXMIZED,RESTORED…),和其他窗口的关系(父窗口,子窗口…),窗口是否可以接收键盘或鼠标消息,等等。

为了表达所有这些窗口的共性,MFC设计了一个窗口基类CWnd。有一点非常重要,那就是CWnd提供了一个标准而通用的MFC窗口过程,MFC下所有的窗口都使用这个窗口过程。至于通用的窗口过程却能为各个窗口实现不同的操作,那就是MFC消息映射机制的奥秘和作用了。这些,将在后面有关章节详细论述。

CWnd提供了一系列成员函数,或者是对Win32相关函数的封装,或者是CWnd新设计的一些函数。这些函数大致如下。

(1)窗口创建函数

这里主要讨论函数Create和CreateEx。它们封装了Win32窗口创建函数::CreateWindowEx。Create的原型如下:

BOOL CWnd::Create(LPCTSTR lpszClassName,

LPCTSTR lpszWindowName, DWORD dwStyle,

const RECT& rect,

CWnd* pParentWnd, UINT nID,

CCreateContext* pContext)

Create是一个虚拟函数,用来创建子窗口(不能创建桌面窗口和POP UP窗口)。CWnd的基类可以覆盖该函数,例如边框窗口类等覆盖了该函数以实现边框窗口的创建,视类则使用它来创建视窗口。

Create调用了成员函数CreateEx。CWnd::CreateEx的原型如下:

BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,

LPCTSTR lpszWindowName, DWORD dwStyle,

int x, int y, int nWidth, int nHeight,

HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)

CreateEx有11个参数,它将调用::CreateWindowEx完成窗口的创建,这11个参数对应地传递给::CreateWindowEx。参数指定了窗口扩展风格、“窗口类”、窗口名、窗口大小和位置、父窗口句柄、窗口菜单和窗口创建参数。

CreateEx的处理流程将在后面4.4.1节讨论窗口过程时分析。

窗口创建时发送WM_CREATE消息,消息参数lParam指向一个CreateStruct结构的变量,该结构有11个域,其描述见后面4.4.1节对窗口过程的分析,Windows使用和CreateEx参数一样的内容填充该变量。

(2)窗口销毁函数

例如:

DestroyWindow函数 销毁窗口

PostNcDestroy( ),销毁窗口后调用,虚拟函数

(3)用于设定、获取、改变窗口属性的函数,例如:

SetWindowText(CString tiltle) 设置窗口标题

GetWindowText() 得到窗口标题

SetIcon(HICON hIcon, BOOL bBigIcon);设置窗口像标

GetIcon( BOOL bBigIcon ) ;得到窗口像标

GetDlgItem( int nID);得到窗口类指定ID的控制子窗口

GetDC(); 得到窗口的设备上下文

SetMenu(CMenu *pMenu); 设置窗口菜单

GetMenu();得到窗口菜单

(4)用于完成窗口动作的函数

用于更新窗口,滚动窗口,等等。一部分成员函数设计成或可重载(Overloaded)函数,或虚拟(Overridden)函数,或MFC消息处理函数。这些函数或者实现了一部分功能,或者仅仅是一个空函数。如:

  • 有关消息发送的函数:

     

SendMessage( UINT message,WPARAM wParam = 0, LPARAM lParam = 0 );

给窗口发送发送消息,立即调用方式

PostMessage(( UINT message,WPARAM wParam = 0, LPARAM lParam = 0 );

给窗口发送消息,放进消息队列

  • 有关改变窗口状态的函数

     

MoveWindow( LPCRECT lpRect, BOOL bRepaint = TRUE );

移动窗口到指定位置

ShowWindow(BOOL );显示窗口,使之可见或不可见

….

  • 实现MFC消息处理机制的函数:

     

virtual LRESULT WindowProc( UINT message, WPARAM wParam, LPARAM lParam ); 窗口过程,虚拟函数

virtual BOOL OnCommand( WPARAM wParam, LPARAM lParam );处理命令消息

  • 消息处理函数:

     

OnCreate( LPCREATESTRUCT lpCreateStruct );MFC窗口消息处理函数,窗口创建时由MFC框架调用

OnClose();MFC窗口消息处理函数,窗口创建时由MFC框架调用

  • 其他功能的函数

     

CWnd的导出类是类型更具体、功能更完善的窗口类,它们继承了CWnd的属性和方法,并提供了新的成员函数(消息处理函数、虚拟函数、等等)。

常用的窗口类及其层次关系见图1-1。

      1. 在MFC下创建一个窗口对象

         

MFC下创建一个窗口对象分两步,首先创建MFC窗口对象,然后创建对应的Windows窗口。在内存使用上,MFC窗口对象可以在栈或者堆(使用new创建)中创建。具体表述如下:

  • 创建MFC窗口对象。通过定义一个CWnd或其派生类的实例变量或者动态创建一个MFC窗口的实例,前者在栈空间创建一个MFC窗口对象,后者在堆空间创建一个MFC窗口对象。

     

  • 调用相应的窗口创建函数,创建Windows窗口对象。

     

例如:在前面提到的AppWizard产生的源码中,有CMainFrame(派生于CMDIFrame(SDI)或者CMDIFrameWnd(MDI))类。它有两个成员变量定义如下:

CToolBar m_wndToolBar;

CStatusBar m_wndStatusBar;

当创建CMainFrame类对象时,上面两个MFC Object也被构造。

CMainFrame还有一个成员函数

OnCreate(LPCREATESTRUCT lpCreateStruct),

它的实现包含如下一段代码,调用CToolBar和CStatusBar的成员函数Create来创建上述两个MFC对象对应的工具栏HWND窗口和状态栏HWND窗口:

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (!m_wndToolBar.Create(this) ||

!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))

{

TRACE0("Failed to create toolbar\n");

return -1; // fail to create

}

if (!m_wndStatusBar.Create(this) ||

!m_wndStatusBar.SetIndicators(indicators,

sizeof(indicators)/sizeof(UINT)))

{

TRACE0("Failed to create status bar\n");

return -1; // fail to create

}

}

关于工具栏、状态栏将在后续有关章节作详细讨论。

在MFC中,还提供了一种动态创建技术。动态创建的过程实际上也如上所述分两步,只不过MFC使用这个技术是由框架自动地完成整个过程的。通常框架窗口、文档框架窗口、视使用了动态创建。介于MFC的结构,CFrameWnd和CView及其派生类的实例即使不使用动态创建,也要用new在堆中分配。理由见窗口的销毁(2.2.5节)。

至于动态创建技术,将在下一章具体讨论。

在Windows窗口的创建过程中,将发送一些消息,如:

在创建了窗口的非客户区(Nonclient area)之后,发送消息WM_NCCREATE;

在创建了窗口的客户区(client area)之后,发送消息WM_CREATE;

窗口的窗口过程在窗口显示之前收到这两个消息。

如果是子窗口,在发送了上述两个消息之后,还给父窗口发送WM_PARENATNOTIFY消息。其他类或风格的窗口可能发送更多的消息,具体参见SDK开发文档。

      1. MFC窗口的使用

         

        MFC提供了大量的窗口类,其功能和用途各异。程序员应该选择哪些类来使用,以及怎么使用他们呢?

        直接使用MFC提供的窗口类或者先从MFC窗口类派生一个新的C++类然后使用它,这些在通常情况下都不需要程序员提供窗口注册的代码。是否需要派生新的C++类,视MFC已有的窗口类是否能满足使用要求而定。派生的C++类继承了基类的特性并改变或扩展了它的功能,例如增加或者改变对消息、事件的特殊处理等。

        主要使用或继承以下一些MFC窗口类(其层次关系图见图1-1):

        框架类CFrameWnd,CMdiFrameWnd;

        文档框架CMdiChildWnd;

        视图CView和CView派生的有特殊功能的视图如:列表CListView,编辑CEditView,树形列表CTreeView,支持RTF的CRichEditView,基于对话框的视CFormView等等。

        对话框CDialog。

        通常,都要从这些类派生应用程序的框架窗口和视窗口或者对话框。

        工具条CToolBar

        状态条CStatusBar

        其他各类控制窗口,如列表框CList,编辑框CEdit,组合框CComboBox,按钮Cbutton等。

        通常,直接使用这些类。

      2. 在MFC下窗口的销毁

         

窗口对象使用完毕,应该销毁。在MFC下,一个窗口对象的销毁包括HWND窗口对象的销毁和MFC窗口对象的销毁。一般情况下,MFC编程框架自动地处理了这些。

(1)对CFrameWnd和CView的派生类

这些窗口的关闭导致销毁窗口的函数DestroyWindow被调用。销毁Windows窗口时,MFC框架调用的最后一个成员函数是OnNcDestroy函数,该函数负责Windows清理工作,并在最后调用虚拟成员函数PostNcDestroy。CFrameWnd和CView的PostNcDestroy调用delete this删除自身这个MFC窗口对象。

所以,对这些窗口,如前所述,应在堆(Heap)中分配,而且,不要对这些对象使用delete操作。

(2)对Windows Control窗口

在它们的析构函数中,将调用DestroyWidnow来销毁窗口。如果在栈中分配这样的窗口对象,则在超出作用范围的时候,随着析构函数的调用,MFC窗口对象和它的Windows window对象都被销毁。如果在堆(Heap)中分配,则显式调用delete操作符,导致析构函数的调用和窗口的销毁。

所以,这种类型的窗口应尽可能在栈中分配,避免用额外的代码来销毁窗口。如前所述的CMainFrame的成员变量m_wndStatusBar和m_wndToolBar就是这样的例子。

(3)对于程序员直接从CWnd派生的窗口

程序员可以在派生类中实现上述两种机制之一,然后,在相应的规范下使用。

后面章节将详细的讨论应用程序退出时关闭、清理窗口的过程。

    1. 设备描述表

       

      1. 设备描述表概述

         

当一个应用程序使用GDI函数时,必须先装入特定的设备驱动程序,然后为绘制窗口准备设备描述表,比如指定线的宽度和颜色、刷子的样式和颜色、字体、剪裁区域等等。不像其他Win32结构,设备描述表不能被直接访问,只能通过系列Win32函数来间接地操作。

如同Windows“窗口类”一样,设备描述表也是一种Windows数据结构,用来描述绘制窗口所需要的信息。它定义了一个坐标映射模式、一组GDI图形对象及其属性。这些GDI对象包括用于画线的笔,绘图、填图的刷子,位图,调色板,剪裁区域,及路径(Path)。

表2-2列出了设备描述表的结构和各项缺省值,表2-3列出了设备描述表的类型,表2-4显示设备描述表的类型。

表2-2 设备描述表的结构

属性

缺省值

Background color

Background color setting from Windows Control Panel (typically, white)

Background mode

OPAQUE

Bitmap

None

Brush

WHITE_BRUSH

Brush origin

(0,0)

Clipping region

Entire window or client area with the update region clipped, as appropriate. Child and pop-up windows in the client area may also be clipped

Palette

DEFAULT_PALETTE

Current pen position

(0,0)

Device origin

Upper left corner of the window or the client area

Drawing mode

R2_COPYPEN

Font

SYSTEM_FONT (SYSTEM_FIXED_FONT for applications written to run with Windows versions 3.0 and earlier)

Intercharacter spacing

0

Mapping mode

MM_TEXT

Pen

BLACK_PEN

Polygon-fill mode

ALTERNATE

Stretch mode

BLACKONWHITE

Text color

Text color setting from Control Panel (typically, black)

Viewport extent

(1,1)

Viewport origin

(0,0)

Window extent

(1,1)

Window origin

(0,0)

 

表2-3 设备描述表的分类

Display

显示设备描述表,提供对视频显示设备上的绘制操作的支持

Printer

打印设备描述表,提供对打印机、绘图仪设备上的绘制操作的支持

Memory

内存设备描述表,提供对位图操作的支持

Information

信息设备描述表,提供对操作设备信息获取的支持

表2-3中的显示设备描述表又分三种类型,如表2-4所示。

表2-4 显示设备描述表的分类

名称

特点

功能

Class Device

Contexts

提供对Win16的向后兼容

 

Common

Device

Contexts

在Windows系统的高速缓冲区,数量有限

Applicaion获取设备描述表时,Windows用缺省值初始化该设备描述表,Application使用它完成绘制操作,然后释放

Private

Device

Contexts

没有数量限制,用完不需释放一次获取,多次使用

多次使用过程中,每次设备描述表属性的任何修改或变化都会被保存,以支持快速绘制

 

(1)使用设备描述表的步骤

要使用设备描述表,一般有如下步骤:

  • 获取或者创建设备描述表;

     

  • 必要的话,改变设备描述表的属性;

     

  • 使用设备描述表完成绘制操作;

     

  • 释放或删除设备描述表。

     

Common设备描述表通过::GetDC,::GetDCEx,::BeginPaint来获得一个设备描述表,用毕,用::ReleaseDC或::EndPaint释放设备描述表;

Printer设备描述表通过::CreateDC创建设备描述表,用::DeleteDC删除设备描述表。

Memory设备描述表通过::CreateCompatibleDC创建设备描述表,用::DeleteDC删除。

Information设备描述表通过::CreateIC创建设备描述表,用::DeleteDC删除。

(2)改变设备描述表属性的途径

要改变设备描述表的属性,可通过以下途径:

用::SelectObject选入新的除调色板以外的GDI Object到设备描述表中;

对于调色板,使用::SelectPalette函数选入逻辑调色板,并使用::RealizePalette把逻辑调色板的入口映射到物理调色板中。

用其他API函数改变其他属性,如::SetMapMode改变映射模式。

      1. 设备描述表在MFC中的实现

         

MFC提供了CDC类作为设备描述表类的基类,它封装了Windows的HDC设备描述表对象和相关函数。

  1. CDC类

     

    CDC类包含了各种类型的Windows设备描述表的全部功能,封装了所有的Win32 GDI 函数和设备描述表相关的SDK函数。在MFC下,使用CDC的成员函数来完成所有的窗口绘制工作。

    CDC 类的结构示意图2-2所示。

    CDC类有两个成员变量:m_hDC,m_hAttribDC,它们都是Windows设备描述表句柄。CDC的成员函数作输出操作时,使用m_Hdc;要获取设备描述表的属性时,使用m_hAttribDC。

    在创建一个CDC类实例时,缺省的m_hDC等于m_hAttribDC。如果需要的话,程序员可以分别指定它们。例如,MFC框架实现CMetaFileDC类时,就是如此:CMetaFileDC从物理设备上读取设备信息,输出则送到元文件(metafile)上,所以m_hDC和m_hAttribDC是不同的,各司其责。还有一个类似的例子:打印预览的实现,一个代表打印机模拟输出,一个代表屏幕显示。

    CDC封装::SelectObject(HDC hdc,HGDIOBJECT hgdiobject)函数时,采用了重载技术,即它针对不同的GDI对象,提供了名同而参数不同的成员函数:

    SelectObject(CPen *pen)用于选入笔;

    SelectObject(CBitmap* pBitmap)用于选入位图;

    SelectObject(CRgn *pRgn)用于选入剪裁区域;

    SelectObject(CBrush *pBrush)用于选入刷子;

    SelectObject(CFont *pFont)用于选入字体;

    至于调色板,使用SelectPalette(CPalette *pPalette,BOOL bForceBackground )选入调色板到设备描述表,使用RealizePalletter()实现逻辑调色板到物理调色板的映射。

  2. 从CDC派生出功能更具体的设备描述表

     

从CDC 派生出四个功能更具体的设备描述表类。层次如图2-3所示。

下面,分别讨论派生出的四种设备描述表。

  • CCientDC

     

代表窗口客户区的设备描述表。其构造函数CClientDC(CWnd *pWin)通过::GetDC获取指定窗口的客户区的设备描述表HDC,并且使用成员函数Attach把它和CClientDC对象捆绑在一起;其析构函数使用成员函数Detach把设备描述表句柄HDC分离出来,并调用::ReleaseDC释放设备描述表HDC。

  • CPaintDC

     

仅仅用于响应WM_PAINT消息时绘制窗口,因为它的构造函数调用了::BeginPaint获取设备描述表HDC,并且使用成员函数Attach把它和CPaintDC对象捆绑在一起;析构函数使用成员函数Detach把设备描述表句柄HDC分离出来,并调用::EndPaint释放设备描述表HDC,而::BeginPaint和::EndPaint仅仅在响应WM_PAINT时使用。

  • CMetaFileDC

     

用于生成元文件。

  • CWindowDC

     

代表整个窗口区(包括非客户区)的设备描述表。其构造函数CWindowDC(CWnd *pWin)通过::GetWindowDC获取指定窗口的客户区的设备描述表HDC,并使用Attach把它和CWindowDC对象捆绑在一起;其析构函数使用Detach把设备描述表HDC分离出来,调用::ReleaseDC释放设备描述表HDC。

      1. MFC设备描述表类的使用

         

  1. 使用CPaintDC、CClientDC、CWindowDC的方法

     

    首先,定义一个这些类的实例变量,通常在栈中定义。然后,使用它。

    例如,MFC中CView对WM_PAINT消息的实现方法如下:

    void CView::OnPaint()

    {

    // standard paint routine

    CPaintDC dc(this);

    OnPrepareDC(&dc);

    OnDraw(&dc);

    }

    在栈中定义了CPaintDC类型的变量dc,随着构造函数的调用获取了设备描述表;设备描述表使用完毕,超出其有效范围就被自动地清除,随着析构函数的调用,其获取的设备描述表被释放。

    如果希望在堆中创建,例如

    CPaintDC *pDC;

    pDC = new CPaintDC(this)

    则在使用完毕时,用delete删除pDC:

    delete pDC;

  2. 直接使用CDC

     

需要注意的是:在生成CDC对象的时候,并不像它的派生类那样,在构造函数里获取相应的Windows设备描述表。最好不要使用::GetDC等函数来获取一个设备描述表,而是创建一个设备描述表。其构造函数如下:

CDC::CDC()

{

m_hDC = NULL;

m_hAttribDC = NULL;

m_bPrinting = FALSE;

}

其析构函数如下:

CDC::~CDC()

{

if (m_hDC != NULL)

::DeleteDC(Detach());

}

在CDC析构函数中,如果设备描述表句柄不空,则调用DeleteDC删除它。这是直接使用CDC时最好创建Windows设备描述表的理由。如果设备描述表不是创建的,则应该在析构函数被调用前分离出设备描述表句柄并用::RealeaseDC释放它,释放后m_hDC为空,则在析构函数调用时不会执行::DeleteDC。当然,不用担心CDC的派生类的析构函数调用CDC的析构函数,因为CDC::~CDC()不是虚拟析构函数。

直接使用CDC的例子是内存设备上下文,例如:

CDC dcMem; //声明一个CDC对象

dcMem.CreateCompatibleDC(&dc); //创建设备描述表

pbmOld = dcMem.SelectObject(&m_bmBall);//更改设备描述表属性

…//作一些绘制操作

dcMem.SelectObject(pbmOld);//恢复设备描述表的属性

dcMem.DeleteDC(); //可以不调用,而让析构函数去删除设备描述表

    1. GDI对象

       

在讨论设备描述表时,已经多次涉及到GDI对象。这里,需强调一下:GDI对象要选入Windows 设备描述表后才能使用;用毕,要恢复设备描述表的原GDI对象,并删除该GDI对象。

一般按如下步骤使用GDI对象:

Create or get a GDI OBJECT hNewGdi;

hOldGdi = ::SelectObject(hdc, hNewGdi)

……

::SelectObject(hdc, hOldGdi)

::DeleteObject(hNewGdi)

先创建或得到一个GDI对象,然后把它选入设备描述表并保存它原来的GDI对象;用毕恢复设备描述表原来的GDI对象并删除新创建的GDI对象。

需要指出的是,如果hNewGdi是一个Stock GDI对象,可以不删除(删除也可以)。通过

HGDIOBJ GetStockObject(

int fnObject // type of stock object

);

来获取Stock GDI对象。

  1. MFC GDI对象

     

    MFC用一些类封装了Windows GDI对象和相关函数,层次结构如图2-4所示:

    CGdiObject封装了Windows GDI Object共有的特性。其派生类在继承的基础上,主要封装了各类GDI的创建函数以及和具体GDI对象相关的操作。

    CGdiObject的构造函数仅仅让m_hObject为空。如果m_hObject不空,其析构函数将删除对应的Windows GDI对象。MFC GDI对象和Windows GDI对象的关系如图2-5所示。

  2. 使用MFC GDI类的使用

     

首先创建GDI对象,可分一步或两步创建。一步创建就是构造MFC对象和Windows GDI对象一步完成;两步创建则先构造MFC对象,接着创建Windows GDI对象。然后,把新创建的GDI对象选进设备描述表,取代原GDI对象并保存。最后,恢复原GDI对象。例如:

void CMyView::OnDraw(CDC *pDC)

{

CPen penBlack; //构造MFC CPen对象

if (penBlack.CreatePen(PS_SOLID, RGB(0, 0, 0)))

{

CPen *pOldPen = pDC->SelectObject(&penBlack)); //选进设备表,保存原笔

pDC->SelectObject(pOldPen); //恢复原笔

}else

{

}

}

和在SDK下有一点不同的是:这里没有DeleteObject。因为执行完OnDraw后,栈中的penBlack被销毁,它的析构函数被调用,导致DeleteObject的调用。

还有一点要说明:

pDC->SelectObject(&penBlack)返回了一个CPen *指针,也就是说,它根据原来PEN的句柄创建了一个MFC CPen对象。这个对象是否需要删除呢?不必要,因为它是一个临时对象,MFC框架会自动地删除它。当然,在本函数执行完毕把控制权返回给主消息循环之前,该对象是有效的。

关于临时对象及MFC处理它们的内部机制,将在后续章节详细讨论。

至此,Windows编程的核心概念:窗口、GDI界面(设备描述表、GDI对象等)已经陈述清楚,特别揭示了MFC对这些概念的封装机制,并简明讲述了与这些Windows Object对应的MFC类的使用方法。还有其他Windows概念,可以参见SDK开发文档。在MFC的实现上,基本上仅仅是对和这些概念相关的Win32函数的封装。如果明白了MFC的窗口、GDI界面的封装机制,其他就不难了。

posted @ 2008-11-01 15:42 wrh 阅读(1321) | 评论 (0)编辑 收藏

系统理解Win32 API和MFC(下)
作者: 温昱
作者主页: lcspace.diy.163.com

系统理解Win32 API和MFC(上)

二、MFC的概念模型

前面我们研究了WIN32 API的“领域模型”,对它有较全面的认识。下面,对MFC概念模型的研究,我们把重点放在对app framework的研究上。
app framework中的message响应/传递机制是最重要的。而Hook机制和Message响应/传递机制是密切相关的,后者以前者为基础。

1. Hook机制

也许有些程序员只知道hook机制可以编写很“牛”的应用,孰不知MFC本身也是依靠hook机制的。

从图中看到,每个hook拥有一个指针队列,每个指针指向一个称为的HookProc函数,HookProc将在合适的时机被OS调用执行。hook是分不同种类的,其实正是hook的种类决定了它什么时机被OS调用执行。提示,可以看一下“订阅-发布”设计模式以助理解。

2 MFC中Message响应函数的安装

2.1 回忆API中Message响应函数的安装

API中Message响应函数的安装,是由CreateWindow()实现的,它将window与一个windowClass联系起来,而后者中记录了Message响应函数的指针。
至于细节,看一下如何用Win32 SDK或Win16 SDK写程序就清楚了,其中 DefWindowProc()是API函数,负责提供缺省的消息处理,所以,程序员只需要handle需要特殊处理的消息。

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR szCmdLine,int iCmdShow)
{
WNDCLASS wndclass;
...
wndclass.lpfnWndProc =WndProc;
wndclass.lpszClassName = szWindowClass;
...
RegisterClass(&wndclass);
hWnd = CreateWindow( szWindowClass, ...);
...
}
LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
switch(message)
{
...
return;
}
return DefWindowProc(hwnd,message,wParam,lParam);
}
2.2 MFC中Message响应函数的安装

MFC中Message响应函数的安装显然更复杂,是在CWnd::CreateEx()被调用时完成的,其中还用到了Hook机制。

我们可以先猜一下MFC是怎么做的。MFC支持massage map,使得对消息的响应份散到多个message handler函数中,而不是API开发是那种集中式的消息处理函数;所以,想必会有专门的代码来负责“检索message map table然后调用message handle”。message map是为了支持程序员处理他关心的特殊message的,那么缺省的message处理逻辑在哪里呢?答案是MFC创建window obj时是用的“预定义的窗口类”,自然已经有了缺省的message处理函数。

从图中看到,CWnd有成员变量m_pfnSuper、成员变量m_hWnd、成员函数OnWndMsg()和成员函数DefWindowProc()。Wnd::OnWndMsg()负责“在message map中定义的message handle”能否处理到来的message,如果处理了要返回true;CWnd::DefWindowProc()负责对message缺省处理。
执行过程是,首先CWnd::CreateEx()被调用,window obj和window class被相应建立,此时window class的WindowProc字段存储了预定义的缺省处理函数的地址;由于有hook在监听窗口创建消息,所以注册的hookProc()会被调用执行,它将classWindow数据结构的WindowProc字段备份到CWnd::m_pfnSuper,再用SetWindowLong()改写classWindow数据结构的WindowProc字段为::AfxWndProc()的地址。当任何一个message到达时,::AfxWndProc()被调用,至于它的逻辑,聪明的你一定猜到了,先调用Wnd::OnWndMsg(),如果返回值为false,还要调用CWnd::DefWindowProc(),CWnd::m_pfnSuper指向的缺省处理逻辑,也会在CWnd::DefWindowProc()中被调用。
提示,上面其实有多态情况发生。比如你可以在搜一下pWnd->WindowProc(nMsg, wParam, lParam); 另外,OnWndMsg和DefWindowProc都是CWnd类的虚拟函数。

要是觉得不太好理解,最好在VC++里创建一个project实际跟踪一下,下面是我跟踪时调用栈映象的截图。


3. SubClass机制


从图中看到,SubClass机制以CWnd自身的m_pfnSuper为基础,和“MFC中Message响应函数的安装”很象。

4.frame work中的主要相关类

frame work中的主要相关类 就是 message route的候选人,正是它们的OnCmdMsg()共同完成了message route,形成了chain of responsability模式。

5. frame work中的chain of responsability模式

下图是一个对象树,注意消息会在纵向和横向两个方向传播。


消息在纵向方向上的传递,是在“上溯父类的massge map表”,MFC的message map完全是为了代替虚函数而采取的手段,而和message route无关。

消息在横向方向上的传递,才是message route,才是chain of responsability模式,由多个相关类的OnCmdMsg()共同完成。

三、 总结
从上面的讨论不难发现,MFC中用到了不少设计模式,如上面提到的chain of responsability模式、composite模式和“订阅-发布”模式。上面的讨论不仅有助于程序员全面掌握Win32 API和MFC,对architect设计architecture也有很大帮助。

posted @ 2008-11-01 15:38 wrh 阅读(341) | 评论 (0)编辑 收藏

系统理解Win32 API和MFC(上)
作者: 温昱
作者主页: lcspace.diy.163.com

Win32 API是微软的操作系统Windows提供给开发人员的编程接口,它决定了我们开发的Windows应用程序的能力。MFC是微软为开发人员提供的类库,在某种意义上是对Win32 API的封装。本文试图从全局角度对Win32 API和MFC进行理解──给出二者的概念模型。
本文使用UML描述概念模型。Win32 API本不是面向对象的,我用面向对象的观点去理解它,无非是想表达其全局。
本文参考了MSDN、相关书籍和网上的一些资料,在此一并感谢。

一、Win32 API的概念模型

Win32 API的object有3种:user obj,gdi obj,kernel obj。但是,如果一点不考虑OS本身的支持,就会在有些问题上疑惑,因此,我这里把“operation system负责将中断封装成message”加上。

1、user obj、gdi obj、kernel obj、system 4者的关系

由于是kernel obj部分负责将另外3者联系起来,因此我们在下图中直接深入到kernel obj部分内部。

从图中看到,在内存中运行的,除了“负责将中断封装成message”的system支持部分,还有另外3类object:kernel obj、user obj和gdi obj,每个obj都有一个句柄handle与之对应。其中,gdi obj建立了待开发的Windows 应用和外部输出设备的联系,kernel obj中的file建立了内存和永久存储设备的联系。具体说,内存中的file从可以从硬盘上来,如果这个file是可执行文件,它将生成module,module运行起来就是process,process可以包含多条thread,而thread的运行映象最终还是来自于file。thread是kernel obj中最重要的一个,因为消息队列就是thread拥有的,只有thread才能够接受message。对gdi obj、urser obj和file的操作,也是发生在thread中的。所以书都讲,process至少拥有一个thread。

2、展开“system负责将中断封装成message”部分

下面展开“system负责将中断封装成message”部分,尽早解除对“message到底是怎么形成的”的困惑。


3、展开“gdi obj”部分

开发人员可以通过gdi obj将app的信息反馈给User。

从图中看到,gdi obj有8种,其中7种为:bmp,brush,pen,region,font,palette,path。另一种比较特殊的是DC,它可以被理解为一种容器,程序员通过调用SelectPallette()将pallte放入容器,通过调用BeginPath()和EndPath()将path放入容器,其它5种gdi obj,是通过调用SelectObject()放入容器的。DC又具体分为4种,其中DisplayDC就是最常用的用来支持我们“画Window”的DC。 另外,如果觉得不好理解,请参考composite设计模式。

4、展开user obj部分

4.1 第1次迭代

window在Windows应用开发中占有重要地位。

从图中看到,window可分为3种:desktop,top-level window,child window。所有window被OS组织成tree,有专门的数据结构来管理。desktop就是树根,desktop的子节点是top-level window,top-level window的子节点是child window,child window仍然可以有子节点,同样归属于child window。tree数据结构中还记录了4种重要信息,是4种指针:parent指针、child指针、brother指针、owner指针。这样,从任何一个window就能很容易地找到其它window了。

好了,暂且得到 window = desktop + topLevel + child 的结论,看看全局先。毕竟,一步到位有时候并不好。
从图中看到,window确实占有重要地位。从逻辑是讲,thread是window的拥有者;但是,所有window一起决定了屏幕看起来是上面样子,何况点击任何一个window都会使window得相互覆盖关系发生变化,对所用window进行统一管理是必须的,所以OS又不得不统一用window tree来管理window,反映复杂的window关系。每个window都必须有一个且只能有一个客户区,还可能有一个title bar。

 

再来看看CreateWindow()函数的interface spec透露了哪些信息。

 


从图中看到,CreateWindow()负责为window建立与窗口类的联系。每个window都有一个窗口类与之对应,而一个窗口类可以对应多个window。窗口类中记录了窗口函数和菜单等资源信息,而由file生成的module正是窗口函数和资源的老家。

 

4.2 第2次迭代

考察消息种类。

从图中看到,每个message都是发送给某个window的。注意,msg可由SYS代码产生,也可以由API函数产生。

进一步考察window,深入topLevel和child。


从图中看到,OVERLAPPED风格的window是top-level window的一种,而另一种POPUP风格的window从本质上(行为上)是特殊的一种OVERLAPPED风格的window,虽然我们从coding的角度常常不这么认为。

还是不好,因为当我们调用CreateWindow() API函数时,明明感觉CHILD、OVERLAPPED、POPUP是“window style”。我再画一张图。

从图中看到,control必须是CHILD风格的,dialog必须是POPUP风格的,而一般性的window却可以是任意风格的。

4.3 第3次迭代

总结user obj:


CreateDialog()函数示意:

从图中看到,CreateDialog()和CreateWindow()最大的区别就是,它有对话框模板支持方便地定制dialog界面。注意,Dialog是特殊的window,窗口类它一定也是有的。

posted @ 2008-11-01 15:37 wrh 阅读(200) | 评论 (0)编辑 收藏
1.检测程序中的括号是否匹配

  把光标移动到需要检测的括号(如大括号{}、方括号[]、圆括号()和尖括号<>)前面,键入快捷键“Ctrl+]”。如果括号匹配正确,光标就跳到匹配的括号处,否则光标不移动,并且机箱喇叭还会发出一声警告声。

  2.查看一个宏(或变量、函数)的宏定义

  把光标移动到你想知道的一个宏上,就比如说最常见的DECLARE_MAP_MESSAGE上按一下F12(或右键菜单中的Go To Defition Of …),如果没有建立Browse files,会出现提示对话框,确定,然后就会跳到定义那些东西的地方。

  相当可喜的是,它也可以看到Microsoft定义的系统宏,非常good.

  3.格式化一段乱七八糟的源代码

  选中那段源代码,按ATL+F8。

  4.在编辑状态下发现成员变量或函数不能显示

  删除该项目扩展名为.ncb文件,重新打开该项目。

  5.如何整理ClassView视图中大量的类

  可以在classview 视图中右键新建文件夹(new folder),再把具有相近性质的类拖到对应的文件夹中,使整个视图看上去清晰明了.

  6.定位预处理指定

  在源文件中定位光标到对称的#if, #endif,使用Ctrl+K.

  7.如何添加系统中Lib到当前项目

  在Project | Settings | Link | Object/library modules:输入Lib名称,不同的Lib之间用空格格开.

  8.如何添加系统中的头文件(.h)到当前项目.

  #include ,告诉编译到VC系统目录去找;使用#include "FileName.h",告诉编译在当前目录找.

  9.如何在Studio使用汇编调试

  在WorkBench的Debugger状态下按CTRL+F7.

  10.怎样处理ClassZiard找不到的系统消息

  如果要在ClassWizard中处理WM_NCHITTEST等系统消息,请在ClassWizard中Class Info页中将Message filter改为Window就有了.

  11.如何干净的删除一个类

  先从Workspace中的FileView中删除对应的.h和.cpp文件,再关闭项目,从实际的文件夹中删除对应的.h和.cpp文件与.clw文件。

  12.在Studio中快速切换两个文件

  有时,我们需要在最近使用的两个文件中快速切换,换Ctrl+F6。这在两个文件不相今的时候就有用的.

  13.取得源程序预处理后的结果:

  在Studio里,可以在->PROJECT-> SETTINGS->C/C++->Project Options中,在最后加上 /P /EP这两个编译开关即可做到"只进行预处理".就可以了。编译以后就可以在源程序目录中发现“文件名.I ”的文本文件。这就是预处理后的结果。

  (注意注:区分大小定,请用大定/P)

  14.在Debug模式中查看WINAPI调用后的返回值:

  很简单,且实用:在watch中加入@hr,err。在CSDN的文档中心有一篇讲得更细,请参考。

  15.产生指定源程序文件的汇编代码:

  从IDE菜单的Project->Setting打开项目设置,按如下文件做:

  1.先在左边选择指定文件,可以多选。

  2. 在右边的C++属性页中,在category中选择List Files,接着在下面的List Files Type中选择Assembly and source code(或选择其它),最后在List File Name中输入在个C/C++源文件产生的相应的汇编代码的文件。

  3.编译整个工程。

  16.手工编译纯资源成dll:

  Rc.exe /v data.rc
  Cvtres.exe /machine:ix86 data.res
  Link /SUBSYSTEM:WINDOWS /DLL /NOENTRY data.res ;编译成DLL文件

  这种方式创建的DLL是最小的,比起你用Win 32 Dynamic Libray等产生的更小。

  17:怎样快速生成一个与现有项目除了项目名外完全相同的新项目?

  利用File菜单下生成新项目中的Custom AppWizard ,选择 An existing Project ,然后选择现有项目的项目文件名(*.dsp)Finish,编译后就生成一个可以生成与现有项目相同但可以重新取名的项目的AppWizard。你可以象用MFC AppWizard一样用它。如果不想用了,可以在VC 安装目录下Common\MSDev98\Template目录中删除该Wizard中.awx和 .pdb文件。

  18:如果想把整个项目拷贝到软盘,那些文件可以删掉?

  除了项目文件夹中debug文件夹可以删除外,.ncb,.clw,.opt 等文件也可以删除,这些文件Rebuilt all后可以重新生成。

  附:VC项目文件说明

  .dsp 项目参数配置文件,这个文件太重要,重点保护对象。.

  .dsw 工作区文件,重要性一般,因为它信息不我,容易恢复。

  以下文件在项目中是可丢弃的,有些文件删除后,VC会自动生成的。

  .clw ClassWizard信息文件,实际上是INI文件的格式,有兴趣可以研究一下.有时候ClassWizard出问题,手工修改CLW文件可以解决.如果此文件不存在的话,每次用ClassWizard的时候绘提示你是否重建.

  .ncb 无编译浏览文件(no compile browser)。当自动完成功能出问题时可以删除此文件。build后会自动生成。

  .opt 工程关于开发环境的参数文件。如工具条位置等信息;(可丢弃)

  .aps (AppStudio File),资源辅助文件,二进制格式,一般不用去管他.

  .plg 是编译信息文件,编译时的error和warning信息文件(实际上是一个html文件),一般用处不大.在Tools->Options里面有个选项可以控制这个文件的生成.

  .hpj (Help Project)是生成帮助文件的工程,用microsfot Help Compiler可以处理.

  .mdp (Microsoft DevStudio Project)是旧版本的项目文件,如果要打开此文件的话,会提示你是否转换成新的DSP格式.

  .bsc 是用于浏览项目信息的,如果用Source Brower的话就必须有这个文件.如果不用这个功能的话,可以在Project Options里面去掉Generate Browse Info File,可以加快编译速度.

  .map 是执行文件的映像信息纪录文件,除非对系统底层非常熟悉,这个文件一般用不着.

  .pch (Pre-Compiled File)是预编译文件,可以加快编译速度,但是文件非常大.

  .pdb (Program Database)记录了程序有关的一些数据和调试信息,在调试的时候可能有用.

  .exp 只有在编译DLL的时候才会生成,记录了DLL文件中的一些信息.一般也没什么用.

posted @ 2008-10-26 09:25 wrh 阅读(185) | 评论 (0)编辑 收藏

VC 中的定时

VC中提供了很多关于时间操作的函数,编写程序时我们可以跟据定时的不同精度要求选择不同的时间函数来完成定时和计时操作。
  
     方式一:VC中的WM_TIMER消息映射能进行简单的时间控制。首先调用函数SetTimer()设置定时间隔,如SetTimer(0,200,NULL)即为设置200ms的时间间隔。然后在应用程序中增加定时响应函数 OnTimer(),并在该函数中添加响应的处理语句,用来完成到达定时时间的操作。这种定时方法非常简单,可以实现一定的定时功能,但其定时功能如同Sleep()函数的延时功能一样,精度非常低,最小计时精度仅为18ms。CPU占用低,且定时器消息在多任务操作系统中的优先级很低,不能得到及时响应,往往不能满足实时控制环境下的应用。只可以用来实现诸如位图的动态显示等对定时精度要求不高的情况。

  方式二:VC中使用sleep()函数实现延时,它的单位是ms,如延时2秒,用sleep(2000)。精度非常低,最小计时精度仅为30ms,用sleep函数的不利处在于延时期间不能处理其他的消息,如果时间太长,就好象死机一样,CPU占用率非常高,只能用于要求不高的延时程序中。

  方式三:利用COleDateTime类和COleDateTimeSpan类结合WINDOWS的消息处理过程来实现秒级延时。以下是实现2秒的延时代码:

     COleDateTime      start_time = COleDateTime::GetCurrentTime();
     COleDateTimeSpan end_time= COleDateTime::GetCurrentTime()-start_time;
     while(end_time.GetTotalSeconds()< 2) //实现延时2秒
    {
             MSG   msg;
             GetMessage(&msg,NULL,0,0);
             TranslateMessage(&msg);
             DispatchMessage(&msg);
             
            //以上四行是实现在延时或定时期间能处理其他的消息,
     //虽然这样可以降低CPU的占有率,
            //但降低了延时或定时精度,实际应用中可以去掉。
            end_time = COleDateTime::GetCurrentTime()-start_time;
     }//这样在延时的时候我们也能够处理其他的消息。      

  方式四:在精度要求较高的情况下,VC中可以利用GetTickCount()函数,该函数的返回值是 DWORD型,表示以ms为单位的计算机启动后经历的时间间隔。精度比WM_TIMER消息映射高,在较短的定时中其计时误差为15ms,在较长的定时中其计时误差较低,如果定时时间太长,就好象死机一样,CPU占用率非常高,只能用于要求不高的延时程序中。下列代码可以实现50ms的精确定时:
      DWORD dwStart = GetTickCount();
      DWORD dwEnd   = dwStart;
      do
      {
         dwEnd = GetTickCount() - dwStart;
      }while(dwEnd <50);
为使GetTickCount()函数在延时或定时期间能处理其他的消息,可以把代码改为:
      DWORD dwStart = GetTickCount();
      DWORD dwEnd   = dwStart;
      do
      {
             MSG   msg;
             GetMessage(&msg,NULL,0,0);
             TranslateMessage(&msg);
             DispatchMessage(&msg);
             dwEnd = GetTickCount()-dwStart;
      }while(dwEnd <50);
虽然这样可以降低CPU的占有率,并在延时或定时期间也能处理其他的消息,但降低了延时或定时精度。

  方式五:与GetTickCount()函数类似的多媒体定时器函数DWORD timeGetTime(void),该函数定时精度为ms级,返回从Windows启动开始经过的毫秒数。微软公司在其多媒体Windows中提供了精确定时器的底层API持,利用多媒体定时器可以很精确地读出系统的当前时间,并且能在非常精确的时间间隔内完成一个事件、函数或过程的调用。不同之处在于调用DWORD timeGetTime(void) 函数之前必须将 Winmm.lib 和 Mmsystem.h 添加到工程中,否则在编译时提示DWORD timeGetTime(void)函数未定义。由于使用该函数是通过查询的方式进行定时控制的,所以,应该建立定时循环来进行定时事件的控制。

  方式六:使用多媒体定时器timeSetEvent()函数,该函数定时精度为ms级。利用该函数可以实现周期性的函数调用。函数的原型如下:
      MMRESULT timeSetEvent( UINT uDelay,
                              UINT uResolution,
                              LPTIMECALLBACK lpTimeProc,
                              WORD dwUser,
                              UINT fuEvent )
  该函数设置一个定时回调事件,此事件可以是一个一次性事件或周期性事件。事件一旦被激活,便调用指定的回调函数,成功后返回事件的标识符代码,否则返回NULL。函数的参数说明如下:
      uDelay:以毫秒指定事件的周期。
      Uresolution:以毫秒指定延时的精度,数值越小定时器事件分辨率越高。缺省值为1ms。
      LpTimeProc:指向一个回调函数。
      DwUser:存放用户提供的回调数据。
      FuEvent:指定定时器事件类型:
      TIME_ONESHOT:uDelay毫秒后只产生一次事件
      TIME_PERIODIC :每隔uDelay毫秒周期性地产生事件。      

  具体应用时,可以通过调用timeSetEvent()函数,将需要周期性执行的任务定义在LpTimeProc回调函数中(如:定时采样、控制等),从而完成所需处理的事件。需要注意的是,任务处理的时间不能大于周期间隔时间。另外,在定时器使用完毕后,应及时调用timeKillEvent()将之释放。

  方式七:对于精确度要求更高的定时操作,则应该使用QueryPerformanceFrequency()和 QueryPerformanceCounter()函数。这两个函数是VC提供的仅供Windows 95及其后续版本使用的精确时间函数,并要求计算机从硬件上支持精确定时器。

     QueryPerformanceFrequency()函数和QueryPerformanceCounter()函数的原型如下:
      BOOL QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);
      BOOL QueryPerformanceCounter(LARGE_INTEGER *lpCount);
  数据类型ARGE_INTEGER既可以是一个8字节长的整型数,也可以是两个4字节长的整型数的联合结构,其具体用法根据编译器是否支持64位而定。该类型的定义如下:
      typedef union _LARGE_INTEGER
      {
          struct
          {
             DWORD LowPart ;// 4字节整型数
             LONG HighPart;// 4字节整型数
          };
          LONGLONG QuadPart ;// 8字节整型数
          
       }LARGE_INTEGER ;
  
     在进行定时之前,先调用QueryPerformanceFrequency()函数获得机器内部定时器的时钟频率,然后在需要严格定时的事件发生之前和发生之后分别调用QueryPerformanceCounter()函数,利用两次获得的计数之差及时钟频率,计算出事件经历的精确时间。下列代码实现1ms的精确定时:
      LARGE_INTEGER litmp;
      LONGLONG QPart1,QPart2;
      double dfMinus, dfFreq, dfTim;
      QueryPerformanceFrequency(&litmp);
      dfFreq = (double)litmp.QuadPart;// 获得计数器的时钟频率
      QueryPerformanceCounter(&litmp);
      QPart1 = litmp.QuadPart;// 获得初始值
      do
      {
         QueryPerformanceCounter(&litmp);
         QPart2 = litmp.QuadPart;//获得中止值
         dfMinus = (double)(QPart2-QPart1);
         dfTim = dfMinus / dfFreq;// 获得对应的时间值,单位为秒
      }while(dfTim<0.001);

posted @ 2008-10-25 09:15 wrh 阅读(1589) | 评论 (0)编辑 收藏


VC定时器 SetTimer 怎么用阿
[此问题的推荐答案]
SetTimer函数的用法
1 )用WM_TIMER来设置定时器

先请看SetTimer这个API函数的原型

UINT_PTR SetTimer(
HWND hWnd, // 窗口句柄
UINT_PTR nIDEvent, // 定时器ID,多个定时器时,可以通过该ID判断是哪个定时器
UINT uElapse, // 时间间隔,单位为毫秒
TIMERPROC lpTimerFunc // 回调函数
);

例如
SetTimer(m_hWnd,1,1000,NULL); //一个1秒触发一次的定时器
在MFC程序中SetTimer被封装在CWnd类中,调用就不用指定窗口句柄了

于是SetTimer函数的原型变为:

UINT SetTimer(UINT nIDEvent,UINT nElapse,void(CALLBACK EXPORT *lpfnTimer)(HWND,UINT ,YINT ,DWORD))

当使用SetTimer函数的时候,就会生成一个计时器。函数中nIDEvent指的是计时器的标识,也就是名字。nElapse指的是时间间隔,
也就是每隔多长时间触发一次事件。第三个参数是一个回调函数,在这个函数里,放入你想要做的事情的代码,你可以将它设定为NULL,
也就是使用系统默认的回调函数,系统默认认的是onTime函数。这个函数怎么生成的呢?你需要在需要计时器的类的生成onTime函数:
在ClassWizard里,选择需要计时器的类,添加WM_TIME消息映射,就自动生成onTime函数了。然后在函数里添加代码,让代码实现功能。
每隔一段时间就会自动执行一次。

例:

SetTimer(1,1000,NULL);

1:计时器的名称;

1000:时间间隔,单位是毫秒;

NULL:使用onTime函数。

当不需要计时器的时候调用KillTimer(nIDEvent);

例如:KillTimer(1);

2) 调用回调函数

此方法首先写一个如下格式的回调函数

void CALLBACK TimerProc(HWND hWnd,UINT nMsg,UINT nTimerid,DWORD dwTime);
然后再用SetTimer(1,100,TimerProc)函数来建一个定时器,第三个参数就是回调函数地址。

二. 或许你会问,如果我要加入两个或者两个以上的 timer怎么办?

继续用SetTimer函数吧,上次的timer的ID是1,这次可以是2,3,4。。。。

SetTimer(2,1000,NULL);

SetTimer(3,500,NULL);

嗯,WINDOWS会协调他们的。当然onTimer函数体也要发生变化,要在函数体内添加每一个timer的处理代码:

onTimer(nIDEvent)

{
switch(nIDEvent)

{
case 1:........;
break;
case 2:.......;
break;
case 3:......;
break;
}
}
本贴来自ZDNetChina中文社区 http://bbs.zdnet.com.cn ,本贴地址:http://bbs.zdnet.com.cn/viewthread.php?tid=313294


VC定时器 SetTimer 怎么用阿
Timer事件,即定时器事件,是在游戏编程中,经常使用的一个事件。借助它可以产生定时执行动作的效果。这篇文章,就和大家一起探讨一下如何使用SetTimer()函数。
1、SetTimer定义在那里?

SetTimer表示的是定义个定时器。根据定义指定的窗口,在指定的窗口(CWnd)中实现OnTimer事件,这样,就可以相应事件了。

SetTimer有两个函数。一个是全局的函数::SetTimer()

UINT SetTimer(
HWND hWnd, // handle of window for timer messages
UINT nIDEvent, // timer identifier
UINT uElapse, // time-out value
TIMERPROC lpTimerFunc // address of timer procedure
);

其中hWnd 是指向CWnd的指针,即处理Timer事件的窗口类。说道窗口类(CWnd),我们有必要来看一下CWnd的继承情况:CWnd有以下子类:CFrameWnd,CDialog,CView,CControlBar等类。这也意味这些类中都可以定义SetTimer事件。

同时,SetTimer()在CWnd中也有定义,即SetTimer()是CWnd的一个成员函数。CWnd的子类可以调用该函数,来设置触发器。

UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) );

参数含义:

nIDEvent:是指设置这个定时器的iD,即身份标志,这样在OnTimer()事件中,才能根据不同的定时器,来做不同的事件响应。这个ID是一个无符号的整型。

nElapse

是指时间延迟。单位是毫秒。这意味着,每隔nElapse毫秒系统调用一次Ontimer()。

void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD)

Specifies the address of the application-supplied TimerProc callback function that processes the WM_TIMER messages. If this parameter is NULL, the WM_TIMER messages are placed in the application’s message queue and handled by the CWnd object。

意思是,指定应用程序提供的TimerProc回调函数的地址,来处里这个Timer事件。如果是NULL,处理这个Timer事件的定义这个Timer的CWnd对象。他将WM_TIMER消息传递给这个对象,通过实现这个对象的OnTimer()事件来处理这个Timer事件。

所以,一般情况下,我们将这个值设为NULL,有设置该定时器的对象中的OnTimer()函数来处理这个事件。

同样的,我们再看看KillTimer()和OnTimer()的定义:

KillTimer同SetTimer()一样,他也有两个,一个是全局的::KillTimer(),另一个是CWnd的一个函数。他的声明如下:


//全局函数

BOOL KillTimer(
HWND hWnd, // handle of window that installed timer
UINT uIDEvent // timer identifier
);

//CWnd函数

BOOL KillTimer( int nIDEvent );

这两个函数表示的意思是将iD为nIDEVENT的定时器移走。使其不再作用。其用法如同SetTimer()一样。

再看看OnTimer()

CWnd::OnTimer
afx_msg void OnTimer( UINT nIDEvent );

ontimer()是响应CWnd对象产生的WM_Timer消息。nIDEvent表示要响应TIMER事件的ID。

二、Timer事件的使用:

由以上的分析,我们应该很清楚,如何来使用Timer事件。假定我们在视图上画一个渐变的动画。我们首先在菜单栏上添加一个菜单项,给这个菜单添加命令响应:

pView->SetTimer(1,1000,NULL);//pView是视图类的指针,这里是在视图类当中设置一个定时器。

添加完毕,再给视图类添加一个WM_Timer事件的相应。在OnTimer()函数中编写汉书,进行相应。

如此,就能做出动画。
本贴来自ZDNetChina中文社区 http://bbs.zdnet.com.cn ,本贴地址:http://bbs.zdnet.com.cn/viewthread.php?tid=313294

 

 

 

posted @ 2008-10-25 09:02 wrh 阅读(4049) | 评论 (0)编辑 收藏

用GetModuleFileName获取程序当前执行文件名

在开发过程中经常需要获得程序当前的运行目录,这时就可以使用GetModuleFileName函数
DWORD WINAPI GetModuleFileName(
  HMODULE hModule,
  LPTSTR lpFileName,
  DWORD nSize
);

hModule:要获取文件名的模块名柄,null表示当前模块
lpFileName:输出参数,存放取得的文件名
nSize:lpFileName参数的长度


void FileName()
{
    TCHAR lpFileName[MAX_PATH];
    ::GetModuleFileName(null, lpFileName, MAX_PATH);
    SetDlgItemText(IDC_TEXTBOX, lpFileName);
}


//==============================================================================

//==============================================================================
在开发软件的过程里,经常需要把数据保存到当前执行文件路径下面,或者读取当前执行文件路径下的一些配置信息。这时就需要从当前模块里获取所在的目录路径,以便进行固定的位置操作文件。要解决这个需求,就需要调用API函数GetModuleFileName来获取模块所在的路径。
 
函数GetModuleFileName声明如下:
WINBASEAPI
DWORD
WINAPI
GetModuleFileNameA(
    __in_opt HMODULE hModule,
    __out_ecount_part(nSize, return + 1) LPCH lpFilename,
    __in     DWORD nSize
    );
WINBASEAPI
DWORD
WINAPI
GetModuleFileNameW(
    __in_opt HMODULE hModule,
    __out_ecount_part(nSize, return + 1) LPWCH lpFilename,
    __in     DWORD nSize
    );
#ifdef UNICODE
#define GetModuleFileName GetModuleFileNameW
#else
#define GetModuleFileName GetModuleFileNameA
#endif // !UNICODE
hModule是模块的句柄,或者设置为NULL表示当前模块。
lpFilename是保存路径的缓冲区。
nSize是缓冲区的大小。
 
调用函数的例子如下:
#001 //获取当前程序所在路径。
#002  //蔡军生 2007/12/05 QQ:9073204 深圳
#003  void TestGetExePath(void)
#004  {
#005         //
#006         const int nBufSize = 512;
#007         TCHAR chBuf[nBufSize];
#008         ZeroMemory(chBuf,nBufSize);
#009 
#010         //获取当前执行文件的路径。
#011        if (GetModuleFileName(NULL,chBuf,nBufSize))
#012         {
#013               //输出带文件名称路径。
#014               OutputDebugString(chBuf);
#015               OutputDebugString(_T("\r\n"));
#016 
#017               //获取文件路径。
#018               TCHAR* lpStrPath = chBuf;
#019               PathRemoveFileSpec(lpStrPath);
#020               OutputDebugString(lpStrPath);
#021               OutputDebugString(_T("\r\n"));
#022         }
#023 
#024  }
 
输出的结果如下:
g:\work\windows_api\wincpp2\debug\WinCpp.exe
g:\work\windows_api\wincpp2\debug
posted @ 2008-10-21 09:45 wrh 阅读(4748) | 评论 (0)编辑 收藏
仅列出标题
共25页: First 15 16 17 18 19 20 21 22 23 Last 

导航

<2024年10月>
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

统计

常用链接

留言簿(19)

随笔档案

文章档案

收藏夹

搜索

最新评论

阅读排行榜

评论排行榜