C++之MFC学习

问题1:stdafx.h是怎么引入进来的?define.h与stdafx.h之间的关系?为什么在MuisicPlayer.cpp中引入stdafx.h

问题2:enum class的使用

问题3:列表初始化:int window_transparency{ 100 }

问题4:CDC的使用

CDC类定义的是设备上下文对象的类。 CDC对象提供处理显示器或打印机等设备上下文的成员函数,以及处理与窗口客户区对应的显示上下文的成员。 通过CDC对象的成员函数进行所有的绘图。 类对设备上下文操作提供了成员函数,处理绘图工具。安全型图形设备接口(GDI)对象收集,以及处理颜色和调色板。 它还为获取和设置绘图属性、映射,处理视点、窗口扩展、转换坐标,处理区域、剪贴、绘制直线及绘制简单椭圆和多边形等形状提供了成员函数。 另外还为绘制文本、处理字体,使用打印机跳转,滚动和播放元文件提供成员函数。 使用CDC对象时要构造它,然后调用与它平等的、使用设备上下文的Windows函数的成员函数。

问题5:BitMap、HBitMap、CBitMap?

C++ CBitmap,HBitmap,Bitmap区别及联系 - 鹿我所录 - 博客园

问题6:Microsoft SDKs和Windows Kits有什么区别?

Microsoft SDKs包含了Windows Kits、Azure SDK、Kinect SDK,等。

问题7:wchar_t类型

wchar_t是C/C++的字符类型,是一种扩展的存储方式。wchar_t类型主要用在国际化程序的实现中,但它不等同于unicode编码。unicode编码的字符一般以wchar_t类型存储。

char是8位字符类型,最多只能包含256种字符,许多外文字符集所含的字符数目超过256个,char型无法表示。

wchar_t数据类型一般为16位或32位,但不同的C或C++库有不同的规定,如GNU Libc规定wchar_t为32位,总之,wchar_t所能表示的字符数远超char型。

标准C中的wprintf函数以及标准C++的iostream类库中的类和对象能提供wchar_t宽字符类型的相关操作。

问题8:CString是什么

CString是MFC中最常见的类之一,用于封装字符串数据结构。

CString没有基类。 一个CString对象由可变长度的一队字符组成。CString使用类似于Basic的语法提供函数和操作符。连接和比较操作符以及简化的内存管理使CString对象比普通字符串数组容易使用。 CString是基于TCHAR数据类型的对象。如果在你的程序中定义了符号UNICODE,则TCHAR被定义为类型wchar_t,即16位字符类型;否则,TCHAR被定义为char,即8位字符类型。在UNICODE方式下,CString对象由16位字符组成。非UNICODE方式下,CString对象由8位字符组成。 当不使用UNICODE时,CString是多字节字符集(MBCS,也被认为是双字节字符集,DBCS)。注意,对于MBCS字符串,CString仍然基于8位字符来计算,返回,以及处理字符串,并且你的应用程序必须自己解释MBCS的开始和结束字节。 CString对象还具有下列特征:

· CString可作为连接操作的结果而增大。
· CString对象遵循“值语义”。应将CString看作是一个真实的字符串而不是指向字符串的指针。
· 你可以使用CString对象任意替换const char*和LPCTSTR函数参数。
· 转换操作符使得直接访问该字符串的字符就像访问一个只读字符(C-风格的字符)数组一样。

问题9:MFC中wstring是什么?

看你要使用什么字符编码了, std::wstring主要用于 UTF-16编码的字符,而std::string主要用于存储单字节的字符( ASCII字符集 ),但是也可以用来保存UTF-8编码的字符。(UTF-8和UTF-16是UNICODE字符集的两种不同的字符编码)

如果你的程序支持多种语言,那么使用UTF-16来处理字符会方便一些,因为该编码中的每个字符都占用2个字节;而UTF-8中的字符所占的字节可能是1个字节或者多个字节(范围是1 ~ 6 个字节),多字节的字符编码对于处理字符不方便,而且std::string也没有提供对UTF-8的支持。

问题10:CImage类

CImage是MFC和ATL共享的新类,它能从外部磁盘中调入一个JPEG、GIF、BMP和PNG格式的图像文件加以显示,而且这些文件格式可以相互转换。

CImage是VC.NET中定义的一种MFC/ATL共享类,也是ATL的一种工具类,它提供增强型的(DDB和DIB)位图支持,可以装入、显示、转换和保存多种格式的图像文件,包括BMP、GIF、JPG、PNG、TIF等。CImage是一个独立的类,没有基类。(CImage类是基于GDI+的,从VC.NET起引进,VC 6.0中没有。)

ATL(Active Template Library,活动模板库)是一套基于模板的 C++ 类,用以简化小而快的 COM 对象的编写。

为了在MFC程序中使用CImage类,必须包含ATL的图像头文件atlimage.h:(在VS08 SP1中不用包含)

#include

问题11:CCriticalSection

类CCriticalSection的对象表示一个“临界区”,它是一个用于同步的对象,同一时刻只允许一个线程存取资源或代码区。临界区在控制一次只有一个线程修改数据或其它的控制资源时非常有用。例如,在链表中增加一个结点就只允许一次一个线程进行。通过使用CCriticalSection对象来控制链表,就可以达到这个目的。 在运行性能比较重要而且资源不会跨进程使用时,建议采用临界区代替信号灯。有关在MFC中使用信号灯的详细信息,请参阅CMutex。使用CCriticalSection对象之前,需要构造它。在构造函数返回后,就可以使用临界区了。在使用完之后要调用UnLock函数。 存取由CCriticalSection控制的资源时,要在资源的存取函数中定义一个CSingleLock型的变量。然后调用加锁对象的Lock成员函数(如CSingleLock::Lock)。此时,调用的线程要么获得对资源的存取权,要么等待他人释放资源等待加锁,或者等待他人释放资源,但又因为超时而加锁失败。这样就保证了一次只有一个线程在存取临界资源。释放资源只需调用成员函数UnLock(例如CSingleLock:Unlock),或让锁对象在作用范围之外。 此外,可以单独地建立一个CCriticalSection对象,并在存取临界资源之前显式地存取它。这种方式有助于保持代码的清晰,但是更容易出错,因为程序员要记住在存取临界资源前加锁,存取之后开锁。 要了解有关使用CCriticalSection对象的更详细的信息,请参阅联机文档“Visual C++程序员指南”中的“多线程:如何使用同步类”。 #include

问题12:static_cast/dynamic_cast/ const_cast/reinterpret_cast

static_casts

static_cast相当于传统的C语言里的强制转换,该运算符把expression转换为new_type类型,用来强迫隐式转换,例如non-const对象转为const对象,编译时检查,用于非多态的转换,可以转换指针及其他,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:

①用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。

进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;

进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。

②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。

③把空指针转换成目标类型的空指针。

④把任何类型的表达式转换成void类型。

注意:static_cast不能转换掉expression的const、volatile、或者__unaligned属性。

dynamic_cast

dynamic_cast(e)
dynamic_cast(e)
dynamic_cast(e)

  type必须是一个类类型,在第一种形式中,type必须是一个有效的指针,在第二种形式中,type必须是一个左值,在第三种形式中,type必须是一个右值。在上面所有形式中,e的类型必须符合以下三个条件中的任何一个:e的类型是是目标类型type的公有派生类、e的类型是目标type的共有基类或者e的类型就是目标type的的类型。如果一条dynamic_cast语句的转换目标是指针类型并且失败了,则结果为0。如果转换目标是引用类型并且失败了,则dynamic_cast运算符将抛出一个std::bad_cast异常(该异常定义在typeinfo标准库头文件中)。e也可以是一个空指针,结果是所需类型的空指针。

dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换(cross cast)。

在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;

在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。dynamic_cast是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作。

(1)指针类型

举例,Base为包含至少一个虚函数的基类,Derived是Base的共有派生类,如果有一个指向Base的指针bp,我们可以在运行时将它转换成指向Derived的指针,代码如下:

if(Derived *dp = dynamic_cast(bp)){
  //使用dp指向的Derived对象  
}
else{
  //使用bp指向的Base对象  
}

值得注意的是,在上述代码中,if语句中定义了dp,这样做的好处是可以在一个操作中同时完成类型转换和条件检查两项任务。

(2)引用类型

因为不存在所谓空引用,所以引用类型的dynamic_cast转换与指针类型不同,在引用转换失败时,会抛出std::bad_cast异常,该异常定义在头文件typeinfo中。

void f(const Base &b){
 try{
   const Derived &d = dynamic_cast(b);  
   //使用b引用的Derived对象
 }
 catch(std::bad_cast){
   //处理类型转换失败的情况
 }
}

const_cast

const_cast,用于修改类型的const或volatile属性。

该运算符用来修改类型的const(唯一有此能力的C++-style转型操作符)或volatile属性。除了const 或volatile修饰之外, new_type和expression的类型是一样的。

①常量指针被转化成非常量的指针,并且仍然指向原来的对象;

②常量引用被转换成非常量的引用,并且仍然指向原来的对象;

③const_cast一般用于修改底指针。如const char *p形式。

举例转换如下:

const int g = 20;
int *h = const_cast(&g);//去掉const常量const属性
​
const int g = 20;
int &h = const_cast(g);//去掉const引用const属性
​
 const char *g = "hello";
char *h = const_cast(g);//去掉const指针const属性

reinterpret_cast

new_type必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值)。

reinterpret_cast意图执行低级转型,实际动作(及结果)可能取决于编辑器,这也就表示它不可移植

  举一个错误使用reintepret_cast例子,将整数类型转换成函数指针后,vc++在执行过程中会报"...中的 0xxxxxxxxx 处有未经处理的异常: 0xC0000005: Access violation"错误:

#include 
using namespace std;
int output(int p){
    cout << p <(&p);
    fun2(p);//...处有未经处理的异常: 0xC0000005: Access violation
    return 0;
}

IBM的C++指南、C++之父Bjarne Stroustrup的FAQ网页和MSDN的Visual C++也都指出:错误的使用reinterpret_cast很容易导致程序的不安全,只有将转换后的类型值转换回到其原始类型,这样才是正确使用reinterpret_cast方式。

  MSDN中也提到了,实际中可将reinterpret_cast应用到哈希函数中,如下(64位系统中需将unsigned int修改为unsigned long):

// expre_reinterpret_cast_Operator.cpp
// compile with: /EHsc
#include 
​
// Returns a hash code based on an address
unsigned short Hash( void *p ) {
   unsigned int val = reinterpret_cast( p );
   return ( unsigned short )( val ^ (val >> 16));
}
​
using namespace std;
int main() {
   int a[20];
   for ( int i = 0; i < 20; i++ )
      cout << Hash( a + i ) << endl;
}

另外,static_cast和reinterpret_cast的区别主要在于多重继承,比如

class A {
    public:
    int m_a;
};
 
class B {
    public:
    int m_b;
};
 
class C : public A, public B {};

  那么对于以下代码:

C c;
printf("%p, %p, %p", &c, reinterpret_cast(&c), static_cast (&c));

前两个的输出值是相同的,最后一个则会在原基础上偏移4个字节,这是因为static_cast计算了父子类指针转换的偏移量,并将之转换到正确的地址(c里面有m_a,m_b,转换为B*指针后指到m_b处),而reinterpret_cast却不会做这一层转换。

 因此, 你需要谨慎使用 reinterpret_cast。

c++强制转换注意事项

  • 新式转换较旧式转换更受欢迎。原因有二,一是新式转型较易辨别,能简化“找出类型系统在哪个地方被破坏”的过程;二是各转型动作的目标愈窄化,编译器愈能诊断出错误的运用。

  • 尽量少使用转型操作,尤其是dynamic_cast,耗时较高,会导致性能的下降,尽量使用其他方法替代。

问题13:必须清楚HWND、HANDLE、HMODULE、HINSTANCE的区别

HWND是线程相关的,你可以通过HWND找到该窗口所属进程和线程

Handle 是代表系统的内核对象,如文件句柄,线程句柄,进程句柄。 系统对内核对象以链表的形式进行管理,载入到内存中的每一个内 核对象都有一个线性地址,同时相对系统来说,在串列中有一个索引位置,这个索引位置就是内核对象的handle。

HINSTANCE的本质是模块基地址,他仅仅在同一进程中才有意义,跨进程的HINSTANCE是没有意义

HMODULE 是代表应用程序载入的模块,win32系统下通常是被载入模块的线性地址。

HINSTANCE 在win32下与HMODULE是相同的东西(只有在16位windows上,二者有所不同). ———————————— 原文链接:Windows客户端开发--必须清楚HWND、HANDLE、HMODULE、HINSTANCE的区别_一蓑烟雨任平生 也无风雨也无晴-CSDN博客_hwnd

