关闭

VC技术内幕笔记8-17

2252人阅读 评论(0) 收藏 举报
 

第十七章:读和写文档--MDI应用程序


1, MFC库中CMDIFrameWnd类封装了主框架窗口和MDI客户窗口所有的函数(包含了所有Windows MDI消息的消息控制函数),因而完全可以管理它的子窗口(由CMDIChildWnd类对象来表示的)。

2,MDI应用程序中,任何时候只有一个子窗口处于活动状态。MDI应用程序只有以个 菜单和工具栏,其上的所有的命令都被传递到当前活动子窗口中。主窗口的标题栏中显示活动窗口的文档文件名。

3,MDI应用程序在启动次序上和SDI程序大部分相同。MDI中被派生类中重载的InitInstance函数与SDI有所不同。(具体的不同跟踪下代码或见362页啊,我就不抄了)

4,MDI应用程序实质:一个MDI应用程序可以使用多个文档类型,并允许同时存在不止一个的文档对象。
比较:
SDI中:
 CSingleDocTemplate* pDocTemplate;
 pDocTemplate = new CSingleDocTemplate(
  IDR_MAINFRAME,
  RUNTIME_CLASS(CStudentDoc),
  RUNTIME_CLASS(CMainFrame),       // main SDI frame window
  RUNTIME_CLASS(CStudentView));
 AddDocTemplate(pDocTemplate);

MDI中:
 CMultiDocTemplate* pDocTemplate;
 pDocTemplate = new CMultiDocTemplate(
  IDR_EX17ATYPE,
  RUNTIME_CLASS(CStudentDoc),
  RUNTIME_CLASS(CChildFrame), // custom MDI child frame
  RUNTIME_CLASS(CStudentView));
 AddDocTemplate(pDocTemplate);
//MDI中AddDocTemplate(pDocTemplate)的调用使得MDI应用程序能够支持多个子窗口,而每个子窗口都与一个文档对象和一个视图对象相连接。若干个子窗口(及其相应的视图对象)也可能与一个文档对象相连接。
//MDI动态的创建CChildFrame对象。

5,SDI只能有一个框架窗口类和一个框架窗口对象(CMainFrame:CFrameWnd);MDI应用程序有两个框架窗口类,并且可以有多个框架对象,即:
CMainFrame:CMDIFrameWnd  只有一个对应对象        有菜单和控制栏   无视图
CChildFrame:CMDIChildWnd 每个子窗口对应一个对象  无菜单和控制栏   有视图
对象的创建:CMainFrame对象在应用程序类的InitInstance函数中(即CMainFrame* pMainFrame = new CMainFrame;);CChildFrame对象是当新的子窗口被打开时,由应用程序框架来创建(MDI中InitInstance函数中ProcessShellCommand的调用用以创建子框架)。

6,MDI的InitInstance函数将CWinApp::m_pMainWnd设置成指向应用程序的主框架窗口指针。因而任何时候可以通过AfxGetApp函数来访问m_pMainWnd。

7,一个MDI程序包含两个独立的字符串资源,分别由IDR_MAINFRAME和IDR_EX17ATYPE(视具体的项目变化)来标识的。(详细见P361-362)

8,创建新文档 和 为现有的文档创建新视图(参见362页,很多条款也不抄了)

9,MDI应用程序每次当文档文件从磁盘中载入的时候,都会构造新的文档对象,并且当子窗口被关闭的时候,相应的文档对象被删除。(问题:如果多个子窗口与同一个文档对象相连接,那当一个子窗口被关闭,关联的文档对象会不会同时被删除呢?)

10,SDI中不支持多次AddDocTemplate调用(SDI中在应用程序生存周期内,文档对象、视图对象、框架对象只被创建一次),而MDI中却是通过多次调用AddDocTemplate来支持多个文档模板,每个模板可制定不同的文档类、视图类以及MDI子框架类的组合。


(注:本章笔记有点粗糙,实例也只看了看没跟踪多少代码,以后找个好实例再补上,时间关系,笔记暂停了些时日,见谅哦)
/////////////////////////


VC 技术内幕(第四版)笔记(第16章)

分类:VC学习

第十六章:读和写文档--SDI应用程序


一,序列化:

1,序列化:对象可以被持续,即当程序退出时候它们被存盘,当程序重启时候它们又可被恢复,对象这种存盘和恢复处理过程称之为序列化。
注意:MFC库中,不能利用序列化来代替数据库管理系统。与文档相关的所有对象只能在某个单独的磁盘文件中进行顺序读写,而不支持对象在磁盘文件中随机读写。

2,磁盘文件和归档(Archives):
1)在MFC库中,磁盘文件是通过CFile类的对象来表示的。
2)如果应用程序不直接利用磁盘I/O,而只依赖于序列化处理过程,则可以避免直接使用CFile对象。
3)在serialize函数(序列化函数)与CFile对象之间,还有一个归档对象(CArchive类对象),归档对象为CFile对象缓存数据,同时还保存一个内部标记,用来标示归档存档(写盘)还是载入(读盘)。
4)每次只能有一个活动的归档与文件相连。应用程序框架会很好管理CFile对象及CArchive对象的创建,为CFile对象打开相应的磁盘文件,并且将相应的归档对象与文件对象相连。
5)关系图:
持续文档对象 <--> Serialize <--> CArchive对象 <--> CFile对象 <--> 磁盘
6)当用户选择了File Open或File Save命令时,应用程序框架自动调用Serialize函数。

3,使类可序列化:
1)可序列化的类必须直接或间接从CObject派生,并且在类声明中,必须包括DECLARE_SERIAL宏调用,在类的实现文件中必须包括IMPLEMENT_SERIAL宏调用。
2)编写Serialize函数。
如:
// example for CObject::Serialize
void CAge::Serialize( CArchive& ar )
 {
 CObject::Serialize( ar );
     if( ar.IsStoring() )//IsStoring来判断当前归档是被用来存入还是被用来载入的。
     ar << m_years;
     else
     ar >> m_years;
 }
注意一:插入运算符对值重载,析取运算符对应用重载,有时必须强制转换才能适应编译器:
如:m_nType是枚举类型,则:ar>>(int&)m_nType;ar<<(int)m_nType;
注意二:插入和析取运算符并不适用于CObject派生类内嵌对象。
3)大多数序列化函数都需要调用基类的Serialize函数(一般在第一行调用,如CStudent是从CPerson派生,那么CStudent中Serialize函数第一行就应该为CPerson::Serialize(ar);)。CObject类Serialize是虚函数并且没做任何工作,因而2)中类子中没调用CObject::Serialize函数。
4)对于CObject派生类的内嵌对象中的Serialize函数中总是需要直接调用内嵌对象的Serialize函数。

4,使集合序列化:
1)所有的集合类都是从CObject类派生,并且在集合类声明中都包含有DECLARE_SERIAL宏调用,应此可以调用集合类的Serialize成员函数,方便使集合序列化。
2)如调用了由一组对象组成的COblist集合的Serialize函数,则COblist集合中每个对象的Serialize函数会被依次调用。
3)如果集合中包含了指向不同类对象的指针,则所有的类名都被相应地存储到归档中,以便所有的对象在被创建时都能够调用相应类的构造函数。
4)如果包容对象(如文档)中包含了一个内嵌集合,则被装入的数据会被追加进现存集合中。(因此有必要在载入前对现存的集合清空。通常由DeleteContents函数完成。)
5)当CObject指针集合被从归档中载入时,集合中的每个对象会被按如下步骤处理:
×指定对象的类。
×为每一个对象分配堆存储空间。
×将对象数据载入到新申请的内存中。
×将新对象的指针存进集合中。
6)当用户选择了File菜单的Save或Open菜单项,应用程序框架随即会创建CArchive对象(以及内部的CFile对象),然后再调用文档类的Serialize函数,并且将CArchive对象的引用传递给它。然后派生文档类的Serialize函数会对每一个非临时数据成员进行序列化。


二,SDI应用程序:

1,SDI MFC应用程序的具体启动步骤:
1)Windows将程序装入内存。
2)构造全局对象theApp。(当程序被载入时候,所有的全局对象都会立刻被创建。)
3)Windows调用全局函数WinMain。
4)WinMain自动搜索CWinApp派生类的唯一实例。
5)WinMain调用theApp的InitInstance函数,该函数在派生应用程序中被重载。
6)被重载的InitInstance函数启动文档的载入以及主框架和视图窗口的显示处理过程。
7)WinMain调用theApp的Run成员函数,启动窗口消息和命令消息的分发处理过程。
还可以对其它的CWinApp成员函数进行重载。当应用程序被中止,且所有窗口被关闭后,ExitInstance函数被调用。

2,派生的应用程序类InitInstance函数中可以看到这样代码:
CSingleDocTemplate* pDocTemplate;
 pDocTemplate = new CSingleDocTemplate(
  IDR_MAINFRAME,
  RUNTIME_CLASS(CMy15aDoc),
  RUNTIME_CLASS(CMainFrame),       // main SDI frame window
  RUNTIME_CLASS(CMy15aView));
 AddDocTemplate(pDocTemplate);
通过这组代码将程序类,文档类,视图窗口类以及主框架类建立类之间的相互关系(注意这里是类之间相互关系)。
(即通过AddDocTemplate调用将所有的应用程序元素联系在一起)
说明:
1)应用程序对象在模板被创建之前已经存在,但此时文档,视图,以及框架对象还没有被创建。应用程序框架在以后需要的时候会通过动态创建这些对象,这里是通过适用RUNTIME_CLASS来实现的。
2)P338页两个图:类之间关系图 和 对象之间关系图 这里掠过,找电子版看看。
3)IDR_MAINFRAME 是用来标示字符串表资源的。IDR_MAINFRAME 所标示的字符串被分成一些以"n"结尾的子字符串。当应用程序执行时候,这些子字符串会在各种地方出现。
在RC文件StringTable中可以找到IDR_MAINFRAME串,串的具体对应关系参看P339页。

3,SDI文档多视图两种技术:提供菜单项供用户选择视图,将多视图安排在切分窗口中。

4,创建空文档……CWinApp::OnFileNew函数
应用程序类的在调用了AddDocTemplate函数之后,通过CWinApp::ProcessShellCommand函数间接调用了CWinApp成员函数OnFileNew完成以下工作:
1)构造文档对象,但并不从磁盘中读取数据。
2)构造主框架对象,并创建主框架窗口,但并不对其显示。主框架窗口包括IDR_MAINFRAME菜单,工具栏和状态栏。
3)构造视图对象,并创建视图窗口,但并不对它进行显示。
4)建立文档,主框架和视图对象之间的相互联系(注意这里是对象之间联系)
5)调用文档对象的OnNewDocument虚成员函数。它会调用DeleteContents虚函数。
6)调用视图对象的CView::OnInitiaUpdate虚成员函数。
7)调用框架对象的CFrameWnd::ActivateFrame虚成员函数,以便显示出具有菜单,视图窗口和控制栏的主框架窗口。
说明:上面一些函数是通过框架间接调用。

在SDI应用程序中,文档,主框架以及视图对象都仅被创建一次,并且将存在于程序的整个运行过程中。
CWinApp::OnFileNew函数被InitInstance函数所调用,当用户选择了File New 菜单项的时候也调用(此时不需要再构造文档框架视图对象,而是利用现存的完成上述后三个步骤)。
注意:OnFileNew函数总是要调用DeleteContents函数,以便将文档清空。