(HWND 是一个基本类型,和char int等同级别的,可以把它当做long型去看待,和身份证号一样。

HWND,h 是类型描述,表示句柄(handle), Wnd 是变量对象描述,表示窗口,所以hWnd 表示窗口句柄。hWnd 属性,返回窗体或控件的句柄(注意 OLE 容器控件不支持该属性。句柄:是由操作环境定义的一个唯一的整数值,它被程序用来标识或者切换到对象,如窗体或控件等。

问题14:GetModuleFileName

想要访问执行程序(.exe)路径下的文件,可以通过函数GetModuleFileName获取执行程序的绝对路径。

TCHAR szPath[ MAX_PATH ] = {0}; GetModuleFileName( NULL, szPath, MAX_PATH ); 解释说明: GetModuleFileName函数为windows的API函数,使用的时候需要包含windows.h的头文件; MAX_PATH是一个宏定义,值为260。执行完GetModuleFileName函数之后,szPath数组中保存的就是执行程序当前的绝对路径。 举例: 假设执行程序xp.exe的绝对路径为C:\Program Files\Dll\xp.exe,那么szPath数组中存储的值就是C:\Program Files\Dll\xp.exe。

问题15:std::string::npos

std::string::npos是一个常数,它等于size_type类型可以表示的最大值,用来表示一个不存在的位置,类型一般是std::container_type::size_type。

问题16:GetDeviceCaps()函数相关说明

CDC::GetDeviceCaps()物理长度与屏幕像素间的转换

作用: 读取DC的一些打印区域信息,主要是像素和英寸方面的数据.

声明: GetDeviceCaps(int )

使用例子: //所有像素数 int pagecx=dc.GetDeviceCaps(HORZRES); int pagecy=dc.GetDeviceCaps(VERTRES);

//即每英寸点数 short cxInch = dc.GetDeviceCaps(LOGPIXELSX); short cyInch = dc.GetDeviceCaps(LOGPIXELSY);

// 计算一个设备单位等于多少0.1mm double scaleX = 254.0 / (double)GetDeviceCaps(dc.m_hAttribDC,LOGPIXELSX); double scaleY = 254.0 / (double)GetDeviceCaps(dc.m_hAttribDC, LOGPIXELSY);

说明: 主要用到的参数见例子中的:HORZRES,VERTRES,LOGPIXELSX,LOGPIXELSY.总的来说是为了方便控制打印或重画时的控制,如为了定制打印时,一般依据的是物理的长度,而不是像素,而DC一般是用像素的映射模式,所以需要一下转换,上面这个函数就为这种转换设计的.

GDI中有一个函数是GetDeviceCaps(),可以获取一些关于设备的一些属性,如HORZSIZE/HORZRES/LOGPIXELSX等。 以上三者的关系通常满足:HORZSIZE = 25.4 * HORZRES/LOGPIXELSX HORZSIZE为屏幕水平尺寸(定为度量尺寸,以mm计),HORZRES为水平的像素总数(定为像素大小,平时所说的屏幕分辨率,但在这不这么称呼。这里,分辨率定为“每英寸的像素数”),LOGPIXELSX为逻辑像素(假设的每英寸的像素数,并不是刚才所说的实际的“分辨率”)。因此HORZSIZE也称为逻辑宽度。 当我们选择“显示”属性里的大字体时,LOGPIXELSX(通常分为96dpi与120dpi)变大了,这样假设原来的字体为10磅,则原来的字体横向所占像素(实际所占的像素数)为10(1/72)LOGPIXELSX,现在LOGPIXELSX变大了,则字体所占像素也大了,因此看起来字体大了。如果HORZRES不变的话,则HORZSIZE应该变小。然后这是和Windows有关的,在16位OS中,HORZSIZE值是固定的。 在XP系统上验证了一下,发现HORZSIZE值与LOGPIXELSX的值也是不变的,如果改变HORZRES的话,则HORZSIZE会发生相应变化,但LOGPIXELSX不变,一直是96。 验证数值是:当HORZRES/VERTRES分别为800/600、1280/1024、1360/768时,LOGPIXELSX/LOGPIXELSY一直为96,但HORZSIZE/VERTSIZE分别为320/240、375/300、400/320。于是个人断定:LOGPIXELSX/LOGPIXELSY与所选的字体(如TrueType)有关,windows默认的字体LOGPIXELSX/LOGPIXELSY值是定的,选大字体或小字体取它们的值都是一样的,而一些字体是不同的。而HORZSIZE/VERTSIZE与系统版本有关,在有的系统中,这两个值是适合此分辨率的标准显示器的尺寸(定值,长宽比与分辨率的比一样),不是通过公式计算的,也不等于公式计算的值;而有的系统版本这两个值为公式所得的值。 下边是petzold那本书上的两句(没摘英文的):“

然而,在Windows NT中,用老的方法定义HORZSIZE和VERTSIZE值。这种方法与Windows的16位版本一致。HORZRES和VERTRES值仍然表示水平和垂直图素的数值,LOGPIXELSX和LOGPIXELSY仍然与在「控制台」的「显示器」程序中选择的字体有关。在Windows 98中,LOGPIXELSX和LOGPIXELSY的典型值是96和120 dpi,这取决于您选择的是小字体还是大字体。

在Windows NT中的区别是HORZSIZE和VERTSIZE值固定表示标准显示器大小。对于普通的显示卡,取得的HORZSIZE和VERTSIZE值分别是320和240毫米。这些值是相同的,与选择的图素大小无关。因此,这些值与用HORZRES、VERTRES、LOGPIXELSX和LOGPIXELSY索引从GetDeviceCaps中得到的值不同。然而,可以用前面的公式计算在Windows 98下的HORZSIZE和VERTSIZE值。

HFONT CreateFont( int nHeight, //字体的高度 int nWidth, //字体的宽度 int nEscapement, //字体显示的角度 int nOrientation, //字体的角度 int nWeight, //字体的磅数 BYTE bItalic, //斜体字体 BYTE bUnderline, //带下划线的字体 BYTE cStrikeOut, //带删除线的字体 BYTE nCharSet, //所需的字符集 BYTE nOutPrecision, //输出的精度 BYTE nClipPrecision, //裁减的精度 BYTE nQuality, //逻辑字体与输出设备的实际 //字体之间的精度 BYTE nPitchAndFamily, //字体间距和字体集 LPCTSTR lpszFacename //字体名称 );

示例:

/************************/ HFONT hFont; HDC hDC; hFont=CreateFont(10,10,0,0,FW_THIN,true,false,false, CHINESEBIG5_CHARSET,OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS,DEFAULT_QUALITY, FF_MODERN,"宋体"); SelectObject(hDC,hFont); /************************/

GDI中有一个函数是GetDeviceCaps(),可以获取一些关于设备的一些属性,如HORZSIZE/HORZRES/LOGPIXELSX等。 以上三者的关系通常满足:HORZSIZE = 25.4 * HORZRES/LOGPIXELSX HORZSIZE为屏幕水平尺寸(定为度量尺寸,以mm计),HORZRES为水平的像素总数(定为像素大小,平时所说的屏幕分辨率,但在这不这么称呼。这里,分辨率定为“每英寸的像素数”),LOGPIXELSX为逻辑像素(假设的每英寸的像素数,并不是刚才所说的实际的“分辨率”)。因此HORZSIZE也称为逻辑宽度。 当我们选择“显示”属性里的大字体时,LOGPIXELSX(通常分为96dpi与120dpi)变大了,这样假设原来的字体为10磅,则原来的字体横向所占像素(实际所占的像素数)为10(1/72)LOGPIXELSX,现在LOGPIXELSX变大了,则字体所占像素也大了,因此看起来字体大了。如果HORZRES不变的话,则HORZSIZE应该变小。然后这是和Windows有关的,在16位OS中,HORZSIZE值是固定的。 我在XP系统上验证了一下,发现HORZSIZE值与LOGPIXELSX的值也是不变的,如果改变HORZRES的话,则HORZSIZE会发生相应变化,但LOGPIXELSX不变,一直是96。 验证数值是:当HORZRES/VERTRES分别为800/600、1280/1024、1360/768时,LOGPIXELSX/LOGPIXELSY一直为96,但HORZSIZE/VERTSIZE分别为320/240、375/300、400/320。于是个人断定:LOGPIXELSX/LOGPIXELSY与所选的字体(如TrueType)有关,windows默认的字体LOGPIXELSX/LOGPIXELSY值是定的,选大字体或小字体取它们的值都是一样的,而一些字体是不同的。而HORZSIZE/VERTSIZE与系统版本有关,在有的系统中,这两个值是适合此分辨率的标准显示器的尺寸(定值,长宽比与分辨率的比一样),不是通过公式计算的,也不等于公式计算的值;而有的系统版本这两个值为公式所得的值。 下边是petzold那本书上的两句(没摘英文的):“ 然而,在Windows NT中,用老的方法定义HORZSIZE和VERTSIZE值。这种方法与Windows的16位版本一致。HORZRES和VERTRES值仍然表示水平和垂直图素的数值,LOGPIXELSX和LOGPIXELSY仍然与在「控制台」的「显示器」程序中选择的字体有关。在Windows 98中,LOGPIXELSX和LOGPIXELSY的典型值是96和120 dpi,这取决于您选择的是小字体还是大字体。

在Windows NT中的区别是HORZSIZE和VERTSIZE值固定表示标准显示器大小。对于普通的显示卡,取得的HORZSIZE和VERTSIZE值分别是320和240毫米。这些值是相同的,与选择的图素大小无关。因此,这些值与用HORZRES、VERTRES、LOGPIXELSX和LOGPIXELSY索引从GetDeviceCaps中得到的值不同。然而,可以用前面的公式计算在Windows 98下的HORZSIZE和VERTSIZE值。

问题17:Afx是什么?

afx是application framework的缩写。 对于类向导来说这个符号是有意义的.它是一个消息处理函数的前缀。类向导生成的消息函数,分发函数,事件响应函数都以这个为前缀;如果去掉了,向导将不能识别。

问题18:初始化列表

#include 
#include 
#include 
​
// 使用 std::initializer_list 来初始化任意长度的初始化列表
//stl中的容器是通过使用 std::initializer_list 完成的
class Foo
{
public:
    Foo(std::initializer_list ){}
};
​
class FooVector
{
    std::vector content_;
​
public:
    FooVector(std::initializer_list list)//initializer_list 负责接收初始化列表
    {
        for (auto it = list.begin(); it != list.end(); ++it)
        {
            content_.push_back(*it);
        }
    }
};
​
​
//map 是以 pair形式插入的。map中的元素的类型value_type 
//typedef pair value_type;
​
​
class FooMap
{
    std::map content_;
    using pair_t = std::map::value_type;//重新命名类型   typedef
​
public:
    FooMap(std::initializer_list list)
    {
        for (auto it = list.begin(); it != list.end(); ++it)
        {
            content_.insert(*it);
        }
    }
};
​
//使用 std::initializer_list 给自定义类型做初始化
void test01()
{
    Foo foo = { 1,2,3,4,5 };
    FooVector foo1 = { 1, 2, 3, 4, 5 };
    FooMap foo2 = { { 1, 2 }, { 3, 4 }, { 5, 6 } };
}
​
//使用 std::initializer_list 传递同类型的数据
void func(std::initializer_list list)
{
    std::cout << "size = "< func2(void)
{
    int a = 1, b = 2;
    return { a,b };//ab 返回时并没有拷贝
}
​
//正确的使用
std::vector func3(void)
{
    int a = 1, b = 2;
    return { a,b };//ab 返回时并没有拷贝
}
​
void test03()
{
    std::initializer_list myList;
    size_t n = myList.size();
    myList = { 1,2,3,4,5,6 };
    n = myList.size();
    myList = { 11,22};
    n = myList.size();
​
    std::vector a;
    a = func2();//值时乱码值
    a = func3();
​
}
​
int main(void)
{
    test01();
    test02();
    test03();
    system("pause");
    return 0;
}

问题19:__FILE__, __LINE__是什么?

在写程序的时候,总是或多或少会加入一些printf之类的语句用于输出调试信息,但是printf语句有个很不方便的地方就是当我们需要发布程序的时候要一条一条的把这些语句删除,而一旦需要再次调试的时候,这些语句又不得不一条条的加上,这给我们带来了很大的不便,浪费了我们很多的时间,也造成了调试的效率低下。所以,很多人会选择使用宏定义的方式来输出调试语句。

编译器内置宏,ANSI C标准中有几个标准预定义宏(也是常用的):

LINE: 在源代码中插入当前源代码行号; FILE: 在源文件中插入当前源文件名; DATE: 在源文件中插入当前的编译日期 TIME: 在源文件中插入当前编译时间; STDC: 当要求程序严格遵循ANSI C标准时该标识被赋值为1; __cplusplus: 当编写C++程序时该标识符被定义。 ———————————————— 版权声明:本文为CSDN博主「郎涯技术」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:C/C++ __FILE__,__LINE__输出调试信息_郎涯技术-CSDN博客

问题20:关于宏的一些知识

#define 定义一个预处理宏 #undef 取消宏的定义

#if 编译预处理中的条件命令,相当于C语法中的if语句 #ifdef 判断某个宏是否被定义,若已定义,执行随后的语句 #ifndef 与#ifdef相反,判断某个宏是否未被定义 #elif 若#if, #ifdef, #ifndef或前面的#elif条件不满足,则执行#elif之后的语句,相当于C语法中的else-if #else 与#if, #ifdef, #ifndef对应, 若这些条件不满足,则执行#else之后的语句,相当于C语法中的else #endif #if, #ifdef, #ifndef这些条件命令的结束标志. defined  与#if, #elif配合使用,判断某个宏是否被定义

问题21: c++中 . 和 -> 的区别是什么?

主要用于访问类的成员。

->主要用于类类型的指针访问的成员,而.运算符,主要用于类类型的对象访问类的成员。

class A{
​
public:
​
  int a;
​
}
​
A  ma;
​
A *p=ma;

指针p应用->来访问成员a,比如p->a,而ma应使用.来访问,比如ma.a区别就在这里,凡是指针就使用->,对象就使用.运算符。

如果定义了一个结构体数组。

struct student
​
{
​
  int age;
​
  char name[100];
​
};
​
struct student array[3];

通过这个数组进行调用这个结构体中的成员的时候,只能使用.而不能使用->。

如果要是让一个指针指向这个数组的话,可以使用->这个符号。

struct student *p = array;

p->age = 30;

memcpy(p->name,"刘德华");

这样是可以的。

问题22: wcscpy_s

wcscpy_s是一个能够拷贝宽字符类型字符串的安全函数。它返回一个error_t类型的值。

wcscpy_s的函数原型为:

error_t wcscpy_s(wchar_t *strDestination,size_t numberOfCharacters,const wchar_t *strSource); 

其中strDestination为指向将要复制字符串的目的缓冲区的地址,numberOfCharacters为缓冲区大小(以字符计),strSource为指向源字符串的指针

下面是一个例子:

PCWSTR string = TEXT("mydef"); 
WCHAR buff[6];
wcscpy_s(buff, _countof(buff), string);

其中TEXT是一个宏,该宏能够自动判断当前字符集环境(Unicode还是ANSI还是其他的)并进行适当的转换。

与其类似的还有以下函数:

error_t wcscat_s(wchar_t *strDestination,size_t numberOfCharacters,const wchar_t *strSource)

问题23: #define __DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_1

#define __DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_1(_ReturnType, _FuncName, _DstType, _Dst, _TType1, _TArg1)   
            extern "C++"                                                                 
        {                                                                             
                template                                                    
                inline   
 _ReturnType __CRTDECL _FuncName(_DstType (&_Dst)[_Size], _TType1 _TArg1) _CRT_SECURE_CPP_NOTHROW 
                {
                    return _FuncName(_Dst, _Size, _TArg1);
                }
       }

这个 __DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_0 宏在 MSVC 的 MSVCRT 头文件 crtdefs.h 中定义,也在 MinGW(对 MSVCRT 开源重实现)中定义。

作者:breaker
链接:https://www.zhihu.com/question/323362565/answer/693091109
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
​
#include 
#include 
​
#define __MY_DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_0(__ret, __func, __dsttype, __dst)   \
extern "C++" {                                              \
template                                     \
inline __ret __cdecl __func(__dsttype (&__dst)[__size]) {   \
    return __func(__dst, __size);                           \
}   \
}
​
int accumulate(const int* arr, size_t len)
{
    int sum = 0;
    for (size_t i = 0; i < len; ++i)
        sum += arr[i];
    return sum;
}
​
__MY_DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_0(int, accumulate, int, arr)
​
int main()
{
    int arr[] = {12, 23, 34, 45, 56};
    int sum1 = accumulate(arr, _countof(arr));
    int sum2 = accumulate(arr);
    printf("sum1 = %d\n"
           "sum2 = %d\n",
           sum1, sum2);
    return 0;
}

由于在C/C++里,原始数组作参数会隐式转换为指针,无法使用sizeof来得出数组的长度,所以一般会带一个参数来传数组长度:

int func(char array[], size_t length); // array为传递的数组,length传递长度

但是手动传长度嫌麻烦,C++里可以利用模板推导长度:

template 
inline int func(char (&array)[__size]) // 模板参数__size自动推导长度
{
    return func(array, __size);       // 调用包含数组和长度两个参数的版本
}

问题中的宏用于快速定义该模板:

// 宏的四个参数依次是:
// __ret     : 返回值类型 
// __func    : 函数名 
// __dsttype : 参数数组的元素类型 
// __dst     : 模板函数的形参名字(这个可以随便写)
__DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_0(int, func, char, random)

#include 

// 好像已经被定义了
#ifdef __DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_0
#undef __DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_0
#endif

// 题中的宏
#define __DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_0(__ret, __func, __dsttype, __dst) \
extern "C++" { \
	template  \
	inline __ret __cdecl __func(__dsttype (&__dst)[__size]) { \
		return __func(__dst,__size); \
	} \
}

// 带两个参数的原始函数
int func(char array[], size_t length)
{
	printf("length : %u\n", length);
	return 0;
}

// 生成模板
__DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_0(int, func, char, chicken_you_are_too_beautiful)

// 不解释,你懂的
int main()
{
	char array1[15];
	char array2[20];
	func(array1);
	func(array2);
	
	return 0;
}

输出:

$ ./a.out
length : 15
length : 20

既然用推导了数组长度,干脆连类型一起推导了:

#include 
​
// 带两个参数的原始函数
size_t func1(char array[], size_t length)
{
    (void)array;
    printf("func1 array length : %u\n", length);
    return length;
}
​
char* func2(int array[], size_t length)
{
    (void)array;
    static char ret[] = "hello world";
    printf("func2 array length : %u\n", length);
    return ret;
}
​
// 更加简化的方式(-std=c++14)
#define SIMPLIFY_OVERRIDE(__func) \
template \
inline decltype(auto) __func(__dsttype (&array)[__size]) \
{ \
    return __func(array, __size); \
}
​
SIMPLIFY_OVERRIDE(func1)
SIMPLIFY_OVERRIDE(func2)
​
// 不解释,你懂的
int main()
{
    char array1[15];
    char array2[20];
    printf("func1 return %u\n", func1(array1));
    printf("func1 return %u\n", func1(array2));
    
    int array3[25];
    printf("func2 return %s\n", func2(array3));
    
    return 0;
}

问题24: swprintf_s

函数原型

template 
int swprintf_s(
   wchar_t (&buffer)[size],
   const wchar_t *format [,
   argument]...
); // C++ only

这个函数只有C++中才有,C++中字符串遇到 int, double等时不能像Java那样自动实现类型的转换 ,所以需要程序员做些事情来代替编译器的工作

参数:

1.一个类型为wchar_t的数组

2.数组的大小

3.目标字符串的格式

4.需要你拼接的部分

注:当然你的格式可以自己定义,后面的参数根据你自己定义的格式来。swprintf_s的功能也不仅限于此,还有许多其它用法。这里就不讲了。

// crt_swprintf_s.c
// wide character example
// also demonstrates swprintf_s returning error code
#include 
​
int main( void )
{
   wchar_t buf[100];
   int len = swprintf_s( buf, 100, L"%s", L"Hello world" );
   printf( "wrote %d characters\n", len );
   len = swprintf_s( buf, 100, L"%s", L"Hello\xffff world" );
   // swprintf_s fails because string contains WEOF (\xffff)
   printf( "wrote %d characters\n", len );
}

输出结果:

wrote 11 characters wrote -1 characters

问题25: CFile与CArchive

CFile是MFC文件类的基类,它直接提供非缓冲的二进制磁盘输入/输出设备,并直接地通过派生类支持文本文件和内存文件。CFile与CArchive类共同使用,支持MFC对象的串行化。 该类与其派生类的层次关系让程序通过多形CFile接口操作所有文件对象。例如,一个内存文件相当一个磁盘文件。使用CFile及其派生类进行一般目的的磁盘I/O,使用ofstream或其它Microsoft输入输出流类将格式化文本送到磁盘文件。 通常,一个磁盘文件在CFile构造时自动打开并在析构时关闭。静态成员函数使你可以在不打开文件的情况下检查文件状态。 要了解关于使用CFile的更多信息,可参阅联机文档“Visual C++程序员指南”中的“MFC中的文件”和“Microsoft Visual C++ 6.0 运行库参考”中的“文件处理”。 #include

CFile::Open
virtual BOOL Open(LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError = NULL);

CArchive

CArchive没有基类。 CArchive允许以一个永久二进制(通常为磁盘存储)的形式保存一个对象的复杂网络,它可以在对象被删除时,还能永久保存。可以从永久存储中装载对象,在内存中重新构造它们。使得数据永久保留的过程就叫作“串行化”。 可以把一个归档对象看作一种二进制流。象输入/输出流一样,归档与文件有关并允许写缓冲区以及从硬盘读出或读入数据。输入/输出流处理一系列ASCII字符,但是归档文件以一种有效率、精练的格式处理二进制对象。 **必须在创建一个CArchive对象之前,创建一个CFile对象。**另外,必须确信归档文件的装入/存储与文件的打开模式是兼容的。每一个文件只限于一个活动归档文件。 当构造一个CArchive对象时,要把它附加给表示一个打开文件的类CFile(或派生类)的对象上。还要指定归档文件将用于装载还是存储。 CArchive对象不仅可以处理首要类型,而且还能处理为串行化而设计的CObject派生类的对象。一个串行化类通常有一个Serialize成员函数并且使用DECLARE_SERIAL和IMPLEMENT_SERIAL宏。这些在CObject类中有所描述。 重载提取(>>)和插入(<<)是方便的归档编程接口。它支持主要类型和CObject派生类。 CArchive还支持使用MFC Windows套接字类CSocket和CSocketFile编程。IsBufferEmpty成员函数也支持这种使用。如果要了解有关CArchive的更多信息,请参阅联机文档“Visual C++ 程序员指南”中的“串行化(永久对象)” 和“Windows套接字:在归档文件中使用套接字” #include

问题26: 关于wstring的rfind()

size_type rfind( const basic_string& str, size_type pos = npos ) const;

寻找等于给定字符序列的最后子串。搜索始于 pos ,即找到的子串必须不始于 pos 后的位置。若将 npos 或任何不小于 size()-1 的值作为 pos 传递,则在整个字符串中搜索。

1) 寻找等于 str 的最后子串。 2) 寻找等于范围 [s, s+count) 的最后子串。此范围能包含空字符。 3) 寻找等于 s 所指向的字符串的最后子串。由首个空字符,用 Traits::length(s) 确定字符串长度。 4) 寻找等于 ch 的最后字符。 5) 如同用 std::basic_string_view sv = t; 隐式转换 t 为 string_view sv ,然后寻找等于 sv 内容的最后子串。此重载仅若 std::is_convertible_v> 为 true 且 std::is_convertible_v 为 false 才参与重载决议。 所有情况下均调用 Traits::eq 检查相等性。

参数

str - 要搜索的 string pos - 开始搜索的位置 count - 要搜索的子串长度 s - 指向要搜索的字符串的指针 ch - 要搜索的字符 t - 要搜索的对象(可转换为 std::basic_string_view )

返回值

找到的子串的首字符位置,或若找不到这种子串则为 npos 。注意这是从字符串开始,而非末尾的偏移。

若搜索任何空字符串( str.size() 、 count 或 Traits::length(s) 为零),则返回 pos (立即找到空字符串),除非 pos > size() (包括 pos == npos 的情况),该情况下返回 size() 。

否则,若 size() 为零,则始终返回 npos 。

异常

5)

noexcept 规定:

noexcept(std::is_nothrow_convertible_v>)

问题27: 字符串转整数函数atoi, _ttoi, _wtoi - 字符串转整数 (int)

函数原型:

int atoi(const char *s);
int _wtoi(const wchar_t *s);

语法:

int atoi(
   const char *str 
);
int _wtoi(
   const wchar_t *str 
);
int _atoi_l(
   const char *str,
   _locale_t locale
);
int _wtoi_l(
   const wchar_t *str,
   _locale_t locale
);

tchar.h 函数 项目选项 _TCHAR maps to 选择 wchar_t 映射到 项目选项 _TCHAR maps to 选择 char 映射到
_ttoi _wtoi atoi

头文件:

#include

命名空间:

std

参数:

s:整数,格式:"空白字符[数字]"

返回值:

整数,int 类型 如果参数 s 包含不可识别的字符,会终止于第一个不可识别的字符,返回前面可识别部分转为整数的值,如果第一个字符不可识别返回值为 0; 如果超出了整数范围会溢出,得到错误的数值 (溢出之后的数值一般会等于丢掉超范围的高位,保留范围之内的低位的数值); 这些错误都不产生异常。

字符串 转换结果 说明
" 12345" 12345
"-54321" -54321
"2000000000" 2000000000
"3000000000" -1294967296 3000000000 超出了 int 整数范围:-2147483648 ~ 2147483647,得到错误的结果
"-7777777777" 812156815 -7777777777 超出了 int 整数范围:-2147483648 ~ 2147483647,得到错误的结果
"-1111111111" -1111111111
"123e45" 123 终止于不可识别的字符 'e',前面的内容转换结果为 123
"abcdef" 0 第一个字符不可识别,转换结果为 0
// crt_atoi.c
// This program shows how numbers 
// stored as strings can be converted to
// numeric values using the atoi functions.

#include 
#include 
#include 

int main( void )
{
    char    *str = NULL;
    int     value = 0;

    // An example of the atoi function.
    str = "  -2309 ";
    value = atoi( str );
    printf( "Function: atoi( \"%s\" ) = %d\n", str, value );

    // Another example of the atoi function.
    str = "31412764";
    value = atoi( str );
    printf( "Function: atoi( \"%s\" ) = %d\n", str, value );

    // Another example of the atoi function 
    // with an overflow condition occuring.
    str = "3336402735171707160320";
    value = atoi( str );
    printf( "Function: atoi( \"%s\" ) = %d\n", str, value );
    if (errno == ERANGE)
    {
       printf("Overflow condition occurred.\n");
    }
}

问题28: c_str()函数

const CharT* c_str() const noexcept;

返回指向拥有数据等价于存储于字符串中的空终止字符数组的指针。

//标准库的string类提供了三个成员函数来从一个string得到c类型的字符数组

//c_str():生成一个const char*指针,指向以空字符终止的数组。

该指针有范围 [c_str(); c_str() + size()] 为合法,且其中的值对应存储于字符串的值,且在最后位置有个附加的空终止字符。

从 c_str() 获得的指针可能被下列行为非法化:

传递给任何非标准库函数字符串的非 const 引用,或 在字符串上调用非 const 成员函数,包括 operator[] 、 at() 、 front() 、 back() 、 begin() 、 rbegin() 、 end() 及 rend() 。 通过 c_str() 写入字符数组是未定义行为。

//这个数组应该是string类内部的数组
#include 
//需要包含cstring的字符串
#include 
using namespace std;
 
int main()
{
    //string-->char*
    //c_str()函数返回一个指向正规C字符串的指针, 内容与本string串相同
 
    //这个数组的数据是临时的,当有一个改变这些数据的成员函数被调用后,其中的数据就会失效。
    //因此要么现用先转换,要么把它的数据复制到用户自己可以管理的内存中
    const char *c;
    string s = "1234";
    c = s.c_str();
    cout< 
  

问题29: 奇怪的代码?以下的代码if里面和if外面有什么区别?

if (m_data_version >= _T("2.66"))
{
    ar >> song_info.track;
}
else
{
    BYTE track;
    ar >> track;
    song_info.track = track;
}

问题30: 通过以下代码可以了解一首歌曲有些什么关键性信息

    wstring file_path{};    //歌曲的路径
    wstring lyric_file{};   //匹配的歌词文件的路径
    wstring title;      //标题
    wstring artist;     //艺术家
    wstring album;      //唱片集
    wstring comment;    //注释
    wstring genre;      //流派
​
    unsigned __int64 song_id{};         //歌曲对应的网易云音乐中的歌曲ID
    __int64 last_played_time{};     //上次播放的时间
    unsigned __int64 modified_time{};        //修改时间
​
​
    int track{};        //音轨序号
    int listen_time{};          //歌曲累计听的时间(单位为秒)
    int freq{};         //采样频率
    Time lengh{};           //歌曲的长度
    Time start_pos{};       //音频的起始位置,用于cue分轨
    Time end_pos{};
    unsigned short year{};      //年份
    short bitrate{};        //比特率
​
​
    WORD flags{};       //保存一些标志
    BYTE tag_type{};        //标签的类型(0:其他;1:ID3v1;2:ID3v2;3:APE)
    BYTE genre_idx{ 255 };      //以字节表示的流派号
    bool info_acquired{ false };        //如果已经获取到了信息,则为ture
    bool is_favourite{ false };
    bool is_cue{ false };       //如果曲目是cue分轨,则为true
    BYTE rating{ 255 };         //歌曲分级
    BYTE bits{};                //位深度
    BYTE channels{};            //声道数
​

问题31: 问题代码的分析

if (m_data_version >= _T("2.663") && m_data_version < _T("2.690"))
{
	bool no_online_album_cover {song_info.NoOnlineAlbumCover() };
	bool no_online_lyric  { song_info.NoOnlineLyric() };
	ar >> no_online_album_cover;
	ar >> no_online_lyric;
}
#define DECLARE_SONG_INFO_FLAGS(func_name,flag_bit)\
	bool func_name() const {return CCommon::GetNumberBit(flags,flag_bit);}\
	void Set##func_name(bool val){CCommon::SetNumberBit(flags, flag_bit,val);}

	DECLARE_SONG_INFO_FLAGS(NoOnlineLyric, 0)
	DECLARE_SONG_INFO_FLAGS(NoOnlineAlbumCover, 1)
	DECLARE_SONG_INFO_FLAGS(AlwaysUseExternalAlbumCover, 2)
	DECLARE_SONG_INFO_FLAGS(ChannerlInfoAcquired, 3)

问题34: 以CMainWindow来实现绘制

头文件的书写:

class CMyApp : public CWinApp
{
public:
	CMyApp() noexcept;


	// 重写
public:
	virtual BOOL InitInstance();
	virtual int ExitInstance();

	// 实现

public:
	afx_msg void OnAppAbout();
	DECLARE_MESSAGE_MAP()
};

class CMainWindow :public CFrameWnd
{
public:
	CMainWindow();
protected:
	afx_msg void OnPaint();
	DECLARE_MESSAGE_MAP();

};
extern CMyApp theApp;

实现文件的书写

BOOL CMyApp::InitInstance()
{
	CWinApp::InitInstance();


	EnableTaskbarInteraction(FALSE);

	// 使用 RichEdit 控件需要 AfxInitRichEdit2()
	// AfxInitRichEdit2();

	// 标准初始化
	// 如果未使用这些功能并希望减小
	// 最终可执行文件的大小,则应移除下列
	// 不需要的特定初始化例程
	// 更改用于存储设置的注册表项
	// TODO: 应适当修改该字符串,
	// 例如修改为公司或组织名
	SetRegistryKey(_T("应用程序向导生成的本地应用程序"));


	 若要创建主窗口,此代码将创建新的框架窗口
	 对象,然后将其设置为应用程序的主窗口对象
	//CFrameWnd* pFrame = new CMainFrame;
	//if (!pFrame)
	//	return FALSE;
	//m_pMainWnd = pFrame;
	 创建并加载框架及其资源
	//pFrame->LoadFrame(IDR_MAINFRAME,
	//	WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, nullptr,
	//	nullptr);





	 唯一的一个窗口已初始化,因此显示它并对其进行更新
	//pFrame->ShowWindow(SW_SHOW);
	//pFrame->UpdateWindow();
	//return TRUE;

	m_pMainWnd = new CMainWindow;
    // Initial state of the application's window; normally,
	// this is an argument to ShowWindow().
	int m_nCmdShow;
	m_pMainWnd->ShowWindow(m_nCmdShow);
	m_pMainWnd->UpdateWindow();
	return TRUE;
}


BEGIN_MESSAGE_MAP(CMainWindow, CFrameWnd)
	ON_WM_PAINT()
END_MESSAGE_MAP()


CMainWindow::CMainWindow() 
{
	Create(NULL, _T("The Hello Application"));
}
void CMainWindow::OnPaint() 
{
	CPaintDC dc(this);
	CRect rect;
	GetClientRect(&rect);

	dc.DrawText(_T("Hello MFC"),-1,&rect, DT_SINGLELINE|DT_CENTER|DT_VCENTER);
}

CFrameWnd::Create
BOOL Create(
   LPCTSTR  lpszClassName,
   LPCTSTR lpszWindowName,
   DWORD dwStyle =  WS_OVERLAPPEDWINDOW,
   const RECT &rect = rectDefault,
   CWnd*  pParentWnd = NULL,
   LPCTSTR lpszMenuName = NULL,
   DWORD dwExStyle  = 0,
   CCreateContext* pContext =  NULL
   );

返回值:如果初始化成功,则返回非零值,否则为0。

参数:

lpszClassName 指向一个用于命名Windows类的以空终止的字符串。类名可以是任何以AfxRegisterJWndClass全局函数登记或RegisterClassWindows函数登记的名。如果为NULL,使用预定义的缺省CFrameWnd属性。
lpszWindowName 指向代表窗口名的以空终止的字符串,用作标题条的文本。
dwStyle 指定窗口风格属性。如果想标题条自动显示窗口代表的文档名,则应包含FWS_ADDTOTITLE风格。
rect 定义窗口大小和位置。rectDefault值使Windows为一个新窗口指定大小和位置。
pParentWnd 指定框架窗口的父窗口,对最高层框架窗口来说应为NULL。
lpszMenuName 指定与窗口一起使用的菜单资源名。如果菜单有一个整数ID而不是字符串ID,则使用MAKEINTRESOURCE。此参数可为NULL。
dwExStyle 指定窗口扩展的风格属性。
pContext 指向CCreateContext结构的指针。可为NULL。
CMainWindow::CMainWindow() {
	CString strWndClass = AfxRegisterWndClass(0, theApp.LoadStandardCursor(IDC_ARROW),
		(HBRUSH)(COLOR_3DFACE + 1),
		theApp.LoadStandardIcon(IDI_WINLOGO));

	CreateEx(0, strWndClass, _T("FontView"), WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX,
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL);

	CRect rect(0, 0, m_cxChar * 68, m_cyChar * 26);
	CalcWindowRect(&rect);

	SetWindowPos(NULL, 0, 0, rect.Width(), rect.Height(),
		SWP_NOZORDER | SWP_NOMOVE | SWP_NOREDRAW);
}

问题35: 关于CDC::GetDeviceCaps()

问题36: 设置滚动条位置和尺寸

问题37: MFC中的字体问题

C++之MFC学习_第1张图片

C++之MFC学习_第2张图片

tmHeight指字符高度(不包括两行字符之间的间距),tmAscent表示字符基线以上部分的高度,tmDescent表示字符基线以下部分的高度。 tmInternalLeading表示字符内预留的间距包含在tmAscent中(主要用于显示重音符号等)。 tmExternalLeading标准两行字符之间的间距, tmAveCharWidth表示(小写 x)字符的加权平均宽度, tmMaxCharWidth表示字符的最大宽宽度。

大写字符的平均宽度通常是字符平均宽度的1.5倍。 大写字母平均宽度 = tmMaxCharWith * 1.5 = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2

tmPitchFamily的低位表示字符是变宽字符还是等宽字符。1表示变宽字符,0表示等宽字符。

字符的横向大小由2个值确定:

① tmAveCharWidth,小写字母加权平均宽度。

② tmMaxCharWidth,字体中最宽字符的宽度。

对于等宽字体,tmAveCharWidth和tmMaxCharWidth这两个值相等。

大写字母的平均宽度比较复杂,如果:

① 字体是等宽字体,那么大写字母的平均宽度等于tmAveCharWidth。

② 字体是变宽字体,那么大写字母的平均宽度等于tmAveCharWidth*1.5。

判断字体是否是变宽字体,可以通过TEXTMETRIC结构中的tmPitchAndFamily域的低位判断,如果低位是1,那么是变宽字体,如果是0,那么是等宽字体。

  TTextMetric = record
    tmHeight : Longint; { the height of a character }
    tmAscent : Longint; { the ascent of a character }
    tmDescent : Longint; { the descent of a character }
    tmInternalLeading : Longint; { the internal leading }
    tmExternalLeading : Longint; { the external leading }
    tmAveCharWidth : Longint; { the average character width }
    tmMaxCharWidth : Longint; { the maximum character width }
    tmWeight : Longint; { the boldness value }
    tmOverhang : Longint; { the overhang width }
    tmDigitizedAspectX : Longint; { the horizontal aspect }
    tmDigitizedAspectY : Longint; { the vertical aspect }
    tmFirstChar : WORD; { the first character }
    tmLastChar : WORD; { the last character }
    tmDefaultChar : WORD; { the default character }
    tmBreakChar : WORD; { the word break character }
    tmItalic : Byte; { the italics flag }
    tmUnderlined : Byte; { the underlined flag }
    tmStruckOut : Byte; { the strikeout flag }
    tmPitchAndFamily : Byte; { the pitch and family flags }
    tmCharSet : Byte; { the character set }
  end;

问题38: 关于CRect

CRect::SetRect

void SetRect( int x1, int y1, int x2, int y2 );

参数: x1 指定左上角的x坐标。  
y2 指定左上角的y坐标。  
x2 指定右下角的x坐标。  
y2 指定右下角的y坐标。  

说明:此函数将CRect的各个坐标设置为指定的坐标值。
CRect::InflateRect

void InflateRect( int x, int y );
void InflateRect( SIZE size );
void InflateRect( LPCRECT lpRect );
void InflateRect( int l, int t, int r, int b );

参数: x 指定扩大CRect左和右边的单位数。  
y 指定扩大CRect上、下边的单位数。  
size 一个指定扩大CRect的单位数的SIZE或CSize。cx值指定扩大左、右边的单位数,cy指定扩大上、下边的单位数。  
lpRect 指向一个RECT结构或CRect,指定扩大每一边的单位数。  
l 指定扩大CRect左边的单位数。  
t 指定扩大CRect上边的单位数。  
r 指定扩大CRect右边的单位数。  
b 指定扩大CRect下边的单位数。  

说明:
InflateRect通过将CRect的边向远离其中心的方向移动来扩大它。为了做到这一点,InflateRect从矩形的左边和上边减去单位数,将单位数增加到右边和下边。InflateRect的参数是有符号的值;正值扩大CRect,而负值则缩小它。
前两个重载函数使CRect相对的两对边都扩大,因此CRect的总宽度增加了了两倍x(或cx),总高度增加了两倍y(或cy)。其它两个重载函数使CRect的边则是相对独立的扩大。
CRect::DeflateRect

void DeflateRect( int x, int y );
void DeflateRect( SIZE size );
void DeflateRect( LPCRECT lpRect );
void DeflateRect( int l, int t, int r, int b );

参数: x 指定缩小CRect的左和右边的单位数。  
y 指定缩小CRect的上、下边的单位数。  
size 一个指定缩小CRect的单位数的SIZE或CSize。cx值指定缩小左、右边的单位数,cy指定缩小上、下边的单位数。  
lpRect 指向一个RECT结构或CRect,指定缩小每一边的单位数。  
l 指定缩小CRect左边的单位数。  
t 指定缩小CRect上边的单位数。  
r 指定缩小CRect右边的单位数。  
b 指定缩小CRect下边的单位数。  

说明:
DeflateRect通过将CRect的边向其中心移动来缩小它。为了做到这一点,DeflateRect将单位数增加到矩形的左边和上边,从右边和下边减去单位数。DeflateRect的参数是有符号的值;正值缩小CRect,而负值则放大它。
前两个重载函数使CRect相对的两对边都缩小,因此CRect的总宽度减小了两倍x(或cx),总高度减小了两倍y(或cy)。其它两个重载函数使CRect的边相对独立的缩小。

问题39: 关于有CalcWindowRect()

  每当主框架窗口的客户区尺寸发生变化或控制条的位置发生变化,需要重新排列客户区时,调用该函数,根据视图客户区尺寸计算视图窗口的尺寸。

  我们知道,排列主窗口客户区是由CFrameWnd::RecalcLayout()完成的。显然,视图的CalcWindowRect()函数也是由它触发调用的。主窗口的客户区尺寸减掉所有控制占用的部分,剩下的区域分给视图,这部分区域作为实参传入CalcWindowRect()。在CalcWindowRect()函数内,需要计算视图窗口的尺寸。代码如下:

void CView::CalcWindowRect(LPRECT lpClientRect, UNIT nAdjustType)
​
  {
​
  // lpClientRect此时是整个视图客户区的尺寸
​
  // 需要为滚动条增加尺寸吗
​
  if (nAdjustType != 0)
​
  {
​
  // 调用API,根据窗口风格计算窗口尺寸
​
  ::AdjustWindowRectEx(lpClientRect, 0, FALSE, GetExStyle());
​
  DWORD dwStyle = GetStyle();
​
  if (dwStyle & WS_VSCROLL)
​
  {
​
  // 为垂直滚动条增加尺寸
​
  int nAdjust = afxData.csVScroll;
​
  if (dwStyle & WS_BORDER)
​
  nAdjust -= CX_BORDER;
​
  lpClientRect->right += nAdjust;
​
  }
​
  if (dwStyle & WS_HSCROLL)
​
  {
​
  // 为水平滚动条增加尺寸
​
  int nAdjust = afxData.cyHScroll;
​
  if (dwStyle & WS_BORDER)
​
  nAdjust -= CY_BORDER;
​
  lpClientRect->bottom += nAdjust;
​
  }
​
  return;
​
  }
​
  // 无需为滚动条增加尺寸,调用基类成员完成计算
​
  CWnd::CalcWindowRect(lpClientRect, nAdjustType);
​
  }

问题40: 关于SetWindowRect

  BOOL SetWindowPos(
   HWND hWndInsertAfter,
   int x,
   int y,
   int cx,
   int cy,
   UINT nFlags 
) throw();
BOOL SetWindowPos(
   HWND hWndInsertAfter,
   LPCRECT lpRect,
   UINT nFlags 
) throw();
//声明:
SetWindowPos(
hWnd: HWND; {窗口句柄}
hWndInsertAfter: HWND; {窗口的 Z 顺序}
X, Y: Integer; {位置}
cx, cy: Integer; {大小}
uFlags: UINT {选项}
): BOOL;
//hWndInsertAfter 参数可选值:
HWND_TOP = 0; {在前面}
HWND_BOTTOM = 1; {在后面}
HWND_TOPMOST = HWND(-1); {在前面, 位于任何顶部窗口的前面}
HWND_NOTOPMOST = HWND(-2); {在前面, 位于其他顶部窗口的后面}
//uFlags 参数可选值:
SWP_NOSIZE = 1; {忽略 cx、cy, 保持大小}
SWP_NOMOVE = 2; {忽略 X、Y, 不改变位置}
SWP_NOZORDER = 4; {忽略 hWndInsertAfter, 保持 Z 顺序}
SWP_NOREDRAW = 8; {不重绘}
SWP_NOACTIVATE = $10; {不激活}
SWP_FRAMECHANGED = $20; {强制发送 WM_NCCALCSIZE 消息, 一般只是在改变大小时才发送此消息}
SWP_SHOWWINDOW = $40; {显示窗口}
SWP_HIDEWINDOW = $80; {隐藏窗口}
SWP_NOCOPYBITS = $100; {丢弃客户区}
SWP_NOOWNERZORDER = $200; {忽略 hWndInsertAfter, 不改变 Z 序列的所有者}
SWP_NOSENDCHANGING = $400; {不发出 WM_WINDOWPOSCHANGING 消息}
SWP_DRAWFRAME = SWP_FRAMECHANGED; {画边框}
SWP_NOREPOSITION = SWP_NOOWNERZORDER;{}
SWP_DEFERERASE = $2000; {防止产生 WM_SYNCPAINT 消息}
SWP_ASYNCWINDOWPOS = $4000; {若调用进程不拥有窗口, 系统会向拥有窗口的线程发出需求

SetWindowPos() 函数功能:该函数改变一个子窗口,弹出式窗口式顶层窗口的尺寸,位置和Z序。子窗口,弹出式窗口,及顶层窗口根据它们在屏幕上出现的顺序排序、顶层窗口设置的级别最高,并且被设置为Z序的第一个窗口。 函数原型:BOOL SetWindowPos(HWN hWnd,HWND hWndlnsertAfter,int X,int Y,int cx,int cy,UNIT.Flags); 参数: hWnd:窗口句柄。 hWndlnsertAfter:在z序中的位于被置位的窗口前的窗口句柄。该参数必须为一个窗口句柄,或下列值之一: HWND_BOTTOM:将窗口置于Z序的底部。如果参数hWnd标识了一个顶层窗口,则窗口失去顶级位置,并且被置在其他窗口的底部。 HWND_DOTTOPMOST:将窗口置于所有非顶层窗口之上(即在所有顶层窗口之后)。如果窗口已经是非顶层窗口则该标志不起作用。 HWND_TOP:将窗口置于Z序的顶部。 HWND_TOPMOST:将窗口置于所有非顶层窗口之上。即使窗口未被激活窗口也将保持顶级位置。

查看该参数的使用方法,请看说明部分。 x:以客户坐标指定窗口新位置的左边界。 Y:以客户坐标指定窗口新位置的顶边界。 cx:以像素指定窗口的新的宽度。 cy:以像素指定窗口的新的高度。

uFlags:窗口尺寸和定位的标志。该参数可以是下列值的组合: SWP_ASNCWINDOWPOS:如果调用进程不拥有窗口,系统会向拥有窗口的线程发出需求。这就防止调用线程在其他线程处理需求的时候发生死锁。 SWP_DEFERERASE:防止产生WM_SYNCPAINT消息。 SWP_DRAWFRAME:在窗口周围画一个边框(定义在窗口类描述中)。 SWP_FRAMECHANGED:给窗口发送WM_NCCALCSIZE消息,即使窗口尺寸没有改变也会发送该消息。如果未指定这个标志,只有在改变了窗口尺寸时才发送WM_NCCALCSIZE。 SWP_HIDEWINDOW;隐藏窗口。 SWP_NOACTIVATE:不激活窗口。如果未设置标志,则窗口被激活,并被设置到其他最高级窗口或非最高级组的顶部(根据参数hWndlnsertAfter设置)。 SWP_NOCOPYBITS:清除客户区的所有内容。如果未设置该标志,客户区的有效内容被保存并且在窗口尺寸更新和重定位后拷贝回客户区。 SWP_NOMOVE:维持当前位置(忽略X和Y参数)。 SWP_NOOWNERZORDER:不改变z序中的所有者窗口的位置。 SWP_NOREDRAW: 不重画改变的内容。如果设置了这个标志,则不发生任何重画动作。适用于客户区和非客户区(包括标题栏和滚动条)和任何由于窗回移动而露出的父窗口的所有部分。如果设置了这个标志,应用程序必须明确地使窗口无效并区重画窗口的任何部分和父窗口需要重画的部分。 SWP_NOREPOSITION;与SWP_NOOWNERZORDER标志相同。 SWP_NOSENDCHANGING:防止窗口接收WM_WINDOWPOSCHANGING消息。 SWP_NOSIZE:维持当前尺寸(忽略cx和Cy参数)。 SWP_NOZORDER:维持当前Z序(忽略hWndlnsertAfter参数)。 SWP_SHOWWINDOW:显示窗口。

返回值:如果函数成功,返回值为非零;如果函数失败,返回值为零。若想获得更多错误消息,请调用GetLastError函数。

问题41:关于CDC::DrawEdge()

CDC::DrawEdge

BOOL DrawEdge(LPRECT lpRect, UNIT nEdge, UNIT nFlags);

返回值:如果成功,则返回非零值,否则为0。

参数:

lpRect 指向包含有逻辑坐标矩形的RECT结构的指针。
nEdge 指定矩形内外边界的类型。该参数是内边界标志和外边界标志的集合。请参阅说明中该参数的类型。
nFlags 指定绘制边界的类型,请参阅说明中该参数的类型。

说明: 调用该成员函数,绘制指定风格和类型的矩形。 内外边界标志如下:

内边界标志: · BDR_RAISEDINNER 内边界凸出。 · BDR_SUNKENINNER 内边界凹下。
外边界标志: · BDR_RAISEDOUTER 外边界凸出。 · BDR_SUNKENOUTER 外边界凹下。

nEdge参数必须是内边界标志和外边界标志的组合。可以为以下值之一:

EDGE_BUMP BDR_RAISEDOUTER和BDR_SUNKENINNER的组合。
EDGE_ETCHED BDR_SUNKENOUTER和BDR_RAISEDINNER的组合。
EDGE_RAISED BDR_REISEDOUTER和BDR_RAISEDINNER的组合。
EDGE_SUNKEN BDR_SUNKENOUTER和BDR_SUNKENINNER的组合。

nFlags参数的类型如下:

BF_RECT 矩形的四周边界。
BF_LEFT 矩形的左边界。
BF_BOTTOM 矩形的底部边界。
BF_RIGHT 矩形的右边界。
BF_TOP 矩形的顶部边界。
BF_TOPLEFT 矩形的左、底部边界。
BF_TOPRIGHT 矩形的右、顶部边界。
BF_BOTTOMLEFT 矩形的左、底部边界。
BF_BOTTOMRIGHT 矩形的右、底部边界。

对于对角线,BF_RECT标志指定了矢量终点:

BF_DIAGONAL_ENDBOTTOMLEFT 对角线边界。终点为矩形的左下角,始点为右上角。
BF_DIAGONAL_ENDBOTTOMRIGHT 对角线边界。终点为矩形的右下角,始点为左下角。
BF_DIAGONAL_ENDTOPLEFT 对角线边界。终点为矩形的左上角,始点为右下角。
BF_DIAGONAL_ENDTOPRIGHT 对角线边界。终点为矩形的右上角,始点为左下角。

问题42:关于CDC &pDC->TabbedTextOutW()

void CMainWindow::OnPaint ()
{
	
	//获取DC
	CPaintDC dc(this);
	CString str = _T("姓名\t年龄\t性别\t学历\t爱好");
	int TabArray[4];
	TabArray[0] = 160;
	TabArray[1] = 250;
	TabArray[2] = 340;
	TabArray[3] = 430;
	CPoint point(100, 100);
	dc.TabbedTextOutW(point.x, point.y, 
                           str, 
                           sizeof(TabArray) / sizeof(TabArray[0]), 
                           TabArray,
                           point.x);
}

C++之MFC学习_第3张图片

CDC::TabbedTextOut
virtual CSize TabbedTextOut( int x, int y, LPCTSTR lpszString, int nCount, int nTabPositions, LPINT lpnTabStopPositions, int nTabOrigin );
​
CSize TabbedTextOut( int x, int y, const CString& str, int nTabPositions, LPINT lpnTabStopPositions, int nTabOrigin );

CDC::TabbedTextOut()函数是一个文本输出函数,它根据输出的字符串之间的制表符停止位数组值的设置,把一个字符串中包含的用制表符隔开的多个字符串,按指定的制表符宽度依次输出。主要用于格式化输出结果。

以上述的第二个函数原型为例:

其中,(1):x,y代表字符串输出的位置坐标。文章开头的代码中,x=100,y=100;

(2):str代表要输出的字符串。其中以\t制表符隔开不同的子字符串。前面的代码中,

CString str = _T("姓名\t年龄\t性别\t学历\t爱好");

(3): 其中,nTabPositions 和 lpnTabStopPositions两个参数是相关的。lpnTabStopPositions代表数组指针,nTabPositions 代表这个数组的长度(或大小)。在上述代码中:

lpnTabStopPositions就是TabArray[]数组,值分别为:

int TabArray[4];
    TabArray[0] = 160;
    TabArray[1] = 250;
    TabArray[2] = 340;
    TabArray[3] = 430;

nTabPositions 代表TabArray[]的长度,nTabPositions =sizeof(TabArray) / sizeof(TabArray[0]),值为4。

(4):nTabOrigin则代表所设制表位宽度从什么地方开始计算。通常保持与输出字符串开始的点的横坐标一致。

怎么理解这个数组所设置的制表位的宽度呢?是不是代表输出的字符串中各个子字符串之间的距离呢?答案是否定的!!! 下面的图示直观反映它的真正含义: C++之MFC学习_第4张图片

由上图所示:设置的制表位值代表的是从指定开始的位置(nTabOrigin 指定,通常与字符串输出开始的坐标点的横坐标相同)计算宽度值。

如上图中,TabArray[0]=160代表从坐标点(100,100)开始,输出的“姓名”的宽度,加上其后的空白宽度,到“年龄”之前一个像素为止,宽度为160.

TabArray[1]=250还是从坐标点(100,100)开始,到输出的“性别”之前的宽度。

后面以此类推……

TabArray[]中的每个宽度值,都是累计计算的,都从指定的横坐标nTabOrigin开始计算的(前述代码中nTabOrigin =x)。因此,后面的值总是要大于前面的值。输出的子字符串之间的:间隔=(设置的宽度值)-(子字符串的宽度)-(累计宽度值) 。如:

姓名与年龄之间的间隔=160 -(姓名)宽度-0(累计宽度)。

年龄与性别之间的间隔=250 -(年龄)宽度 -160(累计宽度)

……

明白了设置的制表宽度值的含义后,就能正确使用TabbedTextOut()函数了。

如果最后一个参数nTabOrigin 不是像上述代码中那样等于x(字符串输出的坐标点的横坐标),而是nTabOrigin =0。

TabArray[]中设置的宽度值又是怎样计算的呢?

其实道理是一样的,只不过制表位宽度值开始计算的参照位置由横坐标100变为横坐标为0而已。

其输出结果及相互之间的空白距离,应该如下:

100个像素+姓名+空白宽度(年龄之前)=160;

100个像素+姓名+空白宽度+年龄+空白宽度(性别之前)=250;

……

以此类推。程序输出结果如下图:

C++之MFC学习_第5张图片

与前面的输出结果相比:由于计算制表位值的开始点由100像素移到了0,而字符串输出又是从第100个像素开始的,这就造成在“姓名”之前的空白占了100个像素,导致“姓名”与“年龄”之间的空白宽度大大缩小,而年龄到爱好等字符串之间的距离保持不变,没有受到影响。这个变化主要是宽度值是“累计计算”的结果。

问题43:关于CDC &pDC->ExtTextOutW()

void MyMainWindow::DrawInputText(CDC* pDC) {
	pDC->ExtTextOutW(m_ptTextOrigin.x, m_ptTextOrigin.y,
		ETO_OPAQUE, m_rcTextBox, m_strInputText, NULL);
}
//声明:
ExtTextOut(
  DC: HDC;          {设备环境句柄}
  X, Y: Integer;    {起点坐标}
  Options: Longint; {选项}
  Rect: PRect;      {指定显示范围; 0 表示限制范围}
  Str: PChar;       {字符串指针}
  Count: Longint;   {字符串长度}
  Dx: PInteger      {表示字符间距的数组; 是可选值}
): BOOL;

//Options 参数可选值:
ETO_OPAQUE         = 2;     {输出前, 用当前背景色填充矩形}
ETO_CLIPPED        = 4;     {剪切输出, 只输出矩形范围内的文本}
ETO_GLYPH_INDEX    = $10;   {}
ETO_RTLREADING     = $80;   {}
ETO_NUMERICSLOCAL  = $400;  {}
ETO_NUMERICSLATIN  = $800;  {}
ETO_IGNORELANGUAGE = $1000; {}
ETO_PDY            = $2000; {}


//举例:
procedure TForm1.FormPaint(Sender: TObject);
var
  MyRect: TRect;
const
  str = 'Delphi';
  arr: array[0..4] of Integer = (10,20,30,40,50);
begin
  MyRect := Rect(10,10,200,200);
  ExtTextOut(Canvas.Handle, 50, 50, ETO_CLIPPED, @MyRect, str, Length(str), @arr);
end;

问题44:关于LOWORD与HIWORD

C++ 常用WinDef 宏函数LOWORD和HIWORD

取WORD高字节和低字节

#define LOWORD(l)           ((WORD)((DWORD_PTR)(l) & 0xffff))
#define HIWORD(l)           ((WORD)((DWORD_PTR)(l) >> 16))

问题45 关于GetMessagePos

GetMessagePos函数返回一个长的值,它给出了屏幕坐标中的光标位置。此位置是由GetMessage功能检索到的最后一条消息时光标所占据的位置。

DWORD GetMessagePos(VOID)

参数

此函数无参数。

返回值

返回值指定光标位置的x坐标和y坐标。x坐标在LOWORD中,y坐标在HIWORD中。

备注

如上所述,x坐标在返回值的低位字中; y坐标在高位字中。如果将返回值分配给变量,则可以使用MAKEPOINTS宏从返回值获取POINTS结构。您还可以使用LOWORD或HIWORD宏来提取x坐标或y坐标。

要确定光标的当前位置,而不是上次发生消息时的位置,请使用GetCursorPos功能。

问题46:关于ScreenToClient

ScreenToClient函数将屏幕上指定点的屏幕坐标转换为客户端坐标。

BOOL ScreenToClient(

HWND 【的hWnd】, //源代码坐标的窗口句柄
LPPOINT 【LPPOINT】 //包含坐标结构的地址
);

参数

【的hWnd】

识别客户端区域将用于转换的窗口。

【LPPOINT】

指向包含要转换的屏幕坐标的POINT结构。

返回值

如果函数成功,返回值不为零。

如果函数失败,返回值为零。

备注

该函数使用由【的hWnd】参数标识的窗口和POINT结构中给出的屏幕坐标来计算客户端坐标。然后用客户端坐标代替屏幕坐标。新坐标相对于指定窗口的客户区域的左上角。

ScreenToClient功能假定指定点在屏幕坐标中。

问题47:MFC键盘和光标还有鼠标的使用

h文件:

​
// VisualKB.h: VisualKB 应用程序的主头文件
//
#pragma once
​
#ifndef __AFXWIN_H__
#error "在包含此文件之前包含 'pch.h' 以生成 PCH"
#endif
​
#include "resource.h"       // 主符号
#define MAX_STRINGS 12
​
// CMyApp:
// 有关此类的实现,请参阅 VisualKB.cpp
//
​
class CMyApp : public CWinApp
{
public:
    CMyApp() noexcept;
​
​
    // 重写
public:
    virtual BOOL InitInstance();
    virtual int ExitInstance();
};
​
class MyMainWindow :public CWnd
{
protected:
    int m_cxChar;
    int m_cyChar;
    int m_cyLine;
    int m_nTextPos;
    int m_nTabStops[7];
    int m_nTextLimit;
    int m_nMsgPos;
​
    HCURSOR m_hCursorArrow;
    HCURSOR m_hCursorIBeam;
​
    CPoint m_ptTextOrigin;
    CPoint m_ptHeaderOrigin;
    CPoint m_ptUpperMsgOrigin;
    CPoint m_ptLowerMsgOrigin;
    CPoint m_ptCaretPos;
​
    CRect m_rcTextBox;
    CRect m_rcTextBoxBorder;
    CRect m_rcMsgBoxBorder;
    CRect m_rcScroll;
​
    CString m_strInputText;
    CString m_strMessages[MAX_STRINGS];
​
public:
    MyMainWindow();
​
protected:
    int GetNearestPos(CPoint point);
    void PositionCaret(CDC* pDC = NULL);
    void DrawInputText(CDC* pDC);
    void ShowMessage(LPCTSTR pszMessage, UINT nChar, UINT nRepCnt, UINT nFlags);
    void DrawMessageHeader(CDC* pDC);
    void DrawMessages(CDC* pDC);
​
protected:
    virtual void PostNcDestroy();
​
​
public:
    DECLARE_MESSAGE_MAP()
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnPaint();
    afx_msg void OnSetFocus(CWnd* pOldWnd);
    afx_msg void OnKillFocus(CWnd* pNewWnd);
    afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
    afx_msg void OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags);
    afx_msg void OnSysKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
    afx_msg void OnSysKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags);
    afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
    afx_msg void OnSysChar(UINT nChar, UINT nRepCnt, UINT nFlags);
};
​
​
extern CMyApp theApp;
​