5,CDocument::OnNewDocument
如果SDI应用程序不重新使用相同的文档对象,则也就没必要使用OnNewDocument函数(因为可以在文档类构造函数中完成所有的文档初始化工作)。但一般还是得(必须)对OnNewDocument函数进行重载,以便每次用户选择File New或File Open时候,都能利用它来初始化文档对象。
说明:
一般,在构造函数里尽量少的做一些工作,构造函数做的工作越少,失败的机会就越小。象CDocument::OnNewDocument和CView::OnInitialUpdate这样的函数是完成初始化工作的好地方。

6,连接File Open与系列化代码………OnFileOpen函数
当AppWizard创建应用程序时,它会自动将File Open菜单项映射到CWinApp的OnFileOpen成员函数。
OnFileOpen函数进一步调用一组函数来完成一下工作:
1)提示用户选择一个文件。
2)调用已经存在文档对象的CDocument::OnOpenDocument虚成员函数。该函数将打开文件,调用DeleteContents函数,再创建一个用于装入数据的CArchive对象,然后调用文档的Serialize函数从归档中载入数据。
3)调用视图的OnInitialUpdate函数。

除了使用File Open菜单项外,还可以选择使用最近使用过的文件列表。(框架会记录4个最近使用过的文件,并将它们的名字显示在File菜单中。这些文件名在程序的运行过程中被记录在Windows的注册表中。
说明:
可以改变最近使用文件数。(在InitInstance函数里为LoadStdProfileSetting函数提供适当参数)
CWinApp::LoadStdProfileSettings
void LoadStdProfileSettings( UINT nMaxMRU = _AFX_MRU_COUNT );
//nMaxMRU:The number of recently used files to track.
//Call this member function from within the InitInstance member function to enable and load the list of most recently used (MRU) files and last preview state. If nMaxMRU is 0, no MRU list will be maintained.

7,CDocument::DeleteContents
1)当File New或File Open菜单项被选中CDocument::OnNewDocument或CDocument::OnOpenDocument函数都要调用CDocument::DeleteContents函数(清除现存文档对象内容)。
即:当文档对象第一次被创建之后,CDocument::DeleteContents 会被立刻调用,而当文档被关闭,它再次被调用。
2)常在派生类中重载DeleteContents 函数完成对文档对象中内容清除工作。
3) CDocument::DeleteContents      Called by the framework to delete the document’s data without destroying the CDocument object itself. It is called just before the document is to be destroyed. It is also called to ensure that a document is empty before it is reused. This is particularly important for an SDI application, which uses only one document; the document is reused whenever the user creates or opens another document. Call this function to implement an “Edit Clear All” or similar command that deletes all of the document’s data. The default implementation of this function does nothing. Override this function to delete the data in your document.

8,将File Save 和File Save As与系列化代码相连接:
1)当AppWizard在创建应用程序时,将File Save菜单项映射到CDocument类的OnFileSave成员函数。
2)OnFileSave函数调用CDocument::OnSaveDocument函数,OnSaveDocument函数使用归档对象(存入)来调用文档的Sarialize函数。
3)File Save As菜单象以类似方式处理。映射CDocument::OnFileSaveAs函数,OnFileSaveAs函数会调用OnSaveDocument函数。
4)文档存盘的必要的文件管理工作都是由应用程序框架来完成。
注意:
File New和File Open被映射到应用程序类成员函数,而File Save和File Save As则被映射到文档类成员函数。

9,文档的‘胀’标志
许多面向文档的应用程序跟踪用户对文档的修改,当用户试图关闭文档或退出时候,应用程序弹出对话框询问是否需要将文档保存。
1)MFC通过CDocument::m_bModified来支持这种功能。文档被修改(变胀了)则m_bModified=TRUE,否则为FALSE。
2)m_bModified为protected类型,可以通过CDocument类中的SetModifiedFlag和IsModified成员函数来访问。(注意:在MSDN中不能查出CDocument类m_bModified数据成员,不过追踪CDocument类定义可以发现如下定义:protected: BOOL m_bModified; )
3)CDocument::SetModifiedFlag
void SetModifiedFlag( BOOL bModified = TRUE );

CDocument::IsModified
BOOL IsModified( );

10,程序注册
1)利用AppWizard在程序创建Step-4使用Advanced选项为程序添加文件扩展名(或在StringTable里IDR_MAINFRAME里添加)
2)在InitInstance函数里加入下列代码行:RegisterShellFileTypes(TRUE);(注意添加的位置在AddDocTemplate函数之后!)

11,允许程序的拖放:
如果希望运行中的程序可以打开拖到其上的文件,则必须在主程序框架窗口中调用CWnd的DragAcceptFiles函数。
在InitInstance函数中(函数未)添加下列代码:m_pMainWnd->DragAcceptFiles();

VC 技术内幕(第四版)笔记(第15章)

分类:VC学习

第十五章:文档与视图的分离

 

1,SDI应用程序文档类由CDocument类派生,一个文档类可以有一个或多个由CView类派生的视图类。
 
2,重要成员函数:
1)CView::GetDocument
CDocument* GetDocument( ) const;
//文档对象是用来保存数据的,而视图对象则是用来显示数据的。
//一个视图对象只有一个与之相关连的文档对象。
//Return A pointer to the CDocument object associated with the view.Return NULL if the view is not attached to a document.
//Call this function to get a pointer to the view’s document. This allows you to call the document’s member functions.

2)CDocument::UpdateAllViews
void UpdateAllViews( CView* pSender, LPARAM lHint = 0L, CObject* pHint = NULL );
//当文档数据发生修改的时候,调用该函数通知所有的视图对所显示的数据进行相应得更新。
//pSender:Points to the view that modified the document, or NULL if all views are to be updated.
//如果在派生文档类中调用UpdateAllViews函数,则pSender应该设置为NULL;如果UpdateAllViews函数在派生视图类成员函数中调用,则pSender参数应该设置成this(如:GetDocument()->UpdateAllViews(this);)

3)CView::OnUpdate
virtual void OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint );
//当应用程序调用了CDocument::UpdateAllViews函数时,会调用OnUpdate函数更新视图显示。
//通常视图类OnUpdate函数先对文档进行访问,读取文档的数据,然后对视图的数据成员或控制进行更新,以反应文档的变化。
//可以利用OnUpdate函数使视图的某部分无效,以触发视图的OnDraw函数调用,从而利用文档数据来重绘对应的视图窗口。
//默认的OnUpdate函数使得整个窗口矩形无效。
//当程序调用CDocument::UpdateAllViews函数,如果pSender参数指向了某个特定的视图对象,则除了该指定的视图之外,文档的所有其它视图的OnUpdate函数都会被调用。

4)CView::OnInitialUpdate
virtual void OnInitialUpdate( );
//当应用程序被启动,或当用户从File菜单选择了New时候,或当用户从File菜单选择了Open时候,该虚函数都会被自动调用。
//CView基类中的OnInitialUpdate函数除调用OnUpdate函数中没做其它任何事情。如果在派生类中重载该函数一定要调用基类的OnInitialUpdate函数,或调用派生类的OnUpdate函数。
//当应用程序启动时,框架调用视图类的OnCreate函数之后立即调用OnInitialUpdate函数。OnCreate函数只能被调用一次,而OnInitialUpdate可以被调用多次。
//可以通过在派生重载OnInitialUpdate函数,在其中对视图对象进行初始化。

5)CDocument::OnNewDocument
virtual BOOL OnNewDocument( );
//Called by the framework as part of the File New command. The default implementation of this function calls the DeleteContents member function to ensure that the document is empty and then marks the new document as clean. Override this function to initialize the data structure for a new document. You should call the base class version of this function from your override.
//If the user chooses the File New command in an SDI application, the framework uses this function to reinitialize the existing document, rather than creating a new one. If the user chooses File New in a multiple document interface (MDI) application, the framework creates a new document each time and then calls this function to initialize it. You must place your initialization code in this function instead of in the constructor for the File New command to be effective in SDI applications.


3,简单文档视图交互应用程序步骤(单文档单视图):
1)在派生文档类中定义文档的数据成员,用以保存程序中数据。为了方便派生视图类的访问可以将这些数据成员定义成公有类型或将派生视图类定义为派生文档类的友元类。
2)在派生视图类中对OnInitialUpdate虚成员函数进行重载。当文档数据被初始化或被从磁盘中读出后,框架会自动调用OnInittialUpdate函数。该函数对视图进行更新,以便放映出当前的文档数据。
3)在派生视图类中,让窗口消息控制函数和命令消息控制函数直接读取和更新文档数据成员,利用GetDocument函数对文档对象进行访问。

对应事件发生次序:
程序启动: CMyDocument对象被创建-》CMyView对象被创建-》视图窗口被创建-》CMyView::OnCreate函数被调用(如果被映射)-》CMyDocument::OnNewDocument函数被调用-》
CMyView::OnInitialUpdate函数被调用-》视图对象被初始化-》视图窗口无效-》CMyView:OnDraw函数被调用
用户编辑数据: CMyView类中函数对CMyDocument数据成员进行更新
退出程序: CMyView对象被删除-》CMyDocument对象被删除


4,CFormView类
CFormView类具有许多无模式对话框的特点,其派生类也和相应的对话框资源相联系,也支持DDX和DDV等。
CFormView类对象可以直接接收来之本身的控制通告消息,也可接收来自框架窗口的命令消息。(同时具有许多无模式类对话框和视图类特征)。
派生层次:CView|CScrolView|CFromView
CFormView类虽然不是从CDialog类派生,但是围绕对话框创建,因而可以使用许多CDialog类成员函数。(这时只需将CFormView指针强制转换成CDialog类指针即可)如:((CDialog*)this)->GotoDlgCtrl(GetDlgItem(IDC_NAME));


5,高级文档视图交互应用程序步骤(单文档多视图):
编写多视图应用程序,只要对其中某一视图编辑改变了文档,则其它的文档需要随之更新,以反映出文档的变化。
步骤:
1)在派生文档类中定义需要的数据成员。同时设计访问该数据成员的方法,或将视图类设计成文档类的友元类。
2)在派生视图类中,利用向导重载OnUpdate虚成员函数。当文档数据被修改后,应用程序框架会自动调用该函数。利用重载的OnUpdate函数来完成视图的更新,以反映当前的文档数据。
3)对所有的命令消息判断其哪些是针对文档的哪些是针对视图的,然后将其映射到相应的类中。
4)在派生视图类中,允许对文档数据进行更改,在退出之前,一定要调用CDocument::UpdateAllViews函数更新其它的视图。使用CView::GetDocument获取相关联的文档对象指针。
5)在派生文档类中,允许对文档数据进行修改,修改后退出前要调用UpdateAllViews函数更新与其关联的所有视图。

对应发生的事件次序:
应用程序启动:CMyDocument对象被创建-》CMyView对象被创建-》其它视图对象被创建-》视图窗口被创建-》CMyView::OnCreate函数被调用(如果被映射)-》CMyDocument::OnNewDocument函数被调用-》CMyView::OnInitialUpdate函数被调用-》调用CMyView::OnUpdate-》初始化视图
用户编辑数据: 视图类中函数对CMyDocument数据成员进行更新
  退出时候调用CDocument::UpdateAllViews函数更新其它的视图