cpp文件

​
// VisualKB.cpp: 定义应用程序的类行为。
//
​
#include "pch.h"
#include "framework.h"
#include "afxwinappex.h"
#include "afxdialogex.h"
#include "VisualKB.h"
#include "MainFrm.h"
​
​
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
​
​
// CMyApp 构造
​
CMyApp::CMyApp() noexcept
{
    SetAppID(_T("VisualKB.AppID.NoVersion"));
}
​
// 唯一的 CMyApp 对象
​
CMyApp theApp;
​
​
// CMyApp 初始化
​
BOOL CMyApp::InitInstance()
{
    CWinApp::InitInstance();
    EnableTaskbarInteraction(FALSE);
    m_pMainWnd = new MyMainWindow;
    m_pMainWnd->ShowWindow(m_nCmdShow);
    m_pMainWnd->UpdateWindow();
    return TRUE;
}
​
int CMyApp::ExitInstance()
{
    return CWinApp::ExitInstance();
}
​
​
// CMyApp 消息处理程序
BEGIN_MESSAGE_MAP(MyMainWindow, CWnd)
    ON_WM_CREATE()
    ON_WM_PAINT()
    ON_WM_SETFOCUS()
    ON_WM_KILLFOCUS()
    ON_WM_SETCURSOR()
    ON_WM_LBUTTONDOWN()
    ON_WM_KEYDOWN()
    ON_WM_KEYUP()
    ON_WM_SYSKEYDOWN()
    ON_WM_SYSKEYUP()
    ON_WM_CHAR()
    ON_WM_SYSCHAR()
END_MESSAGE_MAP()
​
MyMainWindow::MyMainWindow() {
    m_nTextPos = 0;
    m_nMsgPos = 0;
​
    m_hCursorArrow = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
    m_hCursorIBeam = AfxGetApp()->LoadStandardCursor(IDC_IBEAM);
    //注册窗口
    CString strWndClass = AfxRegisterWndClass(0, NULL, (HBRUSH)(COLOR_3DFACE + 1), AfxGetApp()->LoadStandardIcon(IDI_WINLOGO));
    //创建窗口
    CreateEx(0, strWndClass, _T("Visual Keyboard"), WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL);
}
​
​
int MyMainWindow::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    // TODO:  在此添加您专用的创建代码
    if (CWnd::OnCreate(lpCreateStruct) == -1)
        return -1;
​
    CClientDC dc(this);
​
    //获取字体大小信息
    TEXTMETRIC tm;
    dc.GetTextMetrics(&tm);
    m_cxChar = tm.tmAveCharWidth;//单个字符的加权平均长度
    m_cyChar = tm.tmHeight;//单个字符的高度
    m_cyLine = tm.tmHeight + tm.tmExternalLeading;//单个字符高度+两行字符间的行距
​
    //设置文本输入框的位置和尺寸信息
    m_rcTextBoxBorder.SetRect(16, 16, (m_cxChar * 64) + 16, (m_cyChar * 3) / 2 + 16);
    m_rcTextBox = m_rcTextBoxBorder;
    m_rcTextBox.InflateRect(-2, -2);
​
    //设置消息显示卡的位置和尺寸信息
    m_rcMsgBoxBorder.SetRect(16, (m_cyChar * 4) + 16, (m_cxChar * 64) + 16, (m_cyLine * MAX_STRINGS) + (m_cyChar * 6) + 16);
​
    //设置的滚动显示按键信息框的位置
    m_rcScroll.SetRect(m_cxChar + 16, (m_cyChar * 6) + 16, (m_cxChar * 63) + 16, (m_cyLine * MAX_STRINGS) + (m_cyChar * 5) + 16);
​
​
    m_ptTextOrigin.x = m_cxChar + 16;
    m_ptTextOrigin.y = (m_cyChar / 4) + 16;
    m_ptCaretPos = m_ptTextOrigin;
    m_nTextLimit = (m_cxChar * 63) + 16;
​
    m_ptHeaderOrigin.x = m_cxChar + 16;
    m_ptHeaderOrigin.y = (m_cyChar) * 3 + 16;
​
    m_ptUpperMsgOrigin.x = m_cxChar + 16;
    m_ptUpperMsgOrigin.y = (m_cyChar * 5) + 16;
​
​
    m_ptLowerMsgOrigin.x = m_cxChar + 16;
    m_ptLowerMsgOrigin.y = (m_cyChar * 5) + (m_cyLine * (MAX_STRINGS - 1)) + 16;
​
    m_nTabStops[0] = (m_cxChar * 24) + 16;
    m_nTabStops[1] = (m_cxChar * 30) + 16;
    m_nTabStops[2] = (m_cxChar * 36) + 16;
    m_nTabStops[3] = (m_cxChar * 42) + 16;
    m_nTabStops[4] = (m_cxChar * 46) + 16;
    m_nTabStops[5] = (m_cxChar * 50) + 16;
    m_nTabStops[6] = (m_cxChar * 54) + 16;
​
    //设置窗口尺寸
    CRect rect(0, 0, m_rcMsgBoxBorder.right + 16, m_rcMsgBoxBorder.bottom + 16);
​
    CalcWindowRect(&rect);
    SetWindowPos(NULL, 0, 0, rect.Width(), rect.Height(), SWP_NOZORDER | SWP_NOMOVE | SWP_NOREDRAW);
    return 0;
}
​
void MyMainWindow::PostNcDestroy() {
    delete this;
}
​
void MyMainWindow::OnPaint()
{
    CPaintDC dc(this); // device context for painting
​
    //设置文本框和消息框的边界样式
    dc.DrawEdge(m_rcTextBoxBorder, EDGE_SUNKEN, BF_RECT);
    dc.DrawEdge(m_rcMsgBoxBorder, EDGE_SUNKEN, BF_RECT);
​
    DrawInputText(&dc);
    DrawMessageHeader(&dc);
    DrawMessages(&dc);
}
​
​
//将插入符设置为2或者调用GetSystemMetrics(SM_CXBORDER)返回的SM_CXBORDER值,哪个大选哪个
void MyMainWindow::OnSetFocus(CWnd* pOldWnd)
{
    CreateSolidCaret(max(2, ::GetSystemMetrics(SM_CXBORDER)), m_cyChar);
    SetCaretPos(m_ptCaretPos);
    ShowCaret();
}
​
//隐藏插入符,保存当前的插入符以便下次使用
void MyMainWindow::OnKillFocus(CWnd* pNewWnd)
{
    HideCaret();
    m_ptCaretPos = GetCaretPos();
    ::DestroyCaret();
}
​
//设置光标样式
BOOL MyMainWindow::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
    if (nHitTest == HTCLIENT) {
        DWORD dwpos = ::GetMessagePos();//获取屏幕坐标中光标的位置,由GetMessage功能检索到的最后一条消息时光标所占的位置
        CPoint point(LOWORD(dwpos), HIWORD(dwpos));
        //ScreenToClient函数将屏幕上指定点的屏幕坐标转换为客户端坐标。
​
        //  BOOL ScreenToClient(
​
        //      HWND 【的hWnd】,   //源代码坐标的窗口句柄
        //      LPPOINT 【LPPOINT】   //包含坐标结构的地址
        //  );
​
        //参数
​
        //  【的hWnd】
​
        //  识别客户端区域将用于转换的窗口。
​
        //  【LPPOINT】
​
        //  指向包含要转换的屏幕坐标的POINT结构。
​
        //返回值
​
        //  如果函数成功,返回值不为零。
​
        //  如果函数失败,返回值为零。
        ScreenToClient(&point);
        //判断点是否位与矩形内,如果是光标就显示位Beam形状,否则就是箭头形状
        ::SetCursor(m_rcTextBox.PtInRect(point) ? m_hCursorIBeam : m_hCursorArrow);
        return TRUE;
    }
    return CWnd::OnSetCursor(pWnd, nHitTest, message);
}
​
​
void MyMainWindow::OnLButtonDown(UINT nFlags, CPoint point)
{
    if (m_rcTextBox.PtInRect(point)) {
        m_nTextPos = GetNearestPos(point);//点击左键之后,插入符号的位置发生变化,读取此时光标附近位置处作为插入符的位置
        PositionCaret();
    }
}
​
​
void MyMainWindow::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
    ShowMessage(_T("WM_KEYDOWN"), nChar, nRepCnt, nFlags);
    switch (nChar)
    {
    case VK_LEFT:
        if (m_nTextPos != 0) {
            m_nTextPos--;
            PositionCaret();
        }
        break;
    case VK_RIGHT:
        if (m_nTextPos != m_strInputText.GetLength()) {
            m_nTextPos++;
            PositionCaret();
        }
        break;
    case VK_HOME:
        m_nTextPos = 0;
        PositionCaret();
        break;
    case VK_END:
        m_nTextPos = m_strInputText.GetLength();
        PositionCaret();
        break;
    }
}
​
​
void MyMainWindow::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags)
{
    ShowMessage(_T("WM_KEYUP"), nChar, nRepCnt, nFlags);
    CWnd::OnKeyUp(nChar, nRepCnt, nFlags);
}
​
​
void MyMainWindow::OnSysKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
    ShowMessage(_T("WM_SYSKEYDOWN"), nChar, nRepCnt, nFlags);
    CWnd::OnSysKeyDown(nChar, nRepCnt, nFlags);
}
​
​
void MyMainWindow::OnSysKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags)
{
​
    ShowMessage(_T("WM_SYSKEYUP"), nChar, nRepCnt, nFlags);
    CWnd::OnSysKeyUp(nChar, nRepCnt, nFlags);
}
​
​
void MyMainWindow::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
​
    ShowMessage(_T("WM_CHAR"), nChar, nRepCnt, nFlags);
    CClientDC dc(this);
    switch (nChar)
    {
    case VK_ESCAPE:
    case VK_RETURN:
        m_strInputText.Empty();
        m_nTextPos = 0;
        break;
    case VK_BACK://如果按下的是删除键,并且此时插入符的位置不在末尾,则只剩下左边的内容和右边的内容,并且光标位置左移
        if (m_nTextPos != 0) {
            m_strInputText = m_strInputText.Left(m_nTextPos - 1) +
                m_strInputText.Right(m_strInputText.GetLength() -
                    m_nTextPos);
            m_nTextPos--;
        }
        break;
    default://如果数入的是正常的符号则正常流转到下一个环节
        if ((nChar >= 0) && (nChar <= 31))
            return;
        //如果此时光标的位置就在显示字符的最后一位,则正常插入,显示内容也拼接上输入的字符
        if (m_nTextPos == m_strInputText.GetLength())
        {
            m_strInputText += (char)nChar;
            m_nTextPos++;
        }
        else
        {//否则的话则在插入符所在的位置插入字符,并记录插入符的位置+1
            m_strInputText.SetAt(m_nTextPos++, nChar);
        }
        //如果插入后的字符大于了输入框的范围,则重置插入符的位置,但是显示的字符不变
        CSize size = dc.GetTextExtent(m_strInputText, m_strInputText.GetLength());
        if ((m_ptTextOrigin.x + size.cx) > m_nTextLimit) {
            m_strInputText = (char)nChar;
            m_nTextPos = 1;
        }
        break;
    }
    HideCaret();
    DrawInputText(&dc);
    //更新插入符的位置
    PositionCaret(&dc);
    ShowCaret();
}
​
​
void MyMainWindow::OnSysChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
​
    ShowMessage(_T("WM_SYSCHAR"), nChar, nRepCnt, nFlags);
    CWnd::OnSysChar(nChar, nRepCnt, nFlags);
}
​
​
void MyMainWindow::PositionCaret(CDC* pDC) {
​
    BOOL bRealease = FALSE;
​
    if (pDC == NULL)
    {
        pDC = GetDC();
        bRealease = TRUE;
    }
    CPoint point = m_ptTextOrigin;
    //获取输入框中插入符左边的字符
    CString string = m_strInputText.Left(m_nTextPos);
    //插入符的位置更新
    point.x += (pDC->GetTextExtent(string, string.GetLength())).cx;
    SetCaretPos(point);
​
    if (bRealease)
        ReleaseDC(pDC);
}
int MyMainWindow::GetNearestPos(CPoint point)
{
​
    if (point.x <= m_ptTextOrigin.x)
        return 0;
​
    CClientDC dc(this);
    int nLen = m_strInputText.GetLength();
    //如果输入框原点的横坐标+输入框中的文字的长度(实际就是输入框中最后一个字符的与原点之间的差值)
    if (point.x >= (m_ptTextOrigin.x + (dc.GetTextExtent(m_strInputText, nLen)).cx))
        return nLen;
​
    int i = 0;
    int nPrevChar = m_ptTextOrigin.x;
    int nNextChar = m_ptTextOrigin.x;
    //循环去逼近光标的位置,
    while (nNextChar < point.x)
    {
        i++;
        nPrevChar = nNextChar;
        /*CDC::GetTextExtent
        CSize GetTextExtent(LPCTSTR lpszString, int nCount) const;
        CSize GetTextExtent(const CString & str) const;
        返回值:CSize对象中字符串的尺寸信息。
            参数: lpszString 字符串指针。可以为该参数传递CString对象。
            nCount 字符串中的字符数。
            str 包含指定字符的CString对象。*/
        nNextChar = m_ptTextOrigin.x + (dc.GetTextExtent(m_strInputText.Left(i)//左边i个字符
            , i)).cx;
    }
    //光标是否处于两个字符之间
    return((point.x - nPrevChar) < (nNextChar - point.x) ? i - 1 : i);
}
​
//设置输入文本框
void MyMainWindow::DrawInputText(CDC* pDC) {
    pDC->ExtTextOutW(m_ptTextOrigin.x, m_ptTextOrigin.y,
        ETO_OPAQUE, m_rcTextBox, m_strInputText, NULL);
}
​
void MyMainWindow::ShowMessage(LPCTSTR pszMessage, UINT nChar, UINT nRepCnt, UINT nFlags) {
​
    CString string;
    string.Format(_T("%s\t %u\t %u\t %u\t %u\t %u\t %u\t %u"), pszMessage, nChar, nRepCnt, nFlags & 0xFF,
        (nFlags >> 8) & 0x01,
        (nFlags >> 13) & 0x01,
        (nFlags >> 14) & 0x01,
        (nFlags >> 15) & 0x01);
​
    ScrollWindow(0, -m_cyLine, &m_rcScroll);
    ValidateRect(m_rcScroll);
​
    CClientDC dc(this);
    dc.SetBkColor((COLORREF)::GetSysColor(COLOR_3DFACE));
​
    m_strMessages[m_nMsgPos] = string;
    dc.TabbedTextOutW(m_ptLowerMsgOrigin.x, m_ptLowerMsgOrigin.y, m_strMessages[m_nMsgPos], m_strMessages[m_nMsgPos].GetLength(),
        sizeof(m_nTabStops), m_nTabStops, m_ptLowerMsgOrigin.x);
​
    if (++m_nMsgPos == MAX_STRINGS)
        m_nMsgPos = 0;
​
}
​
void MyMainWindow::DrawMessageHeader(CDC* pDC)
{
    static CString string = _T("Message\tChar\tRep\tScan\tExt\tCon\tPrv\tTran");
    pDC->SetBkColor((COLORREF)::GetSysColor(COLOR_3DFACE));
    //以制表符为标志停止位,用以格式化显示。
    pDC->TabbedTextOutW(m_ptHeaderOrigin.x, m_ptHeaderOrigin.y,
        string, string.GetLength(), sizeof(m_nTabStops), m_nTabStops, m_ptHeaderOrigin.x);
}
​
//最下面的消息框可以在oncreate的时候就显示出来了。
void MyMainWindow::DrawMessages(CDC* pDC)
{
    int nPos = m_nMsgPos;
    pDC->SetBkColor((COLORREF)::GetSysColor(COLOR_3DFACE));
​
    for (int i = 0; i < MAX_STRINGS; i++)
    {
        pDC->TabbedTextOutW(m_ptUpperMsgOrigin.x,
            m_ptUpperMsgOrigin.y + (m_cyLine * i),
            m_strMessages[nPos], m_strMessages[nPos].GetLength(),
            sizeof(m_nTabStops), m_nTabStops, m_ptUpperMsgOrigin.x);
​
        if (++nPos == MAX_STRINGS)
            nPos = 0;
    }
}
​
​