退出程序: 视图对象被删除-》CMyDocument对象被删除


6,笔记略过内容:诊断信息转储(参见P296-P300),CObList集合类(参见P308-312)
1)选择Debug目标时_DEBUG常量候被定义,程序中的诊断代码会被连到程序中去。
2)选择Release目标,诊断信息转储被禁止,诊断代码也不会被连到程序中去。由Release目标生成的可执行文件非常小(比使用Debug目标生成的目标文件小很多呢)。
3)CObList很重要的特性是可以包含混合指针。

VC 技术内幕(第四版)笔记(第14章)

分类:VC学习

第十四章:可重用框架窗口类


1, 可重用基类的设计:为某个工程所设计的类应该能够被提取出来 ,使它进一步一般化后被应用于其他的应用程序。

2, 一些重要函数:
1)CFrameWnd::ActivateFrame
virtual void ActivateFrame( int nCmdShow = -1 );
//以nCmdShow作为参数调用CWnd::ShowWindow函数来显示框架窗口(其菜单视图控制栏窗口也会被同时显示)。nCmdShow决定窗口是最大化还是最小化等。
//在派生类中重载ActivateFrame ,在nCmdShow传递给CFrameWnd::ActivateFrame之前修改nCmdShow的值。还可以调用CWnd::SetWindowPlacement设置框架窗口的位置和尺寸并还可以设置控制栏的可视状态。
2)CWnd::PreCreateWindow
virtual BOOL PreCreateWindow( CREATESTRUCT& cs );
//PreCreateWindow在调用ActivateFrame函数之前被框架调用,用以在窗口显示之前改变其窗口的特性(特性主要参考:CREATESTRUCT结构。
//在派生类中重载它,在CS传给基类之前改变CS结构中值,从而实现定制(显示)窗口。
3)在MDI中,主框架窗口的ShowWindow函数是由应用程序类InitInstance函数调用,不是由CFrameWnd::ActivateFrame函数调用。故要控制MDI主框架窗口的一些特性,应该在应用程序类InitInstance函数中添加相应的代码。

3,Window注册表
1)Window注册表是一组系统文件,由Window管理,Windows和其他的应用程序可以在注册表中保存一些永久的信息。
Windows 注册表被组织成一个层次的数据库,字符和整型数据可以通过一个多部分键值来访问。
2)操作:
SetRegistryKey   Causes application settings to be stored in the registry instead of .INI files.

GetProfileInt   Retrieves an integer from an entry in the application’s .INI file.
WriteProfileInt  Writes an integer to an entry in the application’s .INI file.
GetProfileString  Retrieves a string from an entry in the application’s .INI file.
WriteProfileString  Writes a string to an entry in the application’s .INI file.
说明:
1)vc5开始,AppWziard在应用程序类的InitInstance函数中有对CWinApp::SetRegistryKey函数的调用,如下:
 SetRegistryKey(_T("Local AppWizard-Generated Applications"));
该函数的调用使应用程序使用注册表,如果去掉则应用程序不使用注册表,仅在Windows目录下创建一个INI文件,并使用该文件。
SetRegistryKey的字符串参数建立最上层键,其后的注册表函数定义下面的两层(称为头名和入口名)。
2)为能够使用注册表访问函数,需要一个指向应用实例对象的指针,可以通过AfxGetApp函数获得。如下:
AfxGetApp()->WriteProfileString("Text formatting","Font","Times Roman");
AfxGetApp()->WriteProfileInt("Text Formatting","Points",10);
3)注册表访问函数可以将注册表当成CString对象或无符号整数来处理。


4,CString类中,LPCTSTR并不是一个指向CString对象的指针,而是一个用来代替const char*的Unicode版本。
1) CString str;
 char ch[]="abcdefg";
 strcpy(str.GetBuffer(strlen(ch)),ch);
 str.ReleaseBuffer();
 pDC->TextOut(0,0,str);
输出:abcdefg
2) CString str("abcdefg");
 char ch[20];
 strcpy(ch,LPCTSTR(str));
 pDC->TextOut(0,0,ch);
输出:abcdefg
说明:
char *strcpy( char *strDestination, const char *strSource );
3)编写带字符串参数的函数原则:
a,如果不改变字符串内容:可以使用const char*形参,可以使用CString&类型形参
b,如果改变字符串内容,可以使用CString&类型形参(当然可以使用指针,但这儿不建议)。


5,获取窗口坐标
GetWindowPlacement
//Retrieves the show state and the normal (restored), minimized, and maximized positions of a window.
SetWindowPlacement(可以返回最大化前的屏幕坐标)
//Sets the show state and the normal (restored), minimized, and maximized positions for a window.
GetWindowRect
//Gets the screen coordinates of CWnd.

VC 技术内幕(第四版)笔记(第13章)

分类:VC学习

第十三章:工具栏和状态栏

 

1,工具栏是CToolBar类一个对象,状态栏是CStatusBar类的对象.CToolBar类和CStatusBar类均由CControlBar类派生.CControlBar类则由CWnd类派生.
CControlBar类所支持的控制栏窗口位于主框架窗口内,并且这些控制栏窗口能够随着主框架窗口的尺寸改变或移动自动调整自己的尺寸与位置.
控制栏对象的构造与析构以及窗口的创建都是由应用程序框架来管理的,其向导产生的代码位于框架头文件和代码文件中.

2,工具栏的按钮被按下是会象菜单或加速键一样,发送相应的命令消息(该命令消息会象菜单命令一样进行传递.原因:工具栏对象以及状态栏对象都与主框架窗口联系.).
更新命令UI消息控制函数一方面可以用来更新工具栏中的按钮的状态,另一方面可以用来修改工具栏中的按钮图形.
整个工具栏只有一个位图,每一个按钮在其中占一个高15像素宽16像素的位图片.
注意:不要直接编辑工具栏位图,而是应该使用DeveloperStudio的特定工具栏编辑工具进行编辑.

3,更新命令消息控件函数主要是用来对菜单项进行禁止和复选的,但也可以作用于工具栏按钮.
1)CCmdUI::Enable
virtual void Enable( BOOL bOn = TRUE );
//bOn: TRUE to enable the item, FALSE to disable it.
//Call this member function to enable or disable the user-interface item for this command.
2)CCmdUI::SetCheck
virtual void SetCheck( int nCheck = 1 );
//nCheck: Specifies the check state to set. If 0, unchecks; if 1, checks; and if 2, sets indeterminate.
//Call this member function to set the user-interface item for this command to the appropriate check state. This member function works for menu items and toolbar buttons. The indeterminate state applies only to toolbar buttons.

4,当某菜单项所在的弹出式菜单弹出时候,对该菜单项进行处理的更新命令UI消息控制函数才会被调用.
工具栏总是处于显示状态,对它进行处理的更新命令UI控制函数是在应用程序的空状态处理过程中被调用的.
当同一个更新命令UI控制函数既需要对某菜单项处理,又需要对某工具栏按钮进行处理,则在空状态处理过程及当该菜单项所在的弹出式菜单弹出时候,该控制函数都会被调用.

5,创建工具提示:
把提示文本加在菜单提示的后面,前面加一个换行符.如在Prompt:打印活动文档n打印

6,几个重要函数
1)CWnd::GetParentFrame 
CFrameWnd* GetParentFrame( ) const;
//Return Value:A pointer to a frame window if successful; otherwise NULL.
//Call this member function to retrieve the parent frame window. The member function searches up the parent chain until a CFrameWnd (or derived class) object is found.
//MDI中得到的是MDI子框架窗口.
2)AfxGetApp 
CWinApp* AfxGetApp( );
//Return Value:A pointer to the single CWinApp object for the application.
//The pointer returned by this function can be used to access application information such as the main message-dispatch code or the topmost window.
通过应用程序获得主框架窗口指针:(单文档和多文档程序均适用)
 CMainFrame* pFrame=(CMainFrame*)AfxGetApp()->m_pMainWnd;
 CToolBar* pToolBar=&pFrame->m_wndToolBar;
注:在SDI程序中,当视图的OnCreate函数被调用时候,m_pMainWnd还没有被设置,这时候可通过CWnd::GetParentFrame来获取主框架窗口指针.
说明:
在MDI程序中AppWizard向导自动生成了对m_pMainWain的赋值代码.而在SDI中,框架是在视图的创建的过程中对m_pMainWain的赋值代码的.


7,工具栏编辑器的使用:用鼠标选中某按钮,然后按DEL键可以擦除该按钮的像素;如果想删掉某个按钮,只需要把它拖出工具栏即可.

8,工具栏的停靠:
在CMainFrame::OnCreate函数中可以看到如下代码,该代码允许工具栏在框架窗口任何边筐处停靠:
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
说明:
1)CControlBar::EnableDocking
//Call this function to enable a control bar to be docked. The sides specified must match one of the sides enabled for docking in the destination frame window, or the control bar cannot be docked to that frame window.
2)CFrameWnd::EnableDocking
//Call this function to enable dockable control bars in a frame window. By default, control bars will be docked to a side of the frame window in the following order: top, bottom, left, right.
3)CFrameWnd::DockControlBar
//Causes a control bar to be docked to the frame window. The control bar will be docked to one of the sides of the frame window specified in the calls to both CControlBar::EnableDocking and CFrameWnd::EnableDocking. The side chosen is determined by nDockBarID.

 

9,状态栏既不接受用户输入也不产生命令消息,它的作用就是在程序的控制下在窗格(pane)中显示一些文本.
状态栏支持两种类型文本窗格(信息行窗格 和 状态指示器窗格).
注:为实现在状态栏中显示一些应用程序的特殊数据,必须先禁止标准状态栏显示菜单提示及键盘状态.(具体方法?)
1)AppWizard在MainFrm.cpp文件中产生的静态数组indicators是用以定义状态栏的.
static UINT indicators[] =
{
 ID_SEPARATOR,           // status line indicator
 ID_INDICATOR_CAPS, //以下均为串资源ID
 ID_INDICATOR_NUM,
 ID_INDICATOR_SCRL,
};
2)CStatusBar::SetIndicators//根据indicators[]数组内容对状态栏进行设置.
BOOL SetIndicators( const UINT* lpIDArray, int nIDCount );
//Sets each indicator’s ID to the value specified by the corresponding element(对应元素) of the array lpIDArray, loads the string resource specified by each ID, and sets the indicator’s text to the string.
//SetIndicators在应用程序的派生框架类中被调用.在CMainFrame::OnCreate函数中可以看见.
3)信息行窗格所显示的是储蓄动态提供的字符串.
对信息行的设置:首先访问状态栏对象,然后再用从0开始的索引参数来调用CStatusBar::SetPaneText函数设置.
如:
 CMainFrame* pFrame=(CMainFrame*)AfxGetApp()->m_pMainWnd;
 CStatusBar* pStatus=&pFrame->m_wndStatusBar;
 pStatus->SetPaneText(0,"message line for first pane");