问题48:关于CWnd::MeasureItem()

CWnd::OnMeasureItem

afx_msg void OnMeasureItem( int nIDCtl,  LPMEASUREITEMSTRUCT lpMeasureItemStruct );

参数:

nIDCtl 控件的ID。
lpMeasureItemStruct 指向一个MEASUREITEMSTRUCT数据结构,其中包含自画控件的大小。

说明: 当控件被创建的时候,框架为自画按钮、组合框、列表框或菜单项调用这个成员函数。 重载这个函数并填充lpMeasureItemStruct指向的MEASUREITEMSTRUCT数据结构,然后返回;这将通知Windows控件的大小,并使Windows能够正确地处理控件的用户交互。 如果列表框或组合框是用LBSOWNERDRAWVARIABLE或CBSOWNERDRAWVARIA_BLE风格创建的,则框架为控件中的每一个项调用这个函数;否则这个函数只被调用一次。 在发送WM_INITDIALOG消息之前,Windows为用OWNERDRAWFIXED风格创建的组合框和列表框的拥有者发出对OnMeasureItem的调用。其结果是,当拥有者接收到这个调用时,Windows还没有确定在控件中使用的字体的高度和宽度;需要这些值的函数调用和计算应该发生在应用程序或库的主函数中。 如果要测量的的项是CMenu,CListBox或CComboBox对象,则将调用适当的类的虚函数MeasureItem。重载适当的控件类的MeasureItem成员函数以计算并设置每个项的大小。 仅当控件类是在运行时创建,或者它是用LBS_OWNERDRAWVARIABLE或CBS_OWNERDRAWVARIABLE风格创建的时候,OnMeasureItem才会被调用。这是因为WM_MEASUREITEM消息时在控件创建过程的早期被发送的。 如果你使用DDX_Control,SubclassDlgItem或SubclassWindow进行了子类化,则子类化过程通常发生在创建过程之后。因此,在控件的OnChildNotify函数中无法处理WM_MEASUREITEM消息,这是MFC用来实现ON_WM_MEASUREITEM_REFLECT的机制。 注意 框架调用这个成员函数以允许你的应用程序处理一个Windows消息。传递给你的成员函数的参数反映了接收到消息时框架接收到的参数。如果你调用了这个函数的基类实现,则该实现将使用最初传递给消息的参数(而不是你提供给这个函数的参数)。

问题49:关于GetObject()

GetObject函数获取有关指定图形对象的信息。根据图形对象,该函数将填充BITMAP,DIBSECTION,EXTLOGPEN,LOGBRUSH,LOGFONT或LOGPEN结构或表条目数(对于逻辑调色板),进入指定的缓冲区。

int GetObject(

HGDIOBJ 【hgdiobj】, //处理感兴趣的图形对象
INT 【cbBuffer】, //对象信息的缓冲区大小
LPVOID 【lpvObject】 //指向缓冲区的对象信息的指针
);

参数

【hgdiobj】

感兴趣的图形对象的句柄。这可以是以下之一的句柄:通过调用CreateDIBSection函数创建的逻辑位图,画笔,字体,调色板,笔或与设备无关的位图。

【cbBuffer】

指定要写入缓冲区的信息的字节数。

【lpvObject】

指向要接收有关指定图形对象的信息的缓冲区。

下表显示了您可以使用【hgdiobj】指定的每种图形对象类型的缓冲区接收的信息类型:

*【hgdiobj类型】* 数据写为* *【lpvObject】*
HBITMAP BITMAP
HBITMAP从致电CreateDIBSection DIBSECTION,如果的sizeof设置为的sizeofDIBSECTION)或BITMAP,如果【cbBuffer】设置为的sizeofBITMAP
HPALETTE WORD逻辑调色板中的条目数
HPEN从致电ExtCreatePen EXTLOGPEN
HPEN LOGPEN
HBRUSH LOGBRUSH
HFONT LOGFONT

如果【lpvObject】参数为NULL,则函数返回值是将其写入指定图形对象的缓冲区的信息所需的字节数。

返回值

如果函数成功,并且【lpvObject】是有效的指针,则返回值是存储到缓冲区中的字节数。

如果函数成功,并且【lpvObject】为NULL,则返回值是保存函数将存储到缓冲区中的信息所需的字节数。

如果函数失败,返回值为零。要获取扩展错误信息,请调用GetLastError.

备注

【lpvObject】参数指向的缓冲区必须足够大以接收有关图形对象的信息。

如果【hgdiobj】标识通过调用CreateDIBSection创建的位图,并且指定的缓冲区足够大,则GetObject函数返回DIBSECTION结构。此外,BITMAP {BITMAP} {BITMAP}中{BITMAP}结构中的BITMAP成员将包含指向位图的位值的指针。

如果【hgdiobj】识别出任何其他方式创建的位图,GetObject仅返回位图的宽度,高度和颜色格式信息。您可以通过调用GetDIBitsGetBitmapBits函数来获取位图的位值。

如果【hgdiobj】标识一个逻辑调色板,GetObject将检索一个两个字节的整数,指定调色板中的条目数。该函数不检索定义调色板的LOGPALETTE结构。要检索有关调色板条目的信息,应用程序可以调用GetPaletteEntries函数。

问题50:关于CDC::CreateCompatibleDC()

CDC::CreateCompatibleDC
​
virtual BOOL CreateCompatibleDC(CDC*  pDC)
​
返回值:如果成功,则返回非零值,否则为0。

参数:

pDC 设备上下文的指针。如果pDC为NULL,函数将产生与系统显示兼容的内存设备上下文。

说明: 产生与pDC指定设备兼容的设备上下文内存,设备上下文内存包含显示表面的信息,它用于在向实际的兼容设备表面发送图象之前在内存中作好准备。 当创建设备内存上下文时,GDI自动选择单色存储位图格式。只有在位图已被创建并被选入设备上下文之中时,才使用GDI输出函数。 该函数仅用于创建与支持光栅操作的设备上下文。可参阅 CDC::BitBlt成员函数,了解设备上下文之间的位块传输。要决定设备是否支持光栅操作,可参阅成员函数CDC::GetDeviceCaps 中RC_BITBLT的光栅兼容性

if (lpDrawItemStruct->itemState & ODS_CHECKED)
    {
        CDC dcMem;
        //创建一个内存设备描述表,内存中有一个虚拟显示平面,同屏幕或其他输出设备一样,用户可以在上面绘画
        //CreateCompatibleDC创建了一个内存dc,windows不允许直接将位图“位块传送”到显示表面,因此必须先把位图选入内存DC,并将之复制到屏幕dc中
        dcMem.CreateCompatibleDC(&dc);
        CBitmap* pOldBitmap = dcMem.SelectObject(&bitmap);
​
        dc.BitBlt(lpDrawItemStruct->rcItem.left + 4, lpDrawItemStruct->rcItem.top +
            ((lpDrawItemStruct->rcItem.bottom - lpDrawItemStruct->rcItem.top) - bm.bmHeight) / 2,
            bm.bmWidth, bm.bmHeight, &dcMem, 0, 0, SRCCOPY);
​
        dcMem.SelectObject(pOldBitmap);
    }
​

问题51:关于消息程序删除设备表和dc的绑定

    //根据选中的菜单项的id获取颜色,并绘制对应颜色填充的矩形条,表示颜色
    UINT itemID = lpDrawItemStruct->itemID & 0xFFFF;//清楚低位,只保留高位
    pbrush = new CBrush(m_wndView.m_clrColors[itemID - ID_COLOR_RED]);
    CRect rect = lpDrawItemStruct->rcItem;
    rect.DeflateRect(6, 4);
    rect.left += bm.bmWidth;
    dc.FillRect(rect, pbrush);
    delete pbrush;
    dc.Detach();

问题52:关于CPtrArray与CObArray

问题53:CArchive的串行化和并行化过程

问题54:关于应用程序字体的读取

void CMainWindow::FillListBox() {
    m_wndListBox.ResetContent();
​
    CClientDC dc(this);
    ::EnumFontFamilies((HDC)dc, NULL, (FONTENUMPROC)EnumFontFamProc, (LPARAM)this);
}
​
int CALLBACK CMainWindow::EnumFontFamProc(ENUMLOGFONT* lpelf, NEWTEXTMETRIC* lpntm, int nFontType, LPARAM lParam)
{
    CMainWindow* pWnd = (CMainWindow*)lParam;
​
    if ((pWnd->m_wndCheckBox.GetCheck() == BST_UNCHECKED ||
        (nFontType & TRUETYPE_FONTTYPE))) {
        pWnd->m_wndListBox.AddString(lpelf->elfLogFont.lfFaceName);
    }
    return 1;
}

1.EnumFontFamilies函数枚举指定设备上可用的指定字体系列中的字体。此函数取代EnumFonts功能。

int EnumFontFamilies(

HDC 【HDC】, //处理设备控制
LPCTSTR 【lpszFamily】, //指向family-name字符串的指针
FONTENUMPROC 【lpEnumFontFamProc】, //指向回调函数的指针
LPARAM 【lParam的】 //应用程序提供的数据的地址
);

参数

【HDC】

标识设备上下文。

【lpszFamily】

指向指定所需字体的家族名称的以null结尾的字符串。如果【lpszFamily】为NULL,则EnumFontFamilies随机选择并枚举每个可用类型族的一种字体。

【lpEnumFontFamProc】

指定应用程序定义的回调函数的过程实例地址。有关回调函数的信息,请参阅EnumFontFamProc功能。

【lParam的】

指向应用程序提供的数据。将数据与字体信息一起传递给回调函数。

返回值

如果函数成功,返回值是回调函数返回的最后一个值。其含义是具体实现。

备注

EnumFontFamilies功能与EnumFonts功能不同之处在于它检索与TrueType字体相关联的样式名称。使用EnumFontFamilies,可以检索关于使用EnumFonts功能无法枚举的异常字体样式(例如Outline)的信息。基于Win32的应用程序应使用EnumFontFamilies而不是EnumFonts.

对于具有由【lpszFamily】参数指定的字体名称的每个字体,EnumFontFamilies函数将检索有关该字体的信息,并将其传递给【lpEnumFontFamProc】参数指向的函数。应用程序定义的回调函数可以根据需要处理字体信息。枚举继续,直到没有更多的字体或回调函数返回零。

2.EnumFontFamProc函数是一个应用程序定义的回调函数,用于检索描述可用字体的数据。

int CALLBACK EnumFontFamProc(

ENUMLOGFONT FAR **【lpelf】*, //指向逻辑字体数据的指针
NEWTEXTMETRIC FAR **【lpntm】*, //指向物理字体数据的指针
INT 【FontType】, //字体类型
LPARAM 【lParam的】 //应用程序定义数据的地址
);

参数

【lpelf】

指向包含有关字体逻辑属性的信息的ENUMLOGFONT结构。此结构在本地定义。

【lpntm】

指向NEWTEXTMETRIC结构,其中包含有关字体的物理属性的信息,如果该字体是TrueType字体。如果字体不是TrueType字体,则此参数指向TEXTMETRIC结构。

【FontType】

指定字体的类型。此参数可以是以下值的组合:

DEVICE_FONTTYPE RASTER_FONTTYPE TRUETYPE_FONTTYPE

【lParam的】

指向由EnumFontFamilies函数传递的应用程序定义的数据。

返回值

返回值必须是非零值才能继续枚举;要停止枚举,它必须返回零。

备注

应用程序必须通过将其地址传递给EnumFontFamilies函数来注册此回调函数。

EnumFontFamProc函数是应用程序定义的函数名称的占位符。

AND(&)运算符可以与RASTER_FONTTYPE,DEVICE_FONTTYPE和TRUETYPE_FONTTYPE常量一起使用,以确定字体类型。如果设置了RASTER_FONTTYPE位,则字体为栅格字体。如果TRUETYPE_FONTTYPE位被设置,字体是TrueType字体。如果没有设置位,则该字体是矢量字体。当设备(例如激光打印机)支持下载TrueType字体或字体是设备驻留字体时,设置DEVICE_FONTTYPE;如果设备是显示适配器,点阵式打印机或其他光栅设备,则为零。应用程序还可以使用DEVICE_FONTTYPE来区分图形设备接口(GDI) - 提供的栅格字体与设备提供的字体。GDI可以模拟GDI提供的栅格字体的粗体,斜体,下划线和删除属性,但不能对设备提供的字体进行模拟。

问题55:关于LPDRAWITEMSTRUCT

DRAWITEMSTRUCT结构具有如下形式:
typedef struct tagDRAWITEMSTRUCT
{
  UINT  CtlType;
  UINT  CtlID;
  UINT  itemlD;
  UINT  itemAction;
  UINT  itemState;
  HWND  hwndltem;
  HDC   hDC;
  RECT  rcItem;
  DWORD itemData;
} DRAWITEMSTRUCT;

注释: DRAWITEMSTRUCT结构提供了控制属主决定如何绘制其控制所需要的信息。 该控制的属主通过WM_DRAWITEM消息的lParam参数来获取指向此结构的指针

成员: CtlType 控件类型。控件类型的取值如下:

  1. · ODT_BUTTON 自画按钮

  2. · ODT_COMBOBOX 自画组合框

  3. · ODT_LISTBOX 自画列表框

  4. · ODT_MENU 自画菜单

  5. · ODT_LISTVIEW 列表视控件

  6. · ODT_STATIC 自画Static控件

  7. · ODT_TAB Tab 控件

CtlID 组合框,列表框或按钮的控制ID。对菜单不使用这个成员。 itemID 菜单的菜单项ID或是组合框或列表框中的项的索引。对于空的列表框或组合框,这个成员是一个负值,这允许应用程序只在rcItem成员指定的位置画出焦点矩形,既使控件中没有项。这样用户就可以知道列表框或组合框是否具有输入焦点。itemAction成员中位的设置决定了该矩形是否应当画得就象列表框或组合框拥有输入焦点那样。 itemAction 定义了要求的绘图动作。它可以是下面位中的一个或多个:

· ODA_DRAWENTIRE 当需要画出整个控件时设置该位。 · ODA_FOCUS 当控件获得或失去输入焦点时设置该位。如果要确定控件是否拥有输入焦点,应该检查itemState成员。 · ODA_SELECT 当选择项发生变化时设置该位。如果要确定新的选择状态,应该检查itemState成员。

itemState 指定了完成当前绘图动作后项的可视状态。如果要使菜单项无效,则会设置ODS_GRAYED标志。状态标志如下:

  1. · ODS_CHECKED 如果要标记菜单项则设置该位。仅对菜单使用。

  2. · ODS_DISABLED 如果要把该项画成禁止状态则设置该位。

  3. · ODS_FOCUS 如果该项拥有输入焦点则设置该位。

  4. · ODS_GRAYED 如果要使该项变灰则设置该位。仅对菜单使用。

  5. · ODS_SELECTED 如果该项被选中则设置该位。

  6. · ODS_COMBOBOXEDIT 绘图发生在自画组合框控件的选择区域(编辑控件)。

  7. · ODS_DEFAULT 该项为缺省项。

hwndItem 指定了组合框,列表框和按钮控件的窗口句柄。指定了包含菜单项的菜单的句柄(HMENU)。 hDC 标识了一个设备环境。在控件上进行绘图操作时必须使用这个设备环境rcItem hDC成员指定的设备环境中的矩形,定义了将要画出的控件的边界。Windows自动将画出的任何东西裁剪在组合框,列表框和按钮的设备环境之内,但是它不裁剪菜单项。在画出菜单项的时候,拥有者不能在rcItem成员所定义的矩形之外绘图。 itemData 对于组合框或列表框,这个成员包含了下面的函数传递给列表框的值:

  1. CComboBox::AddString

  2. CComboBox::InsertString

  3. CListBox::AddString

  4. CListBox::InsertString

对于菜单,这个成员包含了下面的函数传递给菜单的值:

  1. CMenu::AppendMenu

  2. CMenu::InsertMenu

  3. CMenu::ModifyMenu

问题56:关于派生类控件利用消息反射宏直接跳过父窗口响应自身的消息

class CMyListBox:public CListBox
{
protected:
    afx_msg void OnDoubleClick();
    DECLARE_MESSAGE_MAP()
};
​
BEGIN_MESSAGE_MAP(CMyListBox,CListBox)
    ON_CONTROL_REFLECT(LBN_DBLCLK,OnDoubleClick)
END_MESSAGE_MAP()
​
void CMyListBox::OnDoubleClick()
{
​
    CString string;
    int nIndex=GetCurSel();
    GetText(nIndex,string);
    MessageBox(string);
}

问题57:模态对话框和非模态对话框

问题58:C++的类中,哪些函数不能用virtual修饰?

1.C++的类中,那些函数不能用virtual修饰?

不能是虚函数的成员函数有:静态成员函数,内联成员函数,构造函数。PS:一般情况下,父类的析构函数需要定义为虚函数。

2.构造函数为什么不能被virtual修饰?

构造函数调用时,Vtable没有建立,当然不能使用虚函数。构造函数不需要是虚函数,也不允许是虚函数,因为创建一个对象时我们总是要明确指定对象的类。