4)状态指示器窗格总是与一个字符串相连,该字符串是由字符串资源来提供的(在string table里设置),它是否被显示完全取决于相应的更新命令UI控制函数.
指示器是由一个字符串资源ID来标识的,该ID也被用来传递更新命令UI消息.
afx_msg void CMainFrame::OnUpdateKeyCapsLock(CCmdUI *pCmdUI);
ON_UPDATE_COMMAND_UI(ID_INDICATOR_CAPS,OnUpdateKeyCapsLock)
void CMainFrame::OnUpdateKeyCapsLock(CCmdUI *pCmdUI)
{
 pCmdUI->Enable(::GetKeyState(VK_CAPITAL)&1);
}
说明:
状态栏的更新命令UI消息控制函数是在空闲处理阶段被调用的.(推测:调用的时候传入CCmdUI指针).
5)状态指示器窗格的长度即为相应字符串资源的长度.


10,获取状态栏的控制.
1)用于键盘状态指示器的更新命令UI控制函数被嵌入在主框架窗口类中,它们与资源字符串ID(indicators[]中定义的串资源ID)相联.
2)状态栏实际上有一个子窗口(即status line indicator),其默认ID在CMainFrm::OnCreate函数中的CStatusBar::Create函数中设置为AFX_IDW_STATUS_BAR.改变这个ID可以实现禁止框架在0号窗格中显示菜单提示.
具体实现如下:
将CMainFrm::OnCreate函数中的CStatusBar::Create函数由m_wndStatusBar.Create(this)改成m_wndStatusBar.Create( this,WS_CHILD | WS_VISIBLE | CBRS_BOTTOM, ID_MY_STATUS_BAR(自定义的ID)).


11,注意,左右鼠标按钮跟键盘上的键一样有虚键码,可以通过GetKeyState函数获取。

强烈建议自己动手实现书中两个事例,尤其是第二个了,自己做了就明白这章讲的是什么个回事了.

VC 技术内幕(第四版)笔记(第12章)

分类:VC学习

第十二章:菜单、键盘加速键、复文本编辑控件和属性表


1,主框架窗口包含标题栏和菜单栏,而各种各样的子窗口(包括工具栏窗口、状态栏窗口和视图窗口)覆盖着主框架窗口的客户区域。应用程序通过框架和视图之间的消息传递来控制框架和视图之间的相互作用。

2,MFC应用程序框架提供了一个非常复杂的命令消息传递系统,这些消息来自菜单的选择、键盘加速、以及工具栏和对话框按钮,另外,命令消息也可以通过调用CWnd::SendMessage或PostMessage函数来发送。
1)大多数命令消息都来自主框架窗口,但应用程序框架并没有对其一一的响应处理,需要我们自己在适当的地方添加响应函数加以响应处理。
2)当应用程序框架接收到框架窗口的命令,它将按下列次序来寻找其消息控制函数:
SDI程序(单文档):视图(VIEW类)-》文档(DOC类)-》SDI主框架窗口(FRAME类)-》应用程序(APP类)。
MDI程序(多文档):视图--》文档--》MDI子框架窗口--》MDI主框架窗口--》应用程序。
说明:
当框架接收到框架窗口命令,安上叙的次序寻找其消息控制函数的时候,一旦找到,就停止继续往下寻找,这样当路径中有多个该消息控制函数的时候,只执行先找到的那个。

3,命令传递系统反映对命令消息控制的一个方面,类的层次结构反映了对命令消息进行控制处理的另一个方面。
1)一般,派生类会继承基类所有的消息映射函数,其中包括命令消息函数。
2)如果想对基类的消息映射函数进行重载,就必须在派生类中加入相应的消息映射函数和相应的消息映射入口(及函数声明)。

4,所有的弹出式菜单都属于主框架窗口。
1)弹出式菜单上按钮(及资源编辑器中菜单项)的ID必须在0x8000-0xDFFF范围内,才可以产生可被传递的命令消息。
2)使用符号编辑器来输入ID,可以保证ID范围落在0x8000-0xDFFF范围内。
(注:书中是弹出式对话框,不过应该是弹出式菜单才对。)

5,CFrameWnd::m_bAutoMenuEnable
//Controls automatic enable and disable functionality for menu items.
//CFrameWnd类中的BOOL型数据成员,默认为TRUE。
//如果m_bAutoMenuEnable设为TRUE,则如果某菜单项在当前命令路径中无法找到相应的命令消息控制函数,则此时应用程序框架便禁用该菜单项,并将该菜单项设为灰色。
//如果m_bAutoMenuEnable设为FALSE,便可以禁止上叙自动禁用无控制函数菜单项的特性。
(试一试:自己在框架类构造函数CMainFrame::CMainFrame()中添加this->m_bAutoMenuEnable=TRUE语句,然后看看菜单里菜单项里看看有什么变化哦)


6,MFC文本编辑选择
1)CEditView类:文本大小限制在64K以内,不能设混合字体,实现了基本的编制功能。
2)CRichEditView类:使用了富文本编辑控件,支持混合字体和大数据量文本。
3)CRichEditCtrl类:封装了富文本编辑控件。
//CRichEditCtrl   A window in which the user can enter and edit text with character and paragraph formatting. The control can include embedded OLE objects.


7,示例事例说明:
事例EX12A(未优化前的)中使用CRichEditCtrl类来在基类为CView类的项目中设计编辑功能:
1)方法:构造CRichEditCtrl类对象,并为之创建控件窗口与之关联,设置控件窗口为当前视图客户区大小,使CRichEditCtrl类控件窗口覆盖当前视图客户区,CRichEditCtrl类控件窗口具有富文本编辑功能,从而完成设计。
2)实现细节:
CRichEditCtrl m_rich;//构造CRichEditCtrl对象
m_rich.Create(ES_AUTOVSCROLL | ES_MULTILINE | ES_WANTRETURN | WS_CHILD | WS_VISIBLE | WS_VSCROLL,rect,this,1);//创建控件窗口用以初始化CRichEditCtrl对象
m_rich.SetWindowPos(&wndTop,0,0,rect.right-rect.left,rect.bottom-rect.top,SWP_SHOWWINDOW);
//设置CRichEditCtrl控件窗口大小(使用GetClientRect(rect)获得视图客户区大小)和位置,即覆盖视图客户区域。
3)获取和保存控件文本技巧:
在VIEW类中获取对应DOC类对象指针:CEX12aDoc *pDoc=GetDocument();
获取控件窗口文本:m_rich.SetWindowText(pDoc->m_strText);
设置控件窗口文本:m_rich.GetWindowText(pDoc->m_strText);
技巧说明:在视图类中利用GetDocument()函数获取其对应文档类对象指针,从而可将m_rich控件窗口文本保持到文档类数据成员m_strText中。
4)CRichEditCtrl::SetModify
void SetModify( BOOL bModified = TRUE );
bModified》》A value of TRUE indicates(象征,预示) that the text has been modified, and a value of FALSE indicates it is unmodified. By default, the modified flag is set.
5)关于加速键的添加:在在资源管理器Accelerator中添加(如:ID_GETDATA   VK_F2)。

8,学用属性表
这里提供一种利用资源管理器创建属性表的方法:
第一步:利用资源管理器添加几个对话框作为属性页(这样可方便往属性页上添加控件并处理了)。
第二步:分别为作为属性页的对话框创建类,基类采用CPropertyPage。
第三步:利用类向导创建用户属性表类,基类采用CPropertySheet。
第四步:在刚创建的用户属性表类中添加需要属性页类数据成员,并在属性表类构造函数中调用CPropertySheet::AddPage函数将所需要的属性页一一添加到属性表中去。目前为止属性表类创建结束。
第五步: 在需要创建和显示属性表的地方构造用户属性表对象,并调用CPropertySheet::DoModal显示。
说明:
1)CPropertySheet
Objects of class CPropertySheet represent property sheets, otherwise known as tab dialog boxes. A property sheet consists of a CPropertySheet object and one or more CPropertyPage objects. A property sheet is displayed by the framework as a window with a set of tab indices, with which the user selects the current page, and an area for the currently selected page.

Even though CPropertySheet is not derived from CDialog, managing a CPropertySheet object is similar to managing a CDialog object.

Exchanging data between a CPropertySheet object and some external object is similar to exchanging data with a CDialog object.

2)CPropertyPage
Objects of class CPropertyPage represent individual pages of a property sheet, otherwise known as a tab dialog box. As with standard dialog boxes, you derive a class from CPropertyPage for each page in your property sheet. To use CPropertyPage-derived objects, first create a CPropertySheet object, and then create an object for each page that goes in the property sheet. Call CPropertySheet::AddPage for each page in the sheet, and then display the property sheet by calling CPropertySheet::DoModal for a modal property sheet, or CPropertySheet::Create for a modeless property sheet.

3)Apply按钮的处理:
在所有的属性表类中,只要控件给属性页发送一个消息(如:点击或选择属性页上的控件,框架便调用该属性页OnCommand函数来响应这些控件所发出的通告消息,OnCommand函数是虚函数),在每个属性页中重载OnCommand函数设置SetModified(TRUE)可使Apply按钮有效。
当用户点击Apply按钮,框架自动调用OnApply函数,可以在重载它来完成一些重要的任务。
CWnd::OnCommand 
virtual BOOL OnCommand( WPARAM wParam, LPARAM lParam );
//The framework calls this member function when the user selects an item from a menu, when a child control sends a notification message, or when an accelerator keystroke is translated.

CPropertyPage::OnApply
virtual BOOL OnApply( );
//This member function is called by the framework when the user chooses the OK or the Apply Now button. When the framework calls this function, changes made on all property pages in the property sheet are accepted, the property sheet retains focus, and OnApply returns TRUE (the value 1). Before OnApply can be called by the framework, you must have called SetModified and set its parameter to TRUE. This will activate the Apply Now button as soon as the user makes a change on the property page.

Override this member function to specify what action your program takes when the user clicks the Apply Now button. When overriding, the function should return TRUE to accept changes and FALSE to prevent changes from taking effect.

The default implementation of OnApply calls OnOK.


9,CMenu类
CMenu类对象可以用来表示各种Windows菜单,包括顶层菜单项及相应的弹出式菜单。
添加菜单方法:
程序添加法:1)定义菜单资源;2)构造菜单对象;3)调用CMenu::LoadMenu函数将菜单将菜单资源载入;4)调用CWnd::SetMenu函数将菜单连接到框架窗口中;5)调用CMenu::Detach函数把对象HMENU句柄独立出来(这样当CMenu对象销毁时候,菜单不随之销毁)
/////////////////////////////////
/////////////////////////////////

另一中简便的添加菜单方法:如果想为某对话框添加菜单,可从话框属性中Menu项中选择资源管理器中的需要的菜单完成添加工作。

10,创建浮动式弹出菜单
步骤:
1)用资源编辑器添加菜单资源
2)在视图类或其他接收鼠标右键单击的窗口类中添加WM_CONTEXTMENU消息控制函数,编辑代码如下:
void CAboutDlg::OnContextMenu(CWnd* pWnd, CPoint point)
{
 CMenu menu;
 menu.LoadMenu(IDR_MENU1);
 menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,point.x,point.y,this);
 
}
说明:
GetSubMenu函数得到指向弹出式菜单的CMenu指针。
TrackPopupMenu在指定位置显示浮动菜单,并跟踪弹出菜单的选择项。

11,关于扩展命令的处理:
扩展命令无法使用向导添加,需要自己添加,添加的方法如普通的命令处理类似。具体参看书P256-257页。

////////////////////////
/////////////////////
后续章节笔记待续

VC 技术内幕(第四版)笔记(第11章)

分类:VC学习

第十一章:Winsows消息处理和多线程编程