构造函数是用来创建一个新的对象,而虚函数的运行是建立在对象的基础上,在构造函数执行时,对象尚未形成,所以不能将构造函数定义为虚函数,通常析构函数才会用virtual修饰(虚函数实际存放在对象的头部的虚函数表中的)

问题59 关于MyMusicPlayer的歌词设置

struct LyricSettingData
{
    bool lyric_karaoke_disp{ true };            //可以是否以卡拉OK样式显示
    bool lyric_fuzzy_match{ true };             //歌词模糊匹配
    bool save_lyric_in_offset{};                //是否将歌词保存在offset标签中,还是保存在每个时间标签中
    wstring lyric_path;                         //歌词文件夹的路径
    bool use_inner_lyric_first{};               //优先使用内嵌歌词
    bool show_translate{ true };                //歌词是否显示翻译
​
    enum LyricSavePolicy        //歌词保存策略
    {
        LS_DO_NOT_SAVE,         //不保存(手动保存)
        LS_AUTO_SAVE,           //自动保存
        LS_INQUIRY              //询问
    };
​
    LyricSavePolicy lyric_save_policy{};        //歌词自动保存策略
​
    FontInfo lyric_font;                        //歌词字体
    int lyric_line_space{ 2 };                  //歌词的行间距
    Alignment lyric_align{ Alignment::CENTER }; //歌词的对齐方式
​
    bool cortana_info_enable{};                 //是否允许在Cortana的搜索框中显示信息
    bool cortana_show_lyric{ true };            //是否在Cortana搜索框中显示歌词
    bool cortana_lyric_double_line{ true };     //是否在Cortana搜索中以双行显示歌词
    int cortana_color{ 0 };                     //Cortana搜索框的背景颜色(0:跟随系统,1:黑色,2:白色)
    bool cortana_show_album_cover{ true };      //是否在Cortana搜索框显示专辑封面
    bool cortana_icon_beat{ true };             //Cortana图标随音乐节奏跳动
    bool cortana_lyric_compatible_mode{ false };    //Cortana搜索框歌词显示使用兼容模式
    FontInfo cortana_font;                      //搜索框字体
    bool cortana_lyric_keep_display{ false };   //搜索框歌词是否在暂停时保持显示
    bool cortana_show_spectrum{ false };        //是否在搜索框显示频谱
    bool cortana_opaque{ false };               //搜索框不透明
    Alignment cortana_lyric_align{ Alignment::CENTER };               //搜索框歌词对齐方式
    bool show_default_album_icon_in_search_box{ false };      //没有歌词时搜索框显示黑色胶片图标
    COLORREF cortana_transparent_color{};
​
    bool show_desktop_lyric{ false };           //显示桌面歌词
    DesktopLyricSettingData desktop_lyric_data;
};
​
struct DesktopLyricSettingData      //桌面歌词设置
{
    bool lyric_double_line{ false };
    FontInfo lyric_font;
    COLORREF text_color1{};
    COLORREF text_color2{};
    int text_gradient{};
    COLORREF highlight_color1{};
    COLORREF highlight_color2{};
    int highlight_gradient{};
    int opacity{ 100 };
    bool lock_desktop_lyric{ false };
    bool hide_lyric_window_without_lyric{ false };  //没有歌词时隐藏歌词窗口
    bool hide_lyric_window_when_paused{ false };    //暂停时隐藏歌词窗口
    bool lyric_background_penetrate{ false };
    bool show_unlock_when_locked{ true };           //桌面歌词锁定时显示解锁图标
    Alignment lyric_align{ Alignment::CENTER }; //歌词的对齐方式
};
​

问题60:MFC窗口客户区全屏显示操作

MFC窗口客户区全屏显示以及PICTURE控件全屏显示

相信用电脑看过电影的人都会下意识的默认双击一个播放器就会得到全屏播放影片的效果,那这个是怎么做到的呢?其实本人也是不知道的。。当然这篇文章不是到这就完了,而是介绍一种在MFC上全屏显示窗口或者PICTURE控件的方法,用此方法能做到全屏显示图像的效果,当然好坏还是有待考证的。

首先从简单的着手,来看如何实现MFC窗口的全屏显示,废话不说上代码:

在对话框头文件中添加变量:

//对话框功能
private:
    BOOL bFullScreen;
    CRect rectFullScreen;
    WINDOWPLACEMENT m_struOldWndpl;//结构中包含了有关窗口在屏幕上位置的信息

添加对话框的鼠标左键双击处理函数,并添加如下代码:

if (!bFullScreen)
{
    //获取系统屏幕宽高
    int g_iCurScreenWidth = GetSystemMetrics(SM_CXSCREEN);
    int g_iCurScreenHeight = GetSystemMetrics(SM_CYSCREEN);
​
    //用m_struOldWndpl得到当前窗口的显示状态和窗体位置,以供退出全屏后使用
    GetWindowPlacement(&m_struOldWndpl);
​
    //计算出窗口全屏显示客户端所应该设置的窗口大小,主要为了将不需要显示的窗体边框等部分排除在屏幕外
    CRect rectWholeDlg;
    CRect rectClient;
    GetWindowRect(&rectWholeDlg);//得到当前窗体的总的相对于屏幕的坐标
    RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposQuery, &rectClient);//得到窗口客户区坐标
    ClientToScreen(&rectClient);//将客户区相对窗体的坐标转为相对屏幕坐标
    rectFullScreen.left = rectWholeDlg.left - rectClient.left;
    rectFullScreen.top = rectWholeDlg.top - rectClient.top;
    rectFullScreen.right = rectWholeDlg.right + g_iCurScreenWidth - rectClient.right;
    rectFullScreen.bottom = rectWholeDlg.bottom + g_iCurScreenHeight - rectClient.bottom;
​
    //设置窗口对象参数,为全屏做好准备并进入全屏状态
    WINDOWPLACEMENT struWndpl;
    struWndpl.length = sizeof(WINDOWPLACEMENT);
    struWndpl.flags = 0;
    struWndpl.showCmd = SW_SHOWNORMAL;
    struWndpl.rcNormalPosition = rectFullScreen;
    SetWindowPlacement(&struWndpl);//该函数设置指定窗口的显示状态和显示大小位置等,是我们该程序最为重要的函数
    bFullScreen = true;
}
else
{
    SetWindowPlacement(&m_struOldWndpl);
    bFullScreen = false;
}

OK,到处窗口客户区全屏显示就完成一大部分了,运行一下双击窗口就会全屏显示窗口客户区,再双击则返回之前的窗体。

为什么说只是完成一大部分呢?原因在于现在的全屏还不是真正意义上的全屏,在目前的情况下双击全屏会发现底部开始菜单栏和右边一部分屏幕并没有被覆盖,究其原因是因为当窗体做出调整大小之类的操作时Windows会首先发送WM_GETMINMAXINFO消息,该消息在默认的缺省处理中是不允许窗体大小大于屏幕大小的。为此,我们要修改这个消息的响应函数,即主动为窗体添加WM_GETMINMAXINFO消息的处理函数,实现如下:

void CShowPictureInFullScreenDlg::OnGetMinMaxInfo(MINMAXINFO* lpMMI)
{
	// TODO:  在此添加消息处理程序代码和/或调用默认值
	if (bFullScreen)
	{
		lpMMI->ptMaxSize.x = rectFullScreen.Width();
		lpMMI->ptMaxSize.y = rectFullScreen.Height();
		lpMMI->ptMaxPosition.x = rectFullScreen.left;
		lpMMI->ptMaxPosition.y = rectFullScreen.top;
		lpMMI->ptMaxTrackSize.x = rectFullScreen.Width();
		lpMMI->ptMaxTrackSize.y = rectFullScreen.Height();
	}

	CDialogEx::OnGetMinMaxInfo(lpMMI);
}

到此,全屏显示窗口客户区的功能就完成了。如果仔细观察了上面所写的代码,会轻易的发现其中最为关键的一句为

SetWindowPlacement(&struWndpl);//该函数设置指定窗口的显示状态和显示大小位置等,是我们该程序最为重要的函数

如果我们把窗口客户区变为全屏大小之后将PICTURE控件调整大小填满整个客户区并且隐藏掉其他的控件,则可以得到全屏显示的图片。实现如下:

添加一个类变量存储PICTURE控件原位置信息

WINDOWPLACEMENT m_struOldWndpPic;//PICTURE控件在屏幕上位置的信息

修改对话框的鼠标左键双击处理函数

if (!bFullScreen)
{
    bFullScreen = true;
​
    //获取系统屏幕宽高
    int g_iCurScreenWidth = GetSystemMetrics(SM_CXSCREEN);
    int g_iCurScreenHeight = GetSystemMetrics(SM_CYSCREEN);
​
    //用m_struOldWndpl得到当前窗口的显示状态和窗体位置,以供退出全屏后使用
    GetWindowPlacement(&m_struOldWndpl);
    GetDlgItem(IDC_STATIC_PICSHOW)->GetWindowPlacement(&m_struOldWndpPic);
    
    //计算出窗口全屏显示客户端所应该设置的窗口大小,主要为了将不需要显示的窗体边框等部分排除在屏幕外
    CRect rectWholeDlg;
    CRect rectClient;
    GetWindowRect(&rectWholeDlg);//得到当前窗体的总的相对于屏幕的坐标
    RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposQuery, &rectClient);//得到客户区窗口坐标
    ClientToScreen(&rectClient);//将客户区相对窗体的坐标转为相对屏幕坐标
    //GetDlgItem(IDC_STATIC_PICSHOW)->GetWindowRect(rectClient);//得到PICTURE控件坐标
​
    rectFullScreen.left = rectWholeDlg.left - rectClient.left;
    rectFullScreen.top = rectWholeDlg.top - rectClient.top;
    rectFullScreen.right = rectWholeDlg.right + g_iCurScreenWidth - rectClient.right;
    rectFullScreen.bottom = rectWholeDlg.bottom + g_iCurScreenHeight - rectClient.bottom;
​
    //设置窗口对象参数,为全屏做好准备并进入全屏状态
    WINDOWPLACEMENT struWndpl;
    struWndpl.length = sizeof(WINDOWPLACEMENT); 
    struWndpl.flags = 0;
    struWndpl.showCmd = SW_SHOWNORMAL;
    struWndpl.rcNormalPosition = rectFullScreen;
    SetWindowPlacement(&struWndpl);//该函数设置指定窗口的显示状态和显示大小位置等,是我们该程序最为重要的函数
​
    //将PICTURE控件的坐标设为全屏大小
    GetDlgItem(IDC_STATIC_PICSHOW)->MoveWindow(CRect(0, 0, g_iCurScreenWidth, g_iCurScreenHeight));
}
else
{
    GetDlgItem(IDC_STATIC_PICSHOW)->SetWindowPlacement(&m_struOldWndpPic);
    SetWindowPlacement(&m_struOldWndpl);
    bFullScreen = false;
}

到此本篇博客希望实现的内容就都完成了,效果如下:

C++之MFC学习_第6张图片

C++之MFC学习_第7张图片

可以在鼠标双击函数中加上鼠标位置判断来实现鼠标双击位置在PICTURE控件内才放大的效果。

问题61:关于std::initializer_list<>()

#include 
#include 
#include 
​
// 使用 std::initializer_list 来初始化任意长度的初始化列表
//stl中的容器是通过使用 std::initializer_list 完成的
class Foo
{
public:
    Foo(std::initializer_list ){}
};
​
class FooVector
{
    std::vector content_;
​
public:
    FooVector(std::initializer_list list)//initializer_list 负责接收初始化列表
    {
        for (auto it = list.begin(); it != list.end(); ++it)
        {
            content_.push_back(*it);
        }
    }
};
​
​
//map 是以 pair形式插入的。map中的元素的类型value_type 
//typedef pair value_type;
​
​
class FooMap
{
    std::map content_;
    using pair_t = std::map::value_type;//重新命名类型   typedef
​
public:
    FooMap(std::initializer_list list)
    {
        for (auto it = list.begin(); it != list.end(); ++it)
        {
            content_.insert(*it);
        }
    }
};
​
//使用 std::initializer_list 给自定义类型做初始化
void test01()
{
    Foo foo = { 1,2,3,4,5 };
    FooVector foo1 = { 1, 2, 3, 4, 5 };
    FooMap foo2 = { { 1, 2 }, { 3, 4 }, { 5, 6 } };
}
​
//使用 std::initializer_list 传递同类型的数据
void func(std::initializer_list list)
{
    std::cout << "size = "< func2(void)
{
    int a = 1, b = 2;
    return { a,b };//ab 返回时并没有拷贝
}
​
//正确的使用
std::vector func3(void)
{
    int a = 1, b = 2;
    return { a,b };//ab 返回时并没有拷贝
}
​
void test03()
{
    std::initializer_list myList;
    size_t n = myList.size();
    myList = { 1,2,3,4,5,6 };
    n = myList.size();
    myList = { 11,22};
    n = myList.size();
​
    std::vector a;
    a = func2();//值时乱码值
    a = func3();
​
}
​
int main(void)
{
    test01();
    test02();
    test03();
    system("pause");
    return 0;
}

问题62:关于GetClassInfo与注册窗口

我们知道一个窗体建立的过程.首先注册一个wndClass的结构体.然后才是用createWindow函数来建立窗体. 现在有个问题了,我们想知道我们建立的wndClass结构体是否已经注册了,怎么办?

GetClassInfo就是用来解决这个问题的.

先看msdn中的东西.

The GetClassInfo function retrieves information about a window class. 这个函数返回一些信息关于某个windowClass

Note The GetClassInfo function has been superseded by the GetClassInfoEx function. You can still useGetClassInfo, however, if you do not need information about the class small icon. 需要注意的是这个函数已经被GetClassInfoEx 取代了,如果你不需要小图标的信息 当然还是可以继续用这个函数的.

BOOL GetClassInfo(      
​
    HINSTANCE hInstance,     LPCTSTR lpClassName,     LPWNDCLASS lpWndClass );

hInstance

  • lpClassName

    [in] Pointer to a null-terminated string containing the class name. The name must be that of a preregistered class or a class registered by a previous call to the RegisterClass or RegisterClassExfunction.Alternatively, this parameter can be an atom. If so, it must be a class atom created by a previous call to RegisterClass or RegisterClassEx. The atom must be in the low-order word of lpClassName; the high-order word must be zero. windowClass结构体.

  • lpWndClass

    [out] Pointer to a WNDCLASS structure that receives the information about the class. 返回的结构体的信息会储存在这里.delphi中这个就是一个var 参数.

返回值 boolean;

例子会在综合后面几个API后一起给出来.

BOOL CMyTest::RegisterWindowClass(HINSTANCE hInstance)   
{   
      LPCSTR className = "CMyWin1";//"CMyWin"控件?的名字   
       WNDCLASS windowclass;         
    
       if(hInstance)   
              hInstance = AfxGetInstanceHandle();   
          
       if (!(::GetClassInfo(hInstance, className, &windowclass)))   
       {                
              windowclass.style = CS_DBLCLKS;   
              windowclass.lpfnWndProc = ::DefWindowProc;   
              windowclass.cbClsExtra = windowclass.cbWndExtra = 0;   
              windowclass.hInstance = hInstance;   
              windowclass.hIcon = NULL;   
              windowclass.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);   
              windowclass.hbrBackground= (HBRUSH)(COLOR_BTNFACE+1);
              windowclass.lpszMenuName = NULL;   
              windowclass.lpszClassName = className;   
    
              if (!AfxRegisterClass(&windowclass))   
              {   
                     AfxThrowResourceException();   
                     return FALSE;   
              }   
       }   
    
       return TRUE;   
} 

注册窗口:

CMyTest::CMyTest(){  RegisterWindowClass(); }

问题63 更改MFC对话框默认的窗口类名

Windows操做系统中有一个概念——窗口类。窗口类是抽象的,它能够有不少窗口实例,即HWND/CWnd。在一个程序中,你能够定制并注册一个窗口类,而后用它建立窗口,也能够直接用已经注册的窗口类建立咱们的窗口。编辑器

为了减轻咱们的编程负担,也为了隐藏具体的实现,MFC已经为咱们定制并注册了不少窗口类,咱们能够直接使用它们。好比,对话框的窗口类为“#32770”,按钮的窗口类为“Button”,列表框的窗口类为“ListBox”……等等。函数

不少时候,咱们须要用到FindWindow函数来查找已经存在的窗口,而后给它发送消息。或者查询系统是否已经存在指定的窗口(进程),若是存在,咱们就再也不建立程序的新进程,而仅仅是激活它。FindWindow函数的声明为:spa

HWND FindWindow(   
  LPCTSTR lpClassName,
  LPCTSTR lpWindowName
);

咱们能够经过窗口类名(lpClassName)查找,也能够经过窗口标题文本(lpWindowName)查找,或者同时使用。窗口的标题文本并不老是十分可靠,不少时候,它是动态变化的。因此经过窗口类名来查找就颇有必要了。操作系统

那么怎样把MFC提供给咱们的对话框的默认窗口类名“#32770“改为咱们程序特有的呢?如下是更改步骤:

  1. 打开工程的资源视图进程

  2. 更改对话框资源的ClassName属性。有两种方法:资源

第一种方法,在须要更改类名的对话框资源上右击,选择“属性”,打开属性对话框,能够看到有一个名为“Class Name”的属性,咱们在其中输入一个本身定义的名称(如“MyPrivateClassName”)便可。可是,MFC默认设置这个属性选项是灰色不可用的,咱们先要开启它。切换到资源视图,右击根节点(如“XxxXxx.rc”),选属性,去掉勾选“Enable MFC Features”项(若是是VS.Net,把“MFC Mode property”项改成False)。这样就能够修改对话框的Class Name属性了。get

另外一种方法是直接用文本编辑器打开资源文件,修改对话框定义代码,插入CLASS项,如:

IDD_LIMITDLGINSTANCE_DIALOG DIALOGEX 0, 0, 195, 44
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "LimitDlgInstance"
CLASS "MyPrivateClassName" // Add your class name here!
FONT 8, "MS Sans Serif"
BEGIN
  DEFPUSHBUTTON  "OK",IDOK,138,7,50,14
  PUSHBUTTON   "Cancel",IDCANCEL,138,23,50,14
  PUSHBUTTON   "&Test!",IDC_BUTTON1,48,14,49,15
END
  1. 定制并注册新窗口类。在应用程序类的InitInstance()函数中添加如下代码,以注册资源文件中用到的新窗口类:

 WNDCLASS wc;
 // 获取窗口类信息。MFC默认的全部对话框的窗口类名为 #32770
 ::GetClassInfo(AfxGetInstanceHandle(), _T("#32770"), &wc);
​
 // 改变窗口类名
 wc.lpszClassName = _T("MyPrivateClassName");
​
 // 注册新窗口类,使程序能使用它
 AfxRegisterClass(&wc);

注意:

(1)在调用函数::GetClassInfo()时,请确保传入的第一个参数HINSTANCE是包含你对话框资源所在的dll或exe的进程实例。

(2) 请确保资源中指定的Class Name与InitInstance()中指定的窗口类名彻底相同,不然程序不能运行。

更改完成,请重建工程,运行程序,使用Spy++查看最终效果吧!

问题64:关于CWnd::OnGetMinMaxInfo

CWnd::OnGetMinMaxInfo

afx_msg void OnGetMinMaxInfo( MINMAXINFO FAR* lpMMI );

参数:

参数 参数说明
lpMMI 指向一个MINMAXINFO结构,其中包含了有关窗口的最大化大小和位置以及最小、最大跟踪大小的信息。有关这个结构的更多信息参见MINMAXINFO结构。

说明: 每当Windows需要知道窗口的最大化位置或大小,或者最小、最大的跟踪大小时,框架就调用这个成员函数。最大化大小是指当窗口的边框被完全扩展时窗口的大小。窗口的最大跟踪大小是指用边框改变窗口的大小时可以达到的最大窗口大小。窗口的最小跟踪大小是指用边框改变窗口大小时可以达到的最小窗口大小。 Windows填充一个点组成的数组,为不同的位置和大小指定了缺省值。应用程序可以在OnGetMinMaxInfo中改变这些值。 注意 框架调用这个成员函数以允许你的应用程序处理一个Windows消息。传递给你的成员函数的参数反映了接收到消息时框架接收到的参数。如果你调用了这个函数的基类实现,则该实现将使用最初传递给消息的参数(而不是你提供给这个函数的参数)。

问题65:关于多显示器的调用

Microsoft为支持多显示器模式提供了一些新的API调用,下面具体介绍它们的功能:

1.HMONITOR MonitorFromPoint(POINT pt,DWORD dwFlags)

MonitorFromPoint返回包含特定点(pt)的一个显示器句柄。如果pt不属于任何一个显示器,返回的显示器句柄由dwFlags标志决定:

MONITOR_DEFAULTTONULL时返回NULL;

MONITOR_DEFAULTTOPRIMARY时返回代表主显示器的HMONITOR句柄;

MONITOR_DEFAULTTONEAREST时返回最靠近pt点的显示器的HMONITOR句柄。

2.HMONITOR MonitorFromRect(LPCRECT lprc,DWORD dwFlags)

MonitorFromRect返回包含lprc代表的矩形的显示器句柄;如果包含此矩形的显示区域不止一个,则返回包含矩形最大部分的显示器句柄;如果矩形不属于任何一个显示区域,返回的句柄由dwFlags决定,规则与MonitorFromPoint相同。

  1. HMONITOR MonitorFromWindow(HWND hwnd,DWORD dwFlags)

与MonitorFromRect类似,但输入是一个代表窗口的句柄hwnd而不是指向矩形的指针。

4.BOOL GetMonitorInfo(HMONITOR hMonitor,LPMONITORINFO lpmi)

GetMonitorInfo返回由hMonitor代表的显示器的有关信息,这些信息存储在指向MONITORINFO结构的指针——lpmi中。这些信息包括用RECT结构表示的显示器的显示区域的大小(如果这个显示器不是主显示器,RECT的坐标可能为负数),以及用RECT结构表示的显示器的工作区域的大小,工作区域是显示区域中除去系统任务栏和应用程序快捷方式栏所剩下的区域,还能够判断此显示器是否为主显示器,并返回一个标志。

5.GetSystemMetrics

获取坐标的时候用VIRTUALSCREEN参数

GetSystemMetrics(SM_CXVIRTUALSCREEN);//虚拟桌面宽度

GetSystemMetrics(SM_CYVIRTUALSCREEN);//虚拟桌面高度

GetSystemMetrics(SM_XVIRTUALSCREEN );//虚拟桌面左上角X坐标

GetSystemMetrics(SM_YVIRTUALSCREEN );//虚拟桌面左上角Y坐标

特别注意的是,多显示器的时候,SM_XVIRTUALSCREEN和SM_YVIRTUALSCREEN是可以为负值的。所以多显示器处理时,边界不要以为是(0,0)->(cx,cy)。多显示器的坐标是以主屏幕的左上角为(0,0)。 使用SM_CXSCREEN,SM_CYSCREEN获取只是主屏大小。

问题66:关于MONITORINFO

typedef struct tagMONITORINFO {
  DWORD cbSize;
  RECT  rcMonitor;
  RECT  rcWork;
  DWORD dwFlags;
} MONITORINFO, *LPMONITORINFO;

Members

cbSize

The size of the structure, in bytes.

Set this member to sizeof ( MONITORINFO ) before calling the GetMonitorInfo function. Doing so lets the function determine the type of structure you are passing to it.

rcMonitor

A RECT structure that specifies the display monitor rectangle, expressed in virtual-screen coordinates. Note that if the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values.

rcWork

A RECT structure that specifies the work area rectangle of the display monitor, expressed in virtual-screen coordinates. Note that if the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values.

dwFlags

A set of flags that represent attributes of the display monitor.

The following flag is defined.

Value Meaning
MONITORINFOF_PRIMARY This is the primary display monitor.

问题67:关于GetMonitorInfo

GetMonitorInfoA function (winuser.h)

The GetMonitorInfo function retrieves information about a display monitor.

Syntax

BOOL GetMonitorInfoA(
  [in]  HMONITOR      hMonitor,
  [out] LPMONITORINFO lpmi
);

Parameters

[in] hMonitor

A handle to the display monitor of interest.

[out] lpmi

A pointer to a MONITORINFO or MONITORINFOEX structure that receives information about the specified display monitor.

You must set the cbSize member of the structure to sizeof(MONITORINFO) or sizeof(MONITORINFOEX) before calling the GetMonitorInfo function. Doing so lets the function determine the type of structure you are passing to it.

The MONITORINFOEX structure is a superset of the MONITORINFO structure. It has one additional member: a string that contains a name for the display monitor. Most applications have no use for a display monitor name, and so can save some bytes by using a MONITORINFO structure.

Return value

If the function succeeds, the return value is nonzero.

If the function fails, the return value is zero.

Remarks

The winuser.h header defines GetMonitorInfo as an alias which automatically selects the ANSI or Unicode version of this function based on the definition of the UNICODE preprocessor constant. Mixing usage of the encoding-neutral alias with code that not encoding-neutral can lead to mismatches that result in compilation or runtime errors. For more information, see Conventions for Function Prototypes.

问题68:关于CDialog的构造函数

CDialog::CDialog
CDialog(LPCSTR lpszTemplateName, CWnd* pParentWnd =  NULL);
CDialog(UNIT nIDTemplate, CWnd * pParentWnd = NULL);

参数:

lpszTemplateName 包含一个对话框模板资源的空终止字符串。
nIDTemplate 包含对话框模板资源的ID号。
pParentWnd 包含对话框的父窗口或所有者窗口对象的指针。如果其为NULL,则对话框对象的父窗口设置为主应用程序窗口。

说明: 构造一个基于资源的模态对话框。调用构造程序的窗体,其中一个窗体通过模板便可访问对话框。另一个一般使用带IDD_前缀(如IDD_DIALOG1)的模板ID号实现访问。 从内存中模板来构造模态对话框,首先需要参数和受保护的构造程序,然后调用InitModalIndirect。 使用上述方法之一构造好对话框之后,调用DoModal。 构造非模态对话框,使用CDialog构造程序中受保护的窗体。构造程序受到保护,因为必须从自己的对话框类中派生得到一个非模态对话框。 构造非模态对话框分两步进行:首先调用构造程序,然后调用Create成员函数创建基于资源的对话框,或者从内存模板中调用CreateIndirect来创建对话框。

问题69:关于CListCtrl::SetItemState

CListCtrl::SetItemState

BOOL SetItemState(int nItem, LVITEM* pItem) BOOL SetItemState(int nItem, UINT nState, UINT nMask)

返回值: 如果成功,则返回非零值,否则为0。

参数:

nItem 要设定状态项的索引。
pItem LVITEM结构的地址,如联机文档“平台SDK”中所述。该结构的stateMask成员指定了要改变的状态位,并且state成员则包含这些位的新值。另一个成员则被忽略。
nState 状态位的新值。
nMask 指定要改变状态位的掩码。

说明: 改变列表视图控件中项的状态。 项的“状态”指指定项有效位的数值,它指示了用户的动作,或其它影响项状态的行为。列表视图控件改变了某些状态位,就如同用户选择了某一项。为了使项失效或隐藏项,或者为了指定覆盖图或状态图,应用必须改变其它的状态位。

问题70:关于CImageList

CImageList::Create

BOOL Create( int cx, int cy, UINT nFlags, int nInitial, int nGrow ); BOOL Create( UINT nBitmapID, int cx, int nGrow, COLORREF crMask ); BOOL Create( LPCTSTR lpszBitmapID, int cx, int nGrow, COLORREFcrMask ); BOOL Create( CImageList& ImageList1, int nImage1, CImageList& ImageList2, int nImage2, int dx, int dy );

返回值:如果成功,则返回非零值,否则为0。

参数:

cx 每个图象的尺寸,以像素为单位。
cy 每个图象的尺寸,以像素为单位。
nFlags 确定创建的图象列表类型。此参数可能为以下值的组合,但只能有一个ILC_COLOR值。 含义 ILC_COLOR 如果没有其它ILC_COLOR* 标记被确定,则使用缺省行为。典型地,缺省为ILC_COLOR4;但对于旧的显示驱动程序,缺省为ILC_COLORDDB ILC_COLOR4 使用4位(16色)设备独立位图(DIB)部分作为图象列表的位图 ILC_COLOR8 使用8位DIB部分。彩色表格使用的颜色与半色调调色板的一样 ILC_COLOR16 使用16位(32/64K色)DIB部分 ILC_COLOR24 使用24位DIB部分 ILC_COLOR32 使用32位DIB部分 ILC_COLORDDB 使用设备独立位图 ILC_MASK 使用掩码。图象列表包含两个位图,其中一个是用做掩码的位图。如果不包括此值,图象列表只包含一个位图
nInitial 图象列表最初包含的图象数。
nGrow 当系统需要改变列表为新图象准备空间时,图象列表可生成的图象数。此参数替代改变的图象列表所能包含的新图象数。
nBitmapID 与图象列表联系的位图的源ID。
crMask 用于生成一个掩码的颜色。此指定的位图中的颜色的每个像素变为黑色,掩码中相应位设置为1。
lpszBitmapID 包含图象的源ID的字符串。
ImageList1 CImageList对象的参考。
nImage1 第一个存在的图象的索引。
ImageList2 CImageList对象的参考。
nImage2 第二个存在的图象的索引。
dx 每个图象的尺寸,用像素表示。
dy 每个图象的尺寸,用像素表示。

说明: 需要两步构造一个CImageList。首先调用构造函数,然后调用Create,创建图象列表并附加给CImageList对象。

问题71:关于CListCtrl::GetItemRect

CListCtrl::GetItemRect

BOOL GetItemRect(int nItem,LPRECT lpRect,UNIT nCode) const

返回值:如果成功,则返回非零值,否则为0。

参数:

nItem 要获取位置的项的索引值。
lpRect 接受绑定矩形的RECT结构的地址。
nCode 要获取绑定矩形的列表视图项的部分。它可为下列值之一: · LVIR_BOUNDS 返回整个项的绑定矩形,包括图标和标签。 · LVIR_ICON 返回图标或小图标的绑定矩形。 · LVIR_LABEL 返回项文本的绑定矩形。

说明:在当前视图中获取某项的全部或部分的绑定矩形。

问题72:关于CWnd::GetWindowRect

CWnd::GetWindowRect

void GetWindowRect( LPRECT lpRect ) const;

参数:

参数 描述
lpRect 指向一个CRect对象或RECT结构,用于接收左上角和右下角的屏幕坐标。

说明: 这个函数将CWnd对象的边界矩形的大小拷贝到lpRect所指向的结构中。大小是用相对于显示器屏幕左上角的屏幕坐标给出的,其中包括了标题条,边框和滚动条的大小,如果有的话。

问题73 关于CMenu::TrackPopupMenu

CMenu::TrackPopupMenu

BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd,LPCRECT lpRect = NULL );

返回值:如果成功,则返回非零值,否则为0。

参数:

参数 描述
nFlags 指定屏幕位置标志或鼠标键标志。 屏幕位置标志可以为下列值之一: · TPM_CENTERALIGN 使弹出菜单在水平方向相对于x指定的坐标居中。 · TPM_LEFTALIGN 放置弹出菜单,以便弹出菜单在由坐标值x指定的位置左对齐。 · TPM_RIGHTALIGN 放置弹出菜单,以便弹出菜单在由坐标值x指定的位置右对齐。 鼠标键标志可以为下列值之一: · TPM_LEFTBUTTON 导致弹出菜单追踪鼠标左键。 · TPM_RIGHTBUTTON 导致弹出菜单追踪鼠标右键。
x 指定弹出菜单屏幕坐标的水平位置。根据参数nFlags的值,该菜单可以左对齐、右对齐或在该位置上居中。
y 指定弹出菜单屏幕坐标的垂直位置。
pWnd 标识拥有弹出菜单的窗口。该窗口接收来自菜单的所有WM_COMMAND消息。在Windows 3.1或以后版本中,窗口直到TrackPopupMenu返回才接收WM_COMMAND消息。在Windows 3.0中,窗口在TrackPopupMenu返回之前接收WM_COMMAND消息。
lpRect 指向RECT结构或包含矩形的屏幕坐标CRect对象,用户在该矩形内部单击鼠标也不会消除弹出菜单。若该参数为NULL ,那么用户在该矩形外部单击鼠标,弹出菜单将消失。对于Windows 3.0,该值必须为NULL。

对于Windows 3.1或更高版本,可以使用下列常量: ·TPM_CENTERALIGN ·TPM_LEFTALIGN ·TPM_RIGHTALIGN ·TPM_RIGHTBUTTON

说明: 在指定位置显示浮动弹出菜单,并追踪弹出菜单中被选择的项。浮动弹出菜单可以出现在屏幕的任何位置。

问题74:关于GetHeaderCtrl()

CMFCListCtrl::GetHeaderCtrl

返回对标头控件的引用。

复制

virtual CMFCHeaderCtrl& GetHeaderCtrl();

返回值

对基础 CMFCHeaderCtrl 对象的 引用。

备注

列表控件的标题控件是包含列标题的窗口。 它通常直接位于列的上方。

问题75:关于CListCtrl控件的描述

A list control consists of using one of four views to display a list of items. The list is typically equipped with icons that indicate what view is displaying. There are four views used to display items

提供了四种默认的样式,在Visual Studio中可以手动选择的

1.Icons: The control displays a list of items using icons with a 32x32 pixels size of icons. This is the preferred view when the main idea consists of giving an overview of the items 2.Small Icons: Like the other next two views, it uses 16x16 pixel icons to display a simplified list of the items. Once more, no detail is provided about the items of this list. The list is organized in disparate columns with some on top of others. If the list is supposed to be sorted, the alphabetical arrangement is organized from left to right. 3.List: This list, using small icons, is also organized in columns; this time, the columns are arranged so that the first column gets filled before starting the second. If the list is sorted, the sorting is arranged in a top-down manner. 4.Report: This view displays arranged columns of items and provides as many details as the list developer had arranged it.

创建CListCtrl的方式

可以额外设置风格

CListCtrl::SetExtendedStyle() method. Its syntax is:

DWORD SetExtendedStyle(DWORD dwNewStyle);

When calling this method, pass the desired extended style or a combination of these styles as argument. Some of the values are:

When calling this method, pass the desired extended style or a combination of these styles as argument. Some of the values are:

关于列表中每一项的数据结构

The state member variable of the LVITEM structure specifies what to do with the new item. For example, once the item has been added, you may want to prepare it for deletion prior to a cut-and-paste operation, in which case you would give it a value of LVIS_CUT. If the item is involved in a drag-and-drop operation, you can assign it a state value of LVIS_DROPHILIGHTED. To give focus to the item, set its state value to LVIS_FOCUSED. An item with an LVIS_SELECTED state value will be selected.

问题76:关于NM_CUSTOMDRAW

关于该消息的两个重要结构体

1.NMLVCUSTOMDRAW

list-view 类型控件传递的信息结构,里面包含了具体的自定义绘图信息(NMCUSTOMDRAW)以及文本的前景色和背景色。

This structure contains information specific to an NM_CUSTOMDRAW message sent by a list-view control.

typedef struct tagNMLVCUSTOMDRAW {
  NMCUSTOMDRAW nmcd;
  COLORREF clrText;
  COLORREF clrTextBk;
} NMLVCUSTOMDRAW, *LPNMLVCUSTOMDRAW;

参数

  • nmcd NMCUSTOMDRAW structure that contains general custom draw information.//具体的绘制信息在这里

  • clrText COLORREF value that represents the color that is used to display text foreground in the list-view control.

  • clrTextBk COLORREF value that represents the color that is used to display text background in the list-view control.

2.NMCUSTOMDRAW

This structure contains information specific to an NM_CUSTOMDRAW message.

typedef struct tagNMCUSTOMDRAWINFO {
  NMHDR hdr;
  DWORD dwDrawStage;
  HDC hdc;
  RECT rc;
  DWORD dwItemSpec;
  UINT uItemState;
  LPARAM lItemlParam 
} NMCUSTOMDRAW, FAR* LPNMCUSTOMDRAW;

参数

  • hdr Handle to an NMHDR structure that contains information about this message.

  • dwDrawStage DWORD that specifies the current drawing stage. It is one of the values in the following tables.

    The following table shows the global Drawstage values.

    Value Description
    CDDS_POSTERASE After the erasing cycle is complete.
    CDDS_POSTPAINT After the painting cycle is complete.
    CDDS_PREERASE Before the erasing cycle begins.
    CDDS_PREPAINT Before the painting cycle begins.

    The following table shows the global Drawstage values.

    Value Description
    CDDS_ITEM Indicates that the dwItemSpec, uItemState, and lItemParam members are valid.
    CDDS_ITEMPOSTERASE After an item has been erased.
    CDDS_ITEMPOSTPAINT After an item has been drawn.
    CDDS_ITEMPREERASE Before an item is erased.
    CDDS_ITEMPREPAINT Before an item is drawn.
  • hdc Handle to the device context for the control. Use this handle to perform any GDI functions.

  • rc RECT structure that describes the bounding rectangle of the area being drawn. This member is used with the header, toolbar, ToolTip, and tree view common controls.

  • dwItemSpec(list-view控件中项目数) DWORD that specifies the item number. This value is control specific, using the item-referencing convention for that control. Additionally, trackbar controls use the following values to identify portions of control.(此外,轨迹栏控件使用以下值来标识控件部分。)

    Value Description
    TBCD_CHANNEL Identifies the channel that the trackbar control's thumb marker slides along.
    TBCD_THUMB Identifies the trackbar control's thumb marker. This is the portion of the control that the user moves.
    TBCD_TICS Identifies the increment tic marks that appear along the edge of the trackbar control.
  • uItemState(确定控件目前的状态) Specifies the current item state. It can be a combination of the following values.

    Value Description
    CDIS_CHECKED The item is checked.
    CDIS_DEFAULT The item is in its default state.
    CDIS_DISABLED The item is disabled.
    CDIS_FOCUS The item is in focus.
    CDIS_GRAYED The item is grayed.
    CDIS_HOT The item is currently under the pointer (hot).
    CDIS_SELECTED The item is selected.
  • lItemlParam Application-defined item data.

NM_CUSTOMDRAW消息自身的返回值(即OnNmCustomDraw()函数的返回值)

This message is sent by some common controls to notify their parent windows about drawing operations. The message is sent in the form of a WM_NOTIFY message.

复制

NM_CUSTOMDRAW
#ifdef LIST_VIEW_CUSTOM_DRAW
  lpNMCustomDraw = (LPNMLVCUSTOMDRAW) lParam;
#elif TOOL_TIPS_CUSTOM_DRAW
  lpNMCustomDraw = (LPNMTTCUSTOMDRAW) lParam;
#elif TREE_VIEW_CUSTOM_DRAW
  lpNMCustomDraw = (LPNMTVCUSTOMDRAW) lParam;
#else
  lpNMCustomDraw = (LPNMCUSTOMDRAW) lParam;
#endif

参数

  • lpNMCustomDraw

    Long pointer to a custom draw-related structure that contains information about the drawing operation. The following table lists the controls that send the NM_CUSTOMDRAW message and their associated structures.

    Control Structure
    List view NMLVCUSTOMDRAW
    ToolTips NMTTCUSTOMDRAW
    Tree view NMTVCUSTOMDRAW
    All other supported controls NMCUSTOMDRAW

返回值

The value your application can return depends on the current drawing stage. The dwDrawStage member of the associated NMCUSTOMDRAW structure holds a value that specifies the drawing stage.

You must return one of the following values when dwDrawStage equals CDDS_PREPAINT:

  • CDRF_DODEFAULT The control will draw itself. It will not send any additional NM_CUSTOMDRAW messages for this paint cycle.

  • CDRF_NOTIFYITEMDRAW The control will notify the parent of any item-related drawing operations. It will send NM_CUSTOMDRAW messages before and after drawing items.

  • CDRF_NOTIFYITEMERASE The control will notify the parent when an item will be erased. It will send NM_CUSTOMDRAW messages before and after erasing items.

  • CDRF_NOTIFYPOSTERASE The control will notify the parent after erasing an item.

  • CDRF_NOTIFYPOSTPAINT The control will notify the parent after painting an item.

You must return one of the following values when dwDrawStage equals CDDS_ITEMPREPAINT:

  • CDRF_NEWFONT Your application specified a new font for the item; the control will use the new font. For more information on changing fonts, see Changing Fonts and Colors.

  • CDRF_SKIPDEFAULT Your application drew the item manually. The control will not draw the item.

两个例子

函数执行原理:该函数处理NM_CUSTOMDRAW消息的,只要触发该消息就会执行该函数,在本文中ListCtrl控件的重绘都会出发该消息。

函数执行过程:

该处理函数将控件的绘制分为两部分:擦除和绘画。Windows在每部分的开始和结束都会发送NM_CUSTOMDRAW消息。所以总共就有4个消息。但是 实际上你的程序所收到消息可能就只有1个或者多于四个,这取决于你想要让WINDOWS怎么做。每次发送消息的时段被称作为一个“绘画段”。你必须紧紧抓 住这个概念,因为它贯穿于整个“重绘”的过程。

所以,你将会在以下的时间点收到通知:

一个item被画之前——“绘画前”段 一个item被画之后——“绘画后”段 一个item被擦除之前——“擦除前”段 一个item被擦除之后——“擦除后”段

并不是所有的消息都是一样有用的,实际上,我不需要处理所有的消息,直到这篇文章完成之前,我还没使用过擦除前和擦除后的消息。所以,不要被这些消息吓到你。

NM_CUSTOMDRAW消息将会给你提供以下的信息:

1.ListCtrl的句柄

2.ListCtrl的ID

3.当前的“绘画段”

4.绘画的DC,让你可以用它来画画

5.正在被绘制的控件、item、subitem的RECT值

6.正在被绘制的Item的Index值

7.正在被绘制的SubItem的Index值

8.正被绘制的Item的状态值(selected, grayed, 等等)

9.lItem的LPARAM值,就是你使用CListCtrl::SetItemData所设的那个值

其中4的获得方法:CDC *pDC=CDC::FromHandle(pLVCD->nmcd.hdc);

其中5的获得方法:item=(int)pLVCD->nmcd.dwItemSpec;subitem= pLVCD->iSubItem;

C++之MFC学习_第8张图片

C++之MFC学习_第9张图片

这两个图分别是刚才那两个值的对应值,图一中返回值解释如下

从图一中我们可以看到返回值分为两个部分:

1.当pLVCD->nmcd.dwDrawStage= CDDS_PREPAINT时即绘画周期开始前:

CDRF_DODEFAULT:返回该值之后该控件自己绘制,整个绘画周期内他不会发送任何其他的NM_CUSTOMDRAW消息,

CDRF_NOTIFYITEMDRAW:该返回值返回以后会通知父窗口相关项的绘画操作,并且在项的绘画开始前和开始后都会发送NM_CUSTOMDRAW