一)Winsows消息处理

1,单线程:程序代码只有一条执行路径。

2,单线程程序消息处理过程:
MSG message;
while(::GetMessage(&message,NULL,0,0))
{
::TranslateMessage(&message);//翻译如wm_char消息
::DispatchMessage(&message);//把消息分发给指定窗口的回调函数
}
说明:
1)os决定哪个消息属于我们的程序,当一个消息要处理的时候,用GetMessage函数返回该消息。
2)如果没有属于我们程序的消息发出,则我们的程序被挂起,而其它的程序可以运行;当属于我们程序的消息到达,我们的程序被唤醒

3,改造2中的单线程消息处理过程:
问题一:某个消息控制函数很“笨重”,消耗CPU时间过久。
问题二:DispatchMessage函数要等到该笨重消息控制函数返回后才能返回。在该笨重消息控制函数返回之前DispatchMessage不处理(分发)任何消息。
解决方法:使用PeekMessage函数使占用CPU时间过久的消息控制函数每隔一段时间交出控制一次。(在占CPU时间过久的控制函数中利用PostMessage发送
代码如下:
MSG message;
while(::PeekMessage(&message,NULL,0,0,PM_REMOVE))
{
::TranslateMessage(&message);//翻译如wm_char消息
::DispatchMessage(&message);//把消息分发给指定窗口的回调函数
}
说明:
//The PeekMessage function checks a thread message queue for a message and places the message (if any) in the specified structure.
简要说明:
1)BOOL PeekMessage(
  LPMSG lpMsg,         // pointer to structure for message
  HWND hWnd,           // handle to window
  UINT wMsgFilterMin,  // first message
  UINT wMsgFilterMax,  // last message
  UINT wRemoveMsg      // removal flags
);
//If a message is available, return nonzero,otherwise return 0;
//Unlike the GetMessage function, the PeekMessage function does not wait for a message to be placed in the queue before returning.
//PeekMessage retrieves only messages associated with the window identified by the hWnd parameter or any of its children as specified by the IsChild function, and within the range of message values given by the wMsgFilterMin and wMsgFilterMax parameters.
//If hWnd is NULL, PeekMessage retrieves messages for any window that belongs to the current thread making the call.
//If hWnd is –1, PeekMessage only returns messages with a hWnd value of NULL, as posted by the PostThreadMessage function.
//If wMsgFilterMin and wMsgFilterMax are both zero, PeekMessage returns all available messages (that is, no range filtering is performed).
//PeekMessage does not retrieve messages for windows that belong to other threads.

2)BOOL GetMessage(
  LPMSG lpMsg,         // address of structure with message
  HWND hWnd,           // handle of window
  UINT wMsgFilterMin,  // first message
  UINT wMsgFilterMax   // last message
);
//GetMessage does not retrieve messages for windows that belong to other threads or applications.
//

4,计时器独立于CPU时钟速度。
1)计时器的使用:用一个时间间隔参数调用CWnd::SetTimer函数,然后再用ClassWizard为WM_TIMER添加消息控制函数。
2)一旦调用CWnd::SetTimer启动了计时器,则WM_TIMER消息会被持续发送到指定的窗口,直至调用CWnd::KillTimer函数或计时器窗口被取消为止。
3)由于WINDOWS非实时OS,如果时间间隔少于100毫秒,计时器可能不准。
4)OS发送计时器消息的时候,如果队列里已经有了计时器消息,则不会把同样的计时器消息放进消息队列里。
说明:
1)CWnd::SetTimer
UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) );
//Return The timer identifier of the new timer if the function is successful. An application passes this value to the KillTimer member function to kill the timer. Nonzero if successful; otherwise 0.
//nIDEvent:Specifies a nonzero timer identifier.
//nElapse:Specifies the time-out value, in milliseconds.
//lpfnTimer: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.
//CWnd::SetTimer Installs a system timer. A time-out value is specified, and every time a time-out occurs, the system posts aWM_TIMER message to the installing application’s message queue or passes the message to an application-defined TimerProc callback function.
2)CWnd::KillTimer
BOOL KillTimer( int nIDEvent );
//Return value:Return nozero if the event is killed.It is 0 if the KillTimer member function could not find the specified timer event.
//CWnd::KillTimer Kills the timer event identified by nIDEvent from the earlier call to SetTimer. Any pending WM_TIMER messages associated with the timer are removed from the message queue.


5,在引入多线程编程之前,WINDOWS程序员曾使用空状态处理来完成一些后台任务。(这里是OnIdle函数)
1)应用程序框架在框架消息处理循环中调用CWinApp::OnIdle(虚函数),可以重载它来处理后台任务。通常,一但OnIdle函数完成了它的工作,就要等到下次应用程序消息队列空了后才被调用。基类的OnIdle会更新工具栏按钮和状态栏指示器,并清除各种临时对象指针。
2)重载OnIdle函数可以用来更新用户界面,很有实际意义。(注:在重载OnIdle函数中一定要调用基类的OnIdle函数哦)
3)如果用户运行在一个模式对话框或正在使用菜单,则这时OnIdle函数不被调用。但我们可以在框架类中添加WM_ENTERIDLE消息控制函数来在上叙情况下使用后台处理。(注意,这里是在框架类中添加,不是在视图类中。弹出式对话框中是属于应用程序主框架窗口。)

///////
///////

二,多线程编程:

1,一些概念:
1)一个进程就是一个运行的程序,进程具有独立的内存、文件句柄和其它系统资源。
2)一个独立的进程可以包含多条执行路径,称为线程。线程有OS管理,每个线程有自己的堆栈。
3)大多数情况下,进程的所有代码和数据空间被进程内所有的线程所共享。
4)Windows提供两中线程:辅助线程 和 用户界面线程。mfc对两种线程都支持。
5)一个用户界面线程有窗口,因而有自己的消息循环;辅助线程没有窗口,因而不需要消息处理。

2,启动辅助线程:
准备工作:写一个全局函数。格式:UINT MyControllingFunction( LPVOID pParam );
启动辅助线程:使用AfxBeginThread函数为全局函数MyControllingFunction创建线程。
说明一:
MFC 通过参数重载提供两个版本的 AfxBeginThread:一个用于用户界面线程,另一个用于辅助线程。
//用户界面线程通常用于处理用户输入和响应用户事件,这些行为独立于执行该应用程序其他部分的线程。已经创建并启动主应用程序线程(在 CWinApp 导出的类中提供)。
//辅助线程通常用于处理后台任务,用户不必等待就可以继续使用应用程序。重新计算和后台打印等任务是很好的辅助线程示例。
说明二:
创建用户界面线程比创建辅助复杂(需要重写一些相关的函数),这里只笔记创建辅助线程一些笔记。
1)AfxBeginThread
CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );
//Return Value:Pointer to the newly created thread object.
//pfnThreadProc:Points to the controlling function for the worker thread. Cannot be NULL. This function must be declared as follows:    UINT MyControllingFunction( LPVOID pParam );
//pParam:Parameter to be passed to the controlling function as shown in the parameter to the function declaration in pfnThreadProc.
//nPriority:The desired priority(优先权) of the thread. If 0, the same priority as the creating thread will be used. For a full list and description of the available priorities, seeSetThreadPriority in the Win32 Programmer’s Reference.
//nStackSize:Specifies the size in bytes of the stack for the new thread. If 0, the stack size defaults to the same size stack as the creating thread.
//dwCreateFlags:Specifies an additional flag that controls the creation of the thread. This flag can contain one of two values:
[CREATE_SUSPENDED]   Start the thread with a suspend count of one. The thread will not execute until ResumeThread is called.
[ 0  ]  Start the thread immediately after creation.
//lpSecurityAttrs:Points to a SECURITY_ATTRIBUTES structure that specifies the security attributes for the thread. If NULL, the same security attributes as the creating thread will be used.
//Remarks:
Call this function to create a new thread. The first form of AfxBeginThread creates a worker thread. The second form creates a user-interface thread.
To end the thread, call AfxEndThread from within the thread, or return from the controlling function of the worker thread.
2)挂起线程:
CWinThread::SuspendThread
DWORD SuspendThread( );
//Return Value:The thread’s previous suspend count if successful; 0xFFFFFFFF otherwise.
//CWinThread::SuspendThread:Increments the current thread’s suspend(悬挂,延缓) count. If any thread has a suspend count above zero, that thread does not execute.
3)恢复线程运行:
CWinThread::ResumeThread
DWORD ResumeThread( );
//Return Value:The thread’s previous suspend count if successful; 0xFFFFFFFF otherwise. If the return value is zero, the current thread was not suspended. If the return value is one, the thread was suspended, but is now restarted. Any return value greater than one means the thread remains suspended.
//Remarks:Called to resume(恢复) execution of a thread that was suspended(暂停,延缓) by the SuspendThread member function, or a thread created with the CREATE_SUSPENDED flag. The suspend count of the current thread is reduced by one. If the suspend count is reduced to zero, the thread resumes execution; otherwise the thread remains suspended.
4)创建辅助线程事例:
UINT MyThreadProc( LPVOID pParam )
{
    CMyObject* pObject = (CMyObject*)pParam;
    if (pObject == NULL ||
        !pObject->IsKindOf(RUNTIME_CLASS(CMyObject)))
    return 1;   // if pObject is not valid
    // do something with 'pObject'
    return 0;   // thread completed successfully
}
// inside a different function in the program
...
pNewObject = new CMyObject;
AfxBeginThread(MyThreadProc, pNewObject);

3,主线程 和 辅助线程 的通话:(这里的主线程指应用程序,是个用户界面线程)
1)最简单的方法:使用全局变量。
(注意:如书上事例B中在一个辅助线程中使用全局的计数器,不希望其它现在在计数器递增的时候由于其它线程访问而引起混乱,则将起声明成volatile变量保证计数器不被保存到寄存器中,也可以使用InterlockedIncrement来阻塞其它线程同时使计数器递增。)
补充一:
InterlockedIncrement
LONG InterlockedIncrement(
  LPLONG lpAddend   // pointer to the variable to increment
);
//The InterlockedIncrement function both increments (increases by one) the value of the specified 32-bit variable and checks the resulting value. The function prevents more than one thread from using the same variable simultaneously.
补充二:
volatile
使用 volatile 修饰符能够确保一个线程检索由另一线程写入的最新值。
//当字段声明中含有 volatile 修饰符时,该声明引入的字段为易失字段。

由于采用了优化技术(它会重新安排指令的执行顺序),在多线程的程序运行环境下,如果不采取同步控制手段,则对于非易失字段的访问可能会导致意外的和不可预见的结果。这些优化可以由编译器、运行时系统或硬件执行。但是,对于易失字段,优化时的这种重新排序必须遵循以下规则:

读取一个易失字段称为易失读取。易失读取具有“获取语义”;也就是说,按照指令序列,所有排在易失读取之后的对内存的引用,在执行时也一定排在它的后面。
写入一个易失字段称为易失写入。易失写入具有“释放语义”;也就是说,按照指令序列,所有排在易失写入之前的对内存的引用,在执行时也一定排在它的前面。
这些限制能确保所有线程都会观察到由其他任何线程所执行的易失写入(按照原来安排的顺序)。一个遵循本规范的实现并非必须做到:使易失写入的执行顺序,在所有正在执行的线程看来都是一样的。易失字段的类型必须是下列类型中的一种:
引用类型。

类型 byte、sbyte、short、ushort、int、uint、char、float 或 bool。
枚举基类型为 byte、sbyte、short、ushort、int 或 uint 的枚举类型。
(China msdn-C# 语言规范 )

2)不能使用Windows消息(即不能主线程向辅助线程发送消息通信),辅助线程没有窗口没有消息循环。


4,辅助线程 和 主线程(用户界面线程) 通信
1)辅助线程向主线程(用户界面线程)发送Windows消息,由主线程响应该消息,从而实现通信。(主线程有一个窗口,可见或不可见,如果辅助线程可以得到主线程的窗口句柄,便可以向主线程发送Windows消息了。主线程总是有一个消息循环的。)
2)辅助线程可以通过AfxBeginThread函数参数传入主线程句柄从而得到主线程的句柄。
3)辅助线程使用寄出(post)消息。任何用户定义的消息都可以。(使用送出(SEND)消息会引起主线程MFC消息处理代码的重入,这在模式对话框中会出现问题。)


5,EX11B事例说明:
1)CComputeDlg::OnStart函数中,利用AfxBeginThread(ComputeThreadProc, GetSafeHwnd(),THREAD_PRIORITY_NORMAL);函数为用户自定义的全局函数ComputeThreadProc创建辅助线程的同时,利用GetSafeHwnd()获得对话框句柄并做为参数传入
ComputeThreadProc函数形参pParam中。
//GetSafeHwnd() Returns the window handle for a window. Returns NULL if the CWnd is not attached to a window or if it is used with a NULL CWnd pointer.
2)UINT ComputeThreadProc(LPVOID pParam)函数中利用传进来的参数pParam,调用::PostMessage((HWND) pParam, WM_THREADFINISHED, 0, 0)函数向对话框窗口发送消息用户自定义WM_THREADFINISHED消息。
3)在对话框类中为WM_THREADFINISHED添加控制函数。
三步:
消息控制函数声明:CComputeDlg类头文件 LRESULT OnThreadFinished(WPARAM wParam, LPARAM lParam);消息映射:CComputeDlg类代码文件 ON_MESSAGE(WM_THREADFINISHED, OnThreadFinished)
消息控制函数:CComputeDlg类代码文件
LRESULT CComputeDlg::OnThreadFinished(WPARAM wParam, LPARAM lParam)
{
 CDialog::OnOK();
 return 0;
}

6,排斥区(CCriticalSection)
MFC提供了CCriticalSection类来帮助我们实现在线程之间共享全局数据(保证对其临界访问).
使用方法下面代码演示:
CCriticalSection g_cs;//定义g_cs为临界访问对象
int g_nCount;
voit func()
{
 g_cs.Lock();
 g_nCount++;
 g_cs.Unlock();
}
说明:
1)CCriticalSection从CSyncObject类派生而来:
An object of class, CCriticalSection represents a "critical section" - a synchronization object that allows one thread at a time to access a resource or section of code. Critical sections are useful when only one thread at a time can be allowed to modify data or some other controlled resource.
2)构造函数CCriticalSection( )说明:
Constructs a CCriticalSection object. To access or release a CCriticalSection object, create a CSingleLock object and call its Lock and Unlock member functions. If the CCriticalSection object is being used stand-alone, call its Unlock member function to release it.
3)CCriticalSection::Unlock:  Releases the CCriticalSection object.
CCriticalSection::Lock: Use to gain access to the CCriticalSection object.
4)进一步使用说明:
当线程A正在执行func()函数使g_nCount++增1的时候,线程B调用func()函数执行到g_cs.Lock()的时候线程B被阻塞直到线程A执行了g_cs.Unlock()才继续往下执行g_nCount++。
5)CCriticalSection只是用于当个进程内的控制访问,如果要在不同的进程之间控制数据的访问需要使用 互斥体(CMutex) 和 信号(semaphore)。


7,互斥体(CMutex)
1)CMutex:
An object of class CMutex represents a “mutex” — a synchronization object that allows one thread mutually exclusive access to a resource. Mutexes are useful when only one thread at a time can be allowed to modify data or some other controlled resource.
2)CMutex类从CSyncObject类派生而来,其一般用法参见下E文:
To use a CMutex object, construct the CMutex object when it is needed. Specify the name of the mutex you wish to wait on, and that your application should initially own it. You can then access the mutex when the constructor returns. Call CSyncObject::Unlock when you are done accessing the controlled resource.


8,信号(CSemaphore)
1)CSemaphore(也是从CSyncObject类派生而来):
An object of class CSemaphore represents a “semaphore” — a synchronization object that allows a limited number of threads in one or more processes to access a resource. A CSemaphore object maintains a count of the number of threads currently accessing a specified resource.
2)Semaphores are useful in controlling access to a shared resource that can only support a limited number of users.The current count of the CSemaphore object is the number of additional users allowed. When the count reaches zero, all attempts to use the resource controlled by the CSemaphore object will be inserted into a system queue and wait until they either time out or the count rises above 0. The maximum number of users who can access the controlled resource at one time is specified during construction of the CSemaphore object.
构造函数:
CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );

9,关于从CSyncObject类派生类一些说明:
1)CCriticalSection,CMutex,CSemaphore,CEvent,derived from CSyncObject。
2)CSyncObject类成员中包含一下两成员函数:
Lock   Gains access to the synchronization object.
Unlock   Releases access to the synchronization object.


////////////////
笔记后语:多线程编程是个大块的内容。这些书上只略概了些,笔记作用不是很大,详见MSDN和专门指导书更有实践性。日后等俺功力到火候了,一定做些专题性的总结笔记。

VC 技术内幕(第四版)笔记(第10章)

分类:VC学习

第十章:位图

1,Windows的位图实际上是一些和显示象素相对应的位阵列。

2,GDI位图是设备相关位图,用MS基本类库(MFC)中的CBitmap类表示的,依赖具体的设备。

3,DIB社设备无关位图,比GDI位图有许多编程优势。任何运行Windows的机器都可以处理DIB位图。DIB位图通常以.BMP文件形式保留在磁盘中,或作为资源保存在程序的EXE或DLL文件中。WIN32API只直接支持DIB格式文件。

4,Windows在位图中使用模糊颜色。GDI位图也是GDI对象,使用时先创建(并初始化),再把它选入设备环境,使用完后还要删除掉。
注意:
使用GDI位图的时候,不能直接把位图选进显示设备环境或打印机设备环境中。必须利用CDC::CreateCompatibleDC函数为位图创建一个特殊的内存设备环境,然后使用CDC::StretchBlt或CDC::BitBlt函数把内存设备环境中的各个位复制到真正的设备环境中。
说明:
1)CDC::CreateCompatibleDC 
virtual BOOL CreateCompatibleDC( CDC* pDC );
// Creates a memory device context that is compatible with the device specified by pDC. A memory device context is a block of memory that represents a display surface. It can be used to prepare images in memory before copying them to the actual device surface of the compatible device.
2)CDC::BitBlt 
BOOL BitBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop );
//Copies a bitmap from the source device context to this current device context.
3)CDC::StretchBlt
BOOL StretchBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, int nSrcWidth, int nSrcHeight, DWORD dwRop );
//Moves a bitmap from a source rectangle and device into a destination rectangle, stretching or compressing the bitmap if necessary to fit the dimensions of the destination rectangle.
4)应用事例:
void CGDIView::OnDraw(CDC* pDC)
{
 CBitmap bitmap;
 bitmap.LoadBitmap(IDB_BITMAP1);
 CDC *pdcm=new CDC;
 pdcm->CreateCompatibleDC(pDC);
 pdcm->SelectObject(&bitmap);
 pDC->BitBlt(100,100,54,96,pdcm,0,0,SRCCOPY);
 delete pdcm;
 //pDC->StretchBlt(100,100,500,50,pdcm,0,0,50,50,SRCCOPY);
}
注:事例中显示映射模式为MM_TEXT,MM_TEXT下每个位图象素都映射到一个显示器象素。如果在其它的模式下需要进行一些伸缩变换处理。处理缩小的位图时候可以调用CDC::SetStretchBltMode函数(参数可选COLORONCOLOR)使缩小的位图显示更好些。
在非MM_TEXT模式下,使用BitBlt或StretchBlt函数,如果GDI要对其进行伸缩变换,则更新的速度较慢.可以使用以下代码代替BitBlt语句可加快其更新速度:
pDC->SetMapMode(MM_LOENGLISH);//设置映射模式为MM_LOENGLISH。
CSize size(54,96);
pDC->DPtoLP(&size);
pDC->StretchBlt(0,0,size.cx,-size.cy,dcm,0,0,54,96,SRCCOPY);//MM_LOENGLISH模式下注意-size.cy前的负号不能少。


5,事例EX10A中函数说明:
1)CGdiObject::GetObject
int GetObject( int nCount, LPVOID lpObject ) const;
参数说明:
nCount:Specifies the number of bytes to copy into the lpObject buffer.
lpObject:Points to a user-supplied buffer that is to receive the information.
//Return Value:The number of bytes retrieved; otherwise 0 if an error occurs.
//Fills a buffer with data that describes the Windows GDI object attached to the CGdiObject object.
//
Object  Buffer type
CPen   LOGPEN
CBrush   LOGBRUSH
CFont   LOGFONT
CBitmap  BITMAP
CPalette  WORD
CRgn   Not supported
//If the object is a CBitmap object, GetObject returns only the width, height, and color format information of the bitmap. The actual bits can be retrieved by using CBitmap::GetBitmapBits.

2)CGdiObject::GetSafeHandle
HGDIOBJ GetSafeHandle( ) const;
//Return Value:A HANDLE to the attached Windows GDI object; otherwise NULL if no object is attached.


6,事例EX10B说明:位图有程序自己生成,并且能使位图在屏幕上平滑移动。原理:首先将所选择的位图绘制在内存设备环境中,然后再将它反复快速地在屏幕上进行显示。

7,DIB编程(出于个人需要,这里做概要笔记,详见P190-204)
1)在MFC库中有GDI位图的类CBitmap,但没有DIB的类。
2)DIB包含一个二维数组,数组的元素为象素。
3)DIB是标准的Windows位图格式,且BMP文件包含一个DIB结构。
4)DIB访问函数:
SetDIBitsToDevice//直接在显示器或打印机上显示DIB,不进行缩放,位图每一位对应一显示象素或一打印点。
StretchDIBits//仿照StretchBlt类似的方式将DIB显示在显示器或打印机上。
GetDIBits//函数利用申请到的内存,由GDI位图来构造DIB。
CreateDIBitmap//由DIB来创建GDI位图。
CreateDIBSection//创建一种称为DIB项的DIB,然后返回一个GDI句柄。提供了DIB和GDI位图最好的特性。可以直接访问DIB的内存,还可以在DIB中调用GDI函数画图。
5)LoadImage//The LoadImage function loads an icon, cursor, or bitmap.
LoadImage可以直接从一个磁盘文件中读入位图
6)DrawDibDraw//The DrawDibDraw function draws a DIB to the screen.
DrawDibDraw需要MM_TEXT坐标和MM_TEXT映射模式。