2.当pLVCD->nmcd.dwDrawStage=CDDS_ITEMPREPAINT时即在该项绘画开始前

CDRF_NOTIFYSUBITEMDRAW:在视图项被绘制之前,你的应用程序会收到一个NM_CUSTOMDRAW消息,该消息中的dwDrawStage为CDDS_ITEMPREPAINT | CDDS_SUBITEM,这时你可以指定画笔和颜色对每个子项进行修改,否则返回CDRF_DODEFAULT默认处理。

CDRF_NEWFONT:你的应用程序使用指定的画笔,然后控件将会调用。

CDRF_SKIPDEFAULT:应用程序绘制这个项,控件不绘制。

上面是我们用到的其他自己研究吧!

看了上面的解释也许你还是一头雾水,那这里我就给你解答:

首先你要明白函数的过程是一个简单的绘画操作,绘画周期开始前 ,绘画前,绘画中,绘画后,你第一次触发NM_CUSTOMDRAW消息肯定是绘画开始前的所以第一个if语句肯定是成立的,这样就执行了第一个if语句,而接下来绘画前,绘画中,绘画后都不会触发,都不执行,这样一个关键点就是返回值了LRESULT了,其返回值就决定了在绘画过程中会不会触发NM_CUSTOMDRAW,这样你也就明白了第一个if中的返回值了吧。

而第二个if是绘画前和第一个的道理是一样的,从上面的解释中我们可以看到其返回值有三个,这三个中我尤其有说明的是CDRF_NOTIFYSUBITEMDRAW,因为该返回值决定是否触发消息进入绘画中,后面的代码中我们会看到在绘画中修改代码……….。

hdr NMHDR对象

dwDrawStage 当前绘制状态,其取值如表7所示:

类型值 含义

CDDS_POSTERASE 擦除循环结束

CDDS_POSTPAINT 绘制循环结束

CDDS_PREERASE 准备开始擦除循环

CDDS_PREPAINT 准备开始绘制循环

CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效

CDDS_ITEMPOSTERASE 列表项擦除结束

CDDS_ITEMPOSTPAINT 列表项绘制结束

CDDS_ITEMPREERASE 准备开始列表项擦除

CDDS_ITEMPREPAINT 准备开始列表项绘制

CDDS_SUBITEM 指定列表子项

表7 dwDrawStage的类型值与含义

hdc指定了绘制操作所使用的设备环境。

rc指定了将被绘制的矩形区域。

dwItemSpec 列表项的索引

uItemState 当前列表项的状态,其取值如表8所示:

类型值 含义

CDIS_CHECKED 标记状态。

CDIS_DEFAULT 默认状态。

CDIS_DISABLED 禁止状态。

CDIS_FOCUS 焦点状态。

CDIS_GRAYED 灰化状态。

CDIS_SELECTED 选中状态。

CDIS_HOTLIGHT 热点状态。

CDIS_INDETERMINATE 不定状态。

CDIS_MARKED 标注状态。

表8 uItemState的类型值与含义

lItemlParam 当前列表项的绑定数据

pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage:

当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:

类型值 含义

CDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。

CDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。

CDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。

CDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。

表9 pResult的类型值与含义(一)

当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:

类型值 含义

CDRF_NEWFONT 指定后续操作采用应用中指定的新字体。

CDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。

CDRF_SKIPDEFAULT 系统不必再绘制该子项。

pNMCD->nmcd.dwItemSpec 是项的索引 只有当CDRF_NOTIFYSUBITEMDRAW时pNMCD->iSubItem才是子项索引, 否则为0.

分类: MFC

以下是一个利用 NM_CUSTOMDRAW 消息绘制出的多色ListCtrl的例子。

BEGIN_MESSAGE_MAP(CMyListCtrl, CListCtrl)
    ...
    ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, &CMyListCtrl::OnNMCustomdraw)
    ...
END_MESSAGE_MAP()

void CMyListCtrl::OnNMCustomdraw(NMHDR *pNMHDR, LRESULT *pResult)
{
	NMLVCUSTOMDRAW* pLVCD = reinterpret_cast(pNMHDR); 
	*pResult = 0;
// TODO: Add your control notification handler code here
//指定列表项绘制前后发送消息
if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage) 
{ 
	*pResult = CDRF_NOTIFYITEMDRAW; 
} 
else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage) 
{ 
	//奇数行 
	if(pLVCD->nmcd.dwItemSpec % 2)
		pLVCD->clrTextBk = RGB(255, 255, 128); 
	//偶数行 
	else 
		pLVCD->clrTextBk = RGB(128, 255, 255); 
	//继续 
	*pResult = CDRF_DODEFAULT; 
}
}

问题77:关于CWnd::PreSubClassWindow()

CWnd::PreSubclassWindow

virtual void PreSubclassWindow( );

说明: 框架调用这个成员函数以允许在窗口被子类化之前进行其它必要的子类化。重载这个函数以允许控件的动态子类化。这是个高级可重载函数。

CWnd中PreCreateWindow、PreSubclassWindow、SubclassWin

MFC(VC6.0)的CWnd及其子类中,有如下三个函数:

 class CWnd : public CCmdTarget {   

 public:         

	virtual BOOL PreCreateWindow(CREATESTRUCT& cs);   
	virtual void PreSubclassWindow();    
	BOOL SubclassWindow(HWND hWnd);   
 }; 

让人很不容易区分,不知道它们究竟干了些什么,在什么情况下要改写哪个函数? 想知道改写函数?

让我先告诉你哪个不能改写,那就是SubclassWindow。

Scott Meyers的杰作<>的第36条是这样的Differentiate between inheritance of interface and inheritance of implementation. 看了后你马上就知道,父类中的非虚拟函数是设计成不被子类改写的。

根据有无virtual关键字,我们在排除了SubclassWindow后,也就知道PreCreateWindow和PreSubClassWindow是被设计成可改写的。

接着的问题便是该在什么时候该写了。要知道什么时候该写,必须知道函数是在什么时候被调用,还有执行函数的想要达到的目的。

我们先看看对这三个函数,MSDN给的解释:

PreCreateWindow: Called by the framework before the creation of the Windows window attached to this CWnd object.

(译:在窗口被创建并attach到this指针所指的CWnd对象之前,被framework调用)

PreSubclassWindow: This member function is called by the framework to allow other necessary subclassing to occur before the window is subclassed.

(译:在window被subclassed之前被framework调用,用来允许其它必要的subclassing发生)

虽然我已有译文,但还是让我对CWnd的attach和窗口的subclass作简单的解释吧!

要理解attach,我们必须要知道一个C++的CWnd对象和窗口(window)的区别:window就是实在的窗口,而CWnd就是MFC用类对window所进行C++封装。attach,就是把窗口附加到CWnd对象上操作。附加(attach)完成后,CWnd对象才和窗口发生了联系。

窗口的subclass是指修改窗口过程的操作,而不是面向对象中的派生子类。

子类化允许你接管被子类化的窗口,使你对它有绝对的控制权。举个例子了来阐明一下:例如你需要一个只接受十六进制数字输入的文本编辑框,如果使用一个简单的 Edit控件,当用户输入十六进制以外的字符时,你既不知道也无计可施。也就是说,当用户进文本框中输入字符串 "zb+q*" 时,如果除了拒绝接受整个字符串以外几乎什么也不能做,至少这显得特别不专业。重要的是,你需要具有输入检测的能力,即每当用户输入一个字符到编辑框中时要能检测这个字符。

解释实现细节:当用户往文本框中输入字符时,Windows 会给Edit控件的窗口函数发送 WM_CHAR 消息。这个窗口函数本身寄生于 Windows 中,因此不能直接修改它。但是我们可以重定向这个消息使之发送到我们自己编写的窗口处理函数。如果自定义窗口要处理这个消息那就可以处理它,如果不处理就可以把这个消息转发到它原来窗口处理函数。通过这种方式,自定义的窗口处理函数就把它自己插入到 Windows 系统和 Edit 控件之间。

获取窗口旧的消息处理函数,设置新的消息处理函数,进行需要进行的消息处理,其他的交给旧的消息处理函数

LONG GetWindowLong(HWNDhWnd,intnlndex);

LONG SetWindowLong(HWNDhWnd,intnlndex,LONGdwNewLong);

LRESULT CallWindowProc(WNDPROC lpPrevWndFunc, HWND hWnd, UINT Msg, WPARAM wParam, LPARAM IParam);

示例:

Long OldWindowProc
OldWindowProc = GetWindowLong(/*你的窗口句柄*/,GWL_WNDPROC/* -4 */);
SetWindowLong(/*你的窗口句柄*/,GWL_WNDPROC/* -4 */,NewWndProc);
LRESULT NewWndProc(HWND hWnd, UINT message,WPARAM wParam,LPARAM lParam)
{
 if(message==/*某某消息值*/)
 {
    
 }
 return CallWindowProc(OldWndProc,hWnd,message,wParam,lParam);//不处理的交给旧的PROC
}

好了,PreCreateWindow由framework在窗口创建前被调用,函数名也说明了这一点,Pre应该是previous的缩写,PreSubclassWindow由framework在subclass窗口前调用。 这段话说了等于没说,你可能还是不知道,什么时候该改写哪个函数。罗罗嗦嗦的作者,还是用代码说话吧!源码之前,了无秘密(候捷语)。我们就看看MFC中的这三个函数都是这样实现的吧!

 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)            
 {     // allow modification of several common create parameters   
     CREATESTRUCT cs;    
     cs.dwExStyle = dwExStyle;    
     cs.lpszClass = lpszClassName;    
     cs.lpszName = lpszWindowName;    
     cs.style = dwStyle;    
     cs.x = x;    
     cs.y = y;    
     cs.cx = nWidth;    
     cs.cy = nHeight;    
     cs.hwndParent = hWndParent;    
     cs.hMenu = nIDorHMenu;    
     cs.hInstance = AfxGetInstanceHandle();    
     cs.lpCreateParams = lpParam;       
     if (!PreCreateWindow(cs))      {      
         PostNcDestroy();      
         return FALSE;    
     }       
     AfxHookWindowCreate(this);    
     HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,      
                                  cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,      
                                  cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);            
     return TRUE; 
 }  
// for child windows 
BOOL CWnd::PreCreateWindow(CREATESTRUCT& cs) {    
    if (cs.lpszClass == NULL)      
    {      // make sure the default window class is registered      
        VERIFY(AfxDeferRegisterClass(AFX_WND_REG));           
        // no WNDCLASS provided - use child window default      
        ASSERT(cs.style & WS_CHILD);      
        cs.lpszClass = _afxWnd;    
    }    
    return TRUE; 
}    

CWnd::CreateEx先设定cs(CREATESTRUCT),在调用真正的窗口创建函数::CreateWindowEx之前,调用了CWnd::PreCreateWindow函数,并把参数cs以引用的方式传递了进去。而CWnd的PreCreateWindow函数也只是给cs.lpszClass赋值而已。毕竟,窗口创建函数CWnd::CreateEx的诸多参数中,并没有哪个指定了所要创建窗口的窗口类,而这又是不可缺少的(请参考<>第三章)。所以当你需要修改窗口的大小、风格、窗口所属的窗口类等cs成员变量时,要改写PreCreateWindow函数。

 // From VS Install PathVC98MFCSRCWINCORE.CPP 
BOOL CWnd::SubclassWindow(HWND hWnd) {    
    if (!Attach(hWnd))      
        return FALSE;       
    // allow any other subclassing to occur    
    PreSubclassWindow();       
    // now hook into the AFX WndProc    
    WNDPROC* lplpfn = GetSuperWndProcAddr();    
    WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC,      
                                                  (DWORD)AfxGetAfxWndProc());    
    ASSERT(oldWndProc != (WNDPROC)AfxGetAfxWndProc());       
    if (*lplpfn == NULL)      
        *lplpfn = oldWndProc;  
    // the first control of that type created 
    #ifdef _DEBUG    
    else if (*lplpfn != oldWndProc)      
    {             
        ::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)oldWndProc);    
    } 
    #endif       
    return TRUE; }  
void CWnd::PreSubclassWindow() {    
    // no default processing 
} 

CWnd::SubclassWindow先调用函数Attach(hWnd)让CWnd对象和hWnd所指的窗口发生关联。接着在用::SetWindowLong修改窗口过程(subclass)前,调用了PreSubclassWindow。CWnd::PreSubclassWindow则是什么都没有做。

在CWnd的实现中,除了CWnd::SubclassWindow会调用PreSubclassWindow外,还有一处。上面所列函数CreateEx的代码,其中调用了一个AfxHookWindowCreate函数,见下面代码:

// From VS Install PathVC98MFCSRCWINCORE.CPP 
BOOL CWnd::CreateEx() {    
    // allow modification of several common create parameters              
    if (!PreCreateWindow(cs))        
    {        
        PostNcDestroy();        
        return FALSE;      
    }           
    AfxHookWindowCreate(this);  
    HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,        
                                 cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,        
                                 cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);                  
    return TRUE; 
}   

接着察看AfxHookWindowCreate的代码:

 // From VS Install PathVC98MFCSRCWINCORE.CPP 
 void AFXAPI AfxHookWindowCreate(CWnd* pWnd) {              
     if (pThreadState->m_hHookOldCbtFilter == NULL)        
     {        
         pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT, _AfxCbtFilterHook, NULL,::GetCurrentThreadId());        
      if (pThreadState->m_hHookOldCbtFilter == NULL)          
     {
         AfxThrowMemoryException();      
     }      
 } 

其主要作用的::SetWindowsHookEx函数用于设置一个挂钩函数(Hook函数)AfxCbtFilterHook,每当Windows产生一个窗口时(还有许多其它类似,请参考<<深入浅出MFC>>第9章,563页),就会调用你设定的Hook函数。

这样设定完成后,回到CWnd::CreateEx函数中,执行::CreateWindowEx进行窗口创建,窗口一产生,就会调用上面设定的Hook函数AfxCbtFilterHook。而正是在_AfxCbtFilterHook中对函数PreSubclassWindow进行了第二次调用。见如下代码:

// From VS Install PathVC98MFCSRCWINCORE.CPP /**//**//**// // Window creation hooks  
LRESULT CALLBACK _AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam) {                  
    // connect the HWND to pWndInit      
    pWndInit->Attach(hWnd);    
    // allow other subclassing to occur first    
    pWndInit->PreSubclassWindow();         
    {      
        // subclass the window with standard AfxWndProc      
        oldWndProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)afxWndProc);      
        ASSERT(oldWndProc != NULL);      
        *pOldWndProc = oldWndProc;    
    }    
} 

也在调用函数SetWindowLong进行窗口subclass前调用了PreSubclassWindow. 通常情况下窗口是由用户创建的 CWnd::Create(..)

●在此流程中,MFC提供一个机会"PreCreateWindow()供用户在创建前作点手脚

而对于对话框等,窗口是通过subclass方式交给用户的系统读入对话框模板,建立其中各个子窗口 。

然后将各子窗口的 消息处理函数替换成 对应的C++对象 的消息处理函数 (Subclass:子类化,或"接管") ,

然后,这个子窗口就会按类中定义的方式来动作了。

在此过程中,调用的是CWnd:SubclassWindow( HWND hWnd );

●在此流程中,MFC提供一个机会"PreSubclassWindow" 供用户在关联前作点手脚

具体来说,如果你定义一个窗口(如CButton派生类CMyButton),然后使用对话框数据交换将一个按钮与自己的派生类对象关联,这时候,一些"建立前"的处理就应该写在"PreSubclassWindow"中。

如果你用的不是"对话框数据关联",而是在OnInitDialg中自己创建m_mybtn.Create(...)

这时候,一些"建立前"的处理就应该写在 "PreCreateWindow"中。

问题78:关于HitTest()

CTreeCtrl::HitTest的语法结构:

HTREEITEM HitTest(
   CPoint pt,
   UINT* pFlags = NULL
) const;
HTREEITEM HitTest(
   TVHITTESTINFO* pHitTestInfo 
) const;

参数的取值及含义:

例子: 在CViewTree类中的树点击事件:

void CViewTree::OnClickTree(NMHDR* pNMHDR, LRESULT* pResult)
{
CPoint pt;
GetCursorPos(&pt);    //得到光标位置
this->ScreenToClient(&pt);
 
UINT uFlag;
HTREEITEM hItem = this->HitTest(pt, &uFlag);  
 
if ((TVHT_NOWHERE & uFlag)) 
{
    return;
}
if ((hItem != NULL) && (TVHT_ONITEMSTATEICON & uFlag))
{
     ......//实现功能   
       }

}

或在CFileView类中的树点击事件:

void CFileView::OnClickTree(NMHDR* pNMHDR, LRESULT* pResult)
{
	CPoint pt;
	GetCursorPos(&pt);
	m_wndFileView.ScreenToClient(&pt);
	UINT uFlag;
	HTREEITEM hItem = m_wndFileView.HitTest(pt, &uFlag);
	if ((TVHT_NOWHERE & uFlag)) 
	{
    	return;
	}
 
	if ((hItem != NULL) && (TVHT_ONITEMSTATEICON & uFlag))  //根据uFlag的值的情况
	{
    	。。。//实现功能
	}
   
}

uFlag=16:点中树枝节点,

uFlag=8:点中的是叶节点的折叠处,

uFlag=64:点中树枝右面的复选框,

uFlag=2:点中复选框右面的灯泡,

uFlag=4:点中灯泡右面的文字

......

问题79:关于LVN_GETDISPINFO

Handling the LVN_GETDISPINFO Notification

Virtual list controls maintain very little item information. Except for the item selection and focus information, all item information is managed by the owner of the control. Information is requested by the framework via a LVN_GETDISPINFO notification message. To provide the requested information, the owner of the virtual list control (or the control itself) must handle this notification. This can easily be done using the Class Wizard (see Mapping Messages to Functions). The resultant code should look something like the following example (where CMyDialog owns the virtual list control object and the dialog is handling the notification):

ON_NOTIFY(LVN_GETDISPINFO, IDC_LIST3, &CMyDialog::OnLvnGetdispinfoList3)

In the handler for the LVN_GETDISPINFO notification message, you must check to see what type of information is being requested. The possible values are:

  • LVIF_TEXT The pszText member must be filled in.

  • LVIF_IMAGE The iImage member must be filled in.

  • LVIF_INDENT The iIndent member must be filled in.

  • LVIF_PARAM The lParam member must be filled in. (Not present for sub-items.)

  • LVIF_STATE The state member must be filled in.

You should then supply whatever information is requested back to the framework.

The following example (taken from the body of the notification handler for the list control object) demonstrates one possible method by supplying information for the text buffers and image of an item:

NMLVDISPINFO *pDispInfo = reinterpret_cast(pNMHDR);
LVITEM *pItem = &(pDispInfo)->item;

int iItem = pItem->iItem;

if (pItem->mask & LVIF_TEXT) //valid text buffer?
{
   switch (pItem->iSubItem)
   {
   case 0: //fill in main text
      _tcscpy_s(pItem->pszText, pItem->cchTextMax,
                m_Items[iItem].m_strItemText);
      break;
   case 1: //fill in sub item 1 text
      _tcscpy_s(pItem->pszText, pItem->cchTextMax,
                m_Items[iItem].m_strSubItem1Text);
      break;
   case 2: //fill in sub item 2 text
      _tcscpy_s(pItem->pszText, pItem->cchTextMax,
                m_Items[iItem].m_strSubItem2Text);
      break;
   }
}

if (pItem->mask & LVIF_IMAGE) //valid image?
{
   pItem->iImage = m_Items[iItem].m_iImage;
}

问题80:关于MLVDISPINFOW

NMLVDISPINFOW structure (commctrl.h)

Contains information about an LVN_GETDISPINFO or LVN_SETDISPINFO notification code. This structure is the same as the LV_DISPINFO structure, but has been renamed to fit standard naming conventions.

Syntax

typedef struct tagLVDISPINFOW {
  NMHDR   hdr;
  LVITEMW item;
} NMLVDISPINFOW, *LPNMLVDISPINFOW;

Members

hdr

Type: NMHDR

NMHDR structure that contains information about this notification code.

item

Type: LVITEM

LVITEM structure that identifies the item or subitem. The structure either contains or receives information about the item. The mask member contains a set of bit flags that specify which item attributes are relevant. For more information on the available bit flags, see LVITEM.

Remarks

If the LVITEM structure is receiving item text, the pszText and cchTextMax members specify the address and size of a buffer. You can either copy text to the buffer or assign the address of a string to the pszText member. In the latter case, you must not change or delete the string until the corresponding item text is deleted or two additional LVN_GETDISPINFO messages have been sent.

If you are handling the LVN_GETDISPINFO message, you can set the LVIF_DI_SETITEM flag in the mask member of the LVITEM structure. This tells the operating system to store the requested list item information and not ask for it again. For list-view controls with the LVS_REPORT style, this flag only applies to the first (subitem 0) column's information. The control will not store information for subitems.

问题81:关于Wnd::PreTranslateMessage()

CWnd::PreTranslateMessage

virtual BOOL PreTranslateMessage( MSG* pMsg );

返回值: 如果消息被转换但是不会被分派,则返回非零值;如果消息没有被转换并且要被分派,则返回0。

参数:

参数 说明
pMsg 指向一个MSG结构,其中包含了要处理的消息。

说明: CWinApp类使用这个函数以在消息被分派到Windows的TranslateMessage和DispatchMessage函数之前进行转换。

你可能感兴趣的:(c++,mfc,c++,microsoft)