8,在按钮上设置位图
1)先设置按钮的Owner Draw属性,然后在对话框类中写一个消息控制函数,在该按钮控制的窗口里画图。
2)使用MFC的CBitmapButton类实现在按钮在设置位图。步骤如下:
第一步:在对话框中添加按钮,并设置Owner Draw属性,大小并不重要。(框架自动调整为位图大小)
第二步:添加位图资源(.bmp),并用相应的按钮Caption+D/U来标识位图。即在位图的属性ID栏中输入"按钮Caption+U"或"按钮Caption+D",其中名字两边的引号不能少,这样就表示了用名字标识资源而不是ID,U表示不翻转显示的位图,D表示翻转显示的位图。如:按钮Caption为[Button1],则在该按钮上贴位图的名字应为["Button1D"]或["Button1U"]。
第三步:为要设置位图的按钮,对应在按钮所在对话框类中添加CBitmapButton类型的数据成员。
第四步:映射按钮所在对话框类WM_INITDIALOG消息,在OnInitDialog函数中用刚添加的CBitmapButton类型的数据成员调用
AutoLoad函数把每个按钮和两个匹配的位图资源连接起来。如:VERIFY(CWnd::EnableWindow.AutoLoad(IDC_BUTTON1,this));
说明一:
CBitmapButton::AutoLoad
BOOL AutoLoad( UINT nID, CWnd* pParent );
//nID:The button’s control ID.
//pParent:Pointer to the object that owns the button.
//AutoLoad Associates a button in a dialog box with an object of the CBitmapButton class, loads the bitmap(s) by name, and sizes the button to fit the bitmap.(注意哦,这里的loads the bitmap by name。)
//Use the AutoLoad function to initialize an owner-draw button in a dialog box as a bitmap button.
说明二:
VERIFY
VERIFY( booleanExpression )
//VERIFY是MFC诊断宏,当参数booleanExpression 为 0 的时候,停止程序并显示一个对话框;当booleanExpression 不为零,则什么也不做。
说明三:
在为按钮设置的位图加名字标识的时候:
"按钮Caption+U"  表示按钮凸起状态的位图
"按钮Caption+D"  表示按钮凹起状态的位图
"按钮Caption+F"  表示按钮有焦点状态的位图
"按钮Caption+X"  表示按钮无效状态的位图

9,CWnd::EnableWindow函数可以是一个窗口有效或无效,可以调用它来使按钮等控件(窗口)无效或恢复有效。
CWnd::EnableWindow 
BOOL EnableWindow( BOOL bEnable = TRUE );
//If this parameter is TRUE, the window will be enabled. If this parameter is FALSE, the window will be disabled.
//EnableWindow Enables or disables mouse and keyboard input. When input is disabled, input such as mouse clicks and keystrokes is ignored. When input is enabled, the window processes all input.
//An application can use this function to enable or disable a control in a dialog box. A disabled control cannot receive the input focus, nor can a user access it.

VC 技术内幕(第四版)笔记(第9章)

分类:VC学习

第九章:Win32内存管理


1,一个程序就是一个EXE文件。Windows中,一旦一个程序被启动的,系统为其创建一个进程。一个进程拥有自己的内存,文件句柄,和其它的系统资源。如果连续两次启动同一个程序,系统为其创建两个独立的进程并发运行(并发:微观交替串行,宏观并行)。
说明:
1)一个进程(如Windows Wxplorer)可能有多个主窗口(每一个窗口由一个线程支持),也可能没有任何窗口。
2)进程拥有自己“私有”4GB虚拟地址空间。
3)每个进程内存空间包含各种各样的内容(具体参见169页): 程序EXE镜像,程序中装入的非系统的DLL(包括MFC DLL),程序的全局数据,程序的堆栈,动态分配内存(包括Windows和C运行库堆),内存映射文件,进程之间共享的内存块,特定执行线程的局部内存,所有系统内存模块(包括虚拟内存表),Windows核心、执行过程及DLL(是OS的一部分)。


2,Windows 95进程地址空间:
WIN95中,只有底下的2GB(0-0x7FFFFFFF)地址空间是进程私有的。其中最底下的4MB禁止访问。堆栈、堆和可读写的全局内存,及其应用程序的EXE和DLL文件都被映射底下的2GB地址上。
上面的2GB地址空间对所有的进程都一样的,由所有的进程共享。
WIN95核心、执行过程、虚拟设备驱动程序和文件系统代码,以及一些重要的表(如:页表),都被映射到最上面的1GB(0xC0000000-0xFFFFFFFF)地址空间。
Windows DLL和内存映射文件位于0x80000000-0xBFFFFFFF地址空间。
安全性说明:
1)所有的EXE和DLL代码都有只读标记,因而可以映射到几个不同的进程中。
2)一个进程不可能改写另一个进程私有的2GB地址空间内容。
3)进程地址空间中最上面的2GB由于是共享的,很容易受攻击毁坏。如:错误进程毁坏这一区域重要的系统表,进程的内存映射文件被其它进程弄乱。

3,Windows NT进程地址空间:
WIN NT中,进程只能访问它最底下的2GB地址空间(除最底和最高的64K是不能访问)即:0x00010000-0x7FFEFFFF。
EXE、应用程序的DLL 和 Windows的DLL、以及内存映射文件都驻留在0x00010000-0x7FFEFFFF地址空间中。
Windows NT内核、可执行程序和设备驱动程序都被映射到上面的2GB空间中,完全受保护,可以避免错误程序的侵入。
安全性分析:
1)进程内存地址空间上2GB空间,完全受保护,可以避免错误程序的侵入破坏其中内容。
2)内存映射文件更安全,如果不知道文件的名字,而又没有显式地映射视图,一个进程不能访问另一个进程的内存映射文件。

4,实际中RAM一般没有GB级别,Windows是使用虚拟镜像技术的(通过内存与磁盘的页置换完成的)。

5,程序中如果需要动态内存,可使用Win32函数VirtualAlloc和VirtualFree函数,也可以直接使用Windows 堆函数(如:HeapAlloc 和 HeapFree)和CRT函数(C运行库堆函数,如malloc,new,free,delete)来完成任务。

6,堆是特定进程的内存池。当程序需要内存块的时候,调用堆内存分配函数,并在使用完时调用相关的堆内存释放函数释放内存。
Windows 堆函数(如:HeapAlloc 和 HeapFree)和CRT函数(C运行库堆函数,如malloc,new,free,delete).
C++代码中,new,delete函数直接映射到malloc,free函数。
说明:
new,delete函数与malloc,free函数比较优点:new,delete函数进行类型检查,自动计算要分配类型的大小,而且属于C++语言的一部分。malloc,free函数是作为标准库函数提供给C的,它们并不是C语言的一部分。

7, 内存映射文件:
处理如在程序中读如DIB(设备无关位图文件)的问题时候:文件小一般是分配一个大小合适的缓冲区,打开文件,然后把整个文件读入缓冲区;如果文件大则可使内存映射文件来处理。
内存映射文件是:直接映射一个地址范围到相应的文件(注:个人理解 文件仍然存放在存放在磁盘上),当进程访问该范围内存页时候,OS分配RAM并从磁盘中读入该页数据。
说明:
1)默认情况下,当映射文件时候,虽然可能只映射文件的一部分,但整个文件仍然被占用。
2)MFC里不支持内存映射文件。CSshareFile类只支持HGLOBAL句柄进行剪切板内存传输,不大实用。

8,资源包含在EXE和DLL里,因此会占用虚拟地址空间,而且这些空间在进程的生存期内不会被改变。这就使我们很容易直接读取一个资源(如用LoadResource函数加载位图资源)
说明:
LoadResource : loads the specified resource into global memory.
HGLOBAL LoadResource(
  HMODULE hModule, // resource-module handle
  HRSRC hResInfo   // resource handle
);
LoadResource返回一个HGLOBAL值,但可以安全把它当成指针来使用(强制转换)。

9,堆使用的越多,程序的运行效率就越底下。WIN32程序的堆可以很大(要多大就有多大,没有64KB限制,WIN16有16K的限制)。
                                                                                                                              
////////////////
//////////////
说明:由于缺乏这方面的编程经验,书中翻译的这部分内容有比较拗口难懂,笔记里删漏好多的内容,建议看看原著。

VC 技术内幕(第四版)笔记(第8章)

分类:VC学习

第八章:使用ActiveX控件

 

1,ActiveX控件是一个直接插入到C++程序中的软件模块,以前常称OLE控件(OCX),是基于MS-COM技术。

2,ActiveX控件与普通Windows控件比较:
相同点:ActiveX控件也可看成是一个子窗口(可以看成这样的)。
如果想在对话框上加入ActiveX控件,则只要在对话框编辑器中,把ActiveX控件放在适当的位置上,并在资源模板中标识该控件。如果要在运行的过程中建立ActiveX控件,则可以调用响应控件类的Create成员函数,而且通常在父窗口的WM_CREATE消息控制函数中调用。
不同点:属性和方法。
ActiveX控件不像普通控件那样发送以WM_打头的通知消息给它的包容器窗口,而是激发事件。事件实际上是由控件调用包容器函数。像普通的控件通知消息一样,事件并没有返回值传给ActiveX控件。事件如lick,KeyDown。但对于客户来说时间与控件的通知消息是一样的。

3,在MFC库中,ActiveX控件就像子窗口一样,但在控件窗口和包容器窗口之间有一层重要代码。实际上,ActiveX控件可能没有窗口。当调用Create函数时,并不是直接建立控件窗口,而是把控件代码载入进来,并激发一个“实地激活”(in-place activation)命令。然后ActiveX控件再建立它自己的窗口,通过MFC的CWnd类指针我们可以访问该窗口。不过客户程序最好不要使用ActiveX控件的hWnd句柄。

4,通常ActiveX控件会保存在扩展名为OCX的动态连接库中。包容器程序回根据Windows注册表利用COM技术在需要的时候装入动态连接库。
说明:
1)暂时可以这样认为,如果使用了ActiveX控件,那么在运行时候要装入该ActiveX控件代码。显然在发布含有ActiveX控件的程序时候,必须要包含相应的OCX文件,而且还得提供一个合适的安装程序。

5,安装ActiveX控件:
1)把找到的ActiveX控件动态连接库拷到硬盘上。
2)在WINDOWS注册表中登记注册。(可使用Regsvr32命令行命令)
3)在使用该控件的项目中安装该控件。(选择Project菜单,再选择Add To Project,再选择Components And Controls,再选择Registered ActiveX Controls,这时列表框列出系统已经注册所有的ActiveX控件,选择需要的控件INSERT即可。)

6,ActiveX控件包容器编程:
1)不管ActiveX控件是作为对话框控件,还是做为“子窗口”,MFC和ClassWizard都支持。
2)ActiveX控件编写者设计了ActiveX控件属性供使用者在设计时访问。所有的ActiveX控件属性(包括设计时属性),在运行时都是可以访问的,不过有些属性可能被设计成只读的。
3)当在项目中插入ActiveX控件时,ClassWizard就会产生相应的CWnd的派生类C++类,来满足对空间的方法和属性进行访问要求。控件的属性和方法都有相应的成员函数,同时生成的类还有一个构造函数可用以动态创建ActiveX控件的事例。
4)当在项目中插入ActiveX控件ClassWizard生成的CWnd的派生类C++类中,可以看到其成员函数的代码中都有对InvokeHelper函数的调用,InvokeHelper函数的第一个参数都和对应的属性或方法在ActiveX控件中的分发(dispatch)ID(标识ActiveX控件的方法或属性的)相对应。通过查看ActiveX控件hlp文件可以发现,ActiveX控件的方法在生存的C++类中都有同名的成员函数与之对应,ActiveX控件的属性都有一组Get和Set函数对其操作,其中ActiveX控件的方法和属性操作与生成的C++类成员函数相关联都是通过InvokeHelper函数的调用来完成的,InvokeHelper函数的第一个参数是由Component Gallery(控件提供者)提供的。因为经过这样的处理,所以我们如果要调用ActiveX控件的方法或对其属性进行取和设置操作,只需调用生成的C++类对应的成员函数便可。
下面对InvokeHelper单独说明:
CWnd::InvokeHelper
void InvokeHelper( DISPID dwDispID, WORD wFlags, VARTYPE vtRet, void* pvRet, const BYTE* pbParamInfo, ... );
说明:
Call this member function to invoke the OLE control method or property specified by dwDispID, in the context specified by wFlags.
其中参数:
dwDispID:
//Identifies the method or property to be invoked. This value is usually supplied by Component Gallery.

wFlags:可以为下面些值,指明调用InvokeHelper的目的。
//[ DISPATCH_METHOD ]   The member is invoked as a method. If a property has the same name, both this and the DISPATCH_PROPERTYGET flag may be set.
[ DISPATCH_PROPERTYGET ] The member is retrieved as a property or data member.
[ DISPATCH_PROPERTYPUT ] The member is changed as a property or data member.
[ DISPATCH_PROPERTYPUTREF ] The member is changed by a reference assignment, rather than a value assignment. This flag is valid only when the property accepts a reference to an object.

vtRet:
//Specifies the type of the return value.
VT_EMPTY  void
VT_I2  short
VT_I4  long
VT_R4  float
VT_R8  double
VT_CY  CY
VT_DATE  DATE
VT_BSTR  BSTR
VT_DISPATCH  LPDISPATCH
VT_ERROR  SCODE
VT_BOOL  BOOL
VT_VARIANT VARIANT
VT_UNKNOWN  LPUNKNOWN

pvRet:
//Address of the variable that will that will receive the property value or return value. It must match the type specified by vtRet.

pbParamInfo:一般都设置为NULL
//Pointer to a null-terminated string of bytes specifying the types of the parameters following pbParamInfo.
specifies the types of the parameters passed to the method or property.
...:
//Variable List of parameters, of types specified in pbParamInfo.

5)AppWizard对ActiveX控件的支持是通过在生成的应用程序类的成员函数InitInstance中插入(AfxEnableControlContainer();),同时在响应项目文件的StdAfx.h文件中插入(#include<afxdisp.h>)(原因可参考书P38一些说明)。
如果项目中不包含这两行,而又要加入ActiveX控件,则只要手工加入上面两行代码即可。

6)可以对话框编辑器来生成对话框的模板中加入一个或多个ActiveX控件,这样我们可以在对话框模板生成的类中添加数据成员或事件控制函数来获取ActiveX控件的属性或对其控制。
注意:(详细见书P159页的[致WIN32程序员])
实际上,资源模板并不是在对话框编辑器中所看的那样。函数CDialog::DoModal在把对话框模板交给WINDOWS内部的对话框过程之前,要先对模板进行预处理,即:它先会去掉所有的ActiveX控件,有剩下的控件建立对话框窗口,然后再装入ActiveX控件,激活它们并在正确的位置上创建它的窗口。
当模式对话框运行时候,MFC处理所有送给对话框消息时,是不管是有普通控件发送的,还是ActiveX控件发送的。故ActiveX控件虽然不是对话框模板一部分,但用户仍然可以用TAB键在所有的控件间切换。

7)调用UpdateData(FALSE)将会从所有的对话框控件中读取所有属性值。如果只需要得到ActiveX控件属性的话,可以调用ActiveX控件生成的C++类中Get函数(同样设置调用Set函数),这样就提高了效率。

8)事例部分代码对比说明:
代码一:
   CDataExchange dx(this,TRUE);
   DDX_Text(&dx,IDC_DAY,m_sDay);
   DDX_Text(&dx,IDC_MONTH,m_sMonth);
   DDX_Text(&dx,IDC_YEAR,m_sYear);
说明一:
CDataExchange类构造函数:(注,在MFC|SRC|AFXWIN.H中可以看到其构造函数声明,在MFC|SRC|WINCORE.CPP文件中可以看到其构造函数的定义。)
原型:CDataExchange::CDataExchange(CWnd* pDlgWnd, BOOL bSaveAndValidate);
定义:
CDataExchange::CDataExchange(CWnd* pDlgWnd, BOOL bSaveAndValidate)
{
 ASSERT_VALID(pDlgWnd);
 m_bSaveAndValidate = bSaveAndValidate;
 m_pDlgWnd = pDlgWnd;
 m_hWndLastControl = NULL;
}

//其中m_pDlgWnd和m_bSaveAndValidate是CDataExchange数据成员,可以通过这中方式给它们赋值。
m_pDlgWnd:The dialog box or window where the data exchange takes place.
m_bSaveAndValidate Flag for the direction of DDX and DDV. 详见说明二。

说明二:
//CDataExchange does not have a base class.
//The CDataExchange class supports the dialog data exchange (DDX) and dialog data validation (DDV) routines used by the Microsoft Foundation classes. Use this class if you are writing data exchange routines for custom data types or controls, or if you are writing your own data validation routines.
//A CDataExchange object provides the context information needed for DDX and DDV to take place. The flag m_bSaveAndValidate is FALSE when DDX is used to fill the initial values of dialog controls from data members. The flag m_bSaveAndValidate is TRUE when DDX is used to set the current values of dialog controls into data members and when DDV is used to validate the data values. If the DDV validation fails, the DDV procedure will display a message box explaining the input error. The DDV procedure will then call Fail to reset the focus to the offending control and throw an exception to stop the validation process.


代码二:(是为了比较代码一做些说明的)
void CActiveXDialog::DoDataExchange(CDataExchange* pDX)
{
 CDialog::DoDataExchange(pDX);//调用基类的DoDataExchange
 //{{AFX_DATA_MAP(CActiveXDialog)
 DDX_Control(pDX, IDC_CALENDAR1, m_calendar);
 DDX_Text(pDX, IDC_DAY, m_sDay);
 DDX_Text(pDX, IDC_MONTH, m_sMonth);
 DDX_Text(pDX, IDC_YEAR, m_sYear);
 //}}AFX_DATA_MAP
}
说明一:
//DoDataExchange Called by the framework to exchange and validate dialog data.
//DoDataExchange Never call this function directly. It is called by the UpdateData member function. Call UpdateData to initialize a dialog box’s controls or retrieve data from a dialog box.
说明二:
//在MFC|SRC|WINCORE.CPP文件中可以看到UpdateData函数的定义
BOOL CWnd::UpdateData(BOOL bSaveAndValidate)
{ ... 
 CDataExchange dx(this, bSaveAndValidate);//创建了一个CDataExchange对象,与当前窗口相关联
 ...
 DoDataExchange(&dx); //注意:DoDataExchange是个虚函数。子类中如果有重写了,则调用子类的。
 ...
}
说明三:
//在MFC|Include|AFXWIN2.INL文件中可有看到CWnd::DoDataExchange的如下定义(内联):
// CWnd dialog data support
_AFXWIN_INLINE void CWnd::DoDataExchange(CDataExchange*)
 { } // default does nothing
由此可见代码二中CDialog::DoDataExchange(pDX)调用好象是个‘摆设’,不起做任何事情。框架设置DoDataExchange函数目的是在我们子窗口类(这里是对话筐)中重写它,添加代码完成子窗口类(这里是对话筐)中数据成员与对话筐上控件的交互。
说明四:
如果结合UpdateData和DoDataExchange两函数整体来看,应该体会到这里代码一与代码二实质上是一会事情。代码二只是借助了框架兜了些圈子。

说明四:
摘录MSDN中Dialog Data Exchange一些E文段落对上讨论做个总结:
Dialog Data Exchange

If you use the DDX mechanism, you set the initial values of the dialog object’s member variables, typically in your OnInitDialog handler or the dialog constructor. Immediately before the dialog is displayed, the framework’s DDX mechanism transfers the values of the member variables to the controls in the dialog box, where they appear when the dialog box itself appears in response to DoModal or Create. The default implementation of OnInitDialog in CDialog calls the UpdateData member function of class CWnd to initialize the controls in the dialog box.

The same mechanism transfers values from the controls to the member variables when the user clicks the OK button (or whenever you call the UpdateData member function with the argument TRUE). The dialog data validation mechanism validates any data items for which you specified validation rules.

UpdateData works in both directions, as specified by the BOOL parameter passed to it. To carry out the exchange, UpdateData sets up a CDataExchange object and calls your dialog class’s override of CDialog’s DoDataExchange member function. DoDataExchange takes an argument of type CDataExchange. The CDataExchange object passed to UpdateData represents the context of the exchange, defining such information as the direction of the exchange.

When you (or ClassWizard) override DoDataExchange, you specify a call to one DDX function per data member (control). Each DDX function knows how to exchange data in both directions based on the context supplied by the CDataExchange argument passed to your DoDataExchange by UpdateData.

MFC provides many DDX functions for different kinds of exchange.

If the user cancels a modal dialog box, the OnCancel member function terminates the dialog box and DoModal returns the value IDCANCEL. In that case, no data is exchanged between the dialog box and the dialog object.

9)当输入焦点在某ActiveX控件上时,按下F1键引起OnHelpInfo函数调用,可在OnHelpInfo函数中设置帮助信息。
说明:
ClassWizard不能修改生成的ActiveX控件类,因而必须手工加入消息映射代码。事例代码如下:
//在ActiveX控件类头文件中加入函数原型并声明消息映射表:
 protected:
  afx_msg BOOL OnHelpInfo(HELPINFO* pHelpInfo);
  DECLARE_MESSAGE_MAP()//在ActiveX控件类代码文件中添加消息映射及OnHelpInfo函数定义:
 BEGIN_MESSAGE_MAP(CCalendar,CWnd)
  ON_WM_HELPINFO()
 END_MESSAGE_MAP()
/**
 BOOL CCalendar::OnHelpInfo(HELPINFO *pHelpInfo)
{
 ::WinHelp(GetSafeHwnd(),"C:WINDOWSsystem32MSCAL.hlp",
    HELP_FINDER,0);
 return FALSE;
}

/////////////////
//////////////
///////////

7,在运行时创建ActiveX控件:
1)在项目中插入ActiveX控件。ClassWizard会生存相应的ActiveX控件类的文件。
2)在使用ActiveX控件的对话框或窗口类中添加ActiveX控件类数据成员。
3)重写CDialog::OnInitDialog(或其它窗口中响应WM_CREAT消息),在新的函数中调用ActiveX控件类Create函数。
4)在父窗口类中,手工添加必要的与新控件有关的事件消息处理函数及原型,和相应的消息映射。

/////////
8,更多的ActiveX控件编程参见P160-167页(ActiveX控件在HTML文件中使用 和 在运行时创建ActiveX控件)。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:56452次
    • 积分:1061
    • 等级:
    • 排名:千里之外
    • 原创:50篇
    • 转载:3篇
    • 译文:0篇
    • 评论:8条
    文章分类
    最新评